Objets : suite#
Vous avez vu deux objets essentiels : pod (pour lancer un conteneur) et service (pour pouvoir contacter des pods rendant le même “service” via un point d’entrée unique).
Note
Remarquez que les pods sont des objets en base de données pour lesquels il y a directement un objet ‘réel’ (un conteneur) qui est créé. C’est le rôle du scheduler de veiller à ce que le conteneur associé existe.
Par contre, concernant l’objet “service”, c’est un peu plus ‘virtuel’. Dans la suite, lorsqu’on parle d’objet kubernetes, on parle de l’objet en base de données qui ne donne pas nécessairement lieu à un objet ‘réel’.
Service#
Dans Kubernetes, un Service est un objet qui fournit une abstraction pour exposer un ensemble de Pods. Vous l’avez déjà rencontré, et ici on va aller un peu plus loin en voyant qu’il y a plusieurs types de services. On se concentre ici sur les services de type “NodePort” et “ClusterIP”.
Résolution DNS et communication intra-cluster#
L’un des avantages majeurs des Services est la résolution DNS automatique. Chaque Service obtient une entrée DNS stable dans le cluster, permettant aux autres Pods de le contacter par son nom, indépendamment de l’adresse IP des Pods sous-jacents.
Expérimentation#
Nous allons créer un Deployment avec un serveur NGINX et l’exposer via un Service de type ClusterIP (le type par défaut, qui a ça de moins que le NodePort : il n’expose rien vers l’extérieur du cluster).
Créez les manifestes
deployment-nginx.yamletservice-nginx.yaml:# deployment-nginx.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80 --- # service-nginx.yaml apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx ports: - protocol: TCP port: 80 targetPort: 80 type: ClusterIP
Déployez ces ressources :
kubectl apply -f deployment-nginx.yaml kubectl apply -f service-nginx.yaml kubectl get deploy,svc,pod
Testez la résolution DNS depuis un autre Pod :
Lancez un Pod temporaire et utilisez
curlpour contacter le servicenginx-servicepar son nom.kubectl run -it --rm --image=busybox:1.28 dns-test -- sh # Une fois dans le shell du pod dns-test: nslookup nginx-service wget http://nginx-service -O -
Vous devriez voir la page d’accueil de NGINX, prouvant que la résolution de nom fonctionne !
ClusterIP vs. NodePort#
ClusterIP : Expose le Service sur une adresse IP interne au cluster. C’est le type par défaut et il rend le Service accessible uniquement depuis l’intérieur du cluster.
NodePort : Expose le Service sur un port statique sur l’adresse IP de chaque nœud. Un
Servicede typeNodePortest également accessible via sonClusterIP. Cela permet d’accéder au service depuis l’extérieur du cluster.
Communication entre Namespaces#
Par défaut, un Pod peut contacter un Service dans un autre namespace en utilisant le nom de domaine complet (FQDN) du service, qui suit le format : <nom-service>.<nom-namespace>.svc.cluster.local.
Expérimentation#
Créez un nouveau namespace et un Pod de test :
kubectl create namespace test-ns kubectl run -it --rm --image=busybox:1.28 dns-test-ns -n test-ns -- sh
Depuis le Pod dans
test-ns, essayez de contacternginx-service:# Ceci échouera car le service n'est pas dans le même namespace wget http://nginx-service -O - # Ceci fonctionnera en utilisant le FQDN wget http://nginx-service.default.svc.cluster.local -O -
Cela démontre que la communication inter-namespace est possible mais nécessite de spécifier le nom complet du service.
ReplicaSet#
Un objet de type replicaSet permet de demander l’existence d’un certain nombre de répliques de pods. Lorsqu’on crée un objet de type replicaset, le “controller-manager” de Kubernetes va se charger de garantir qu’à tout moment les objets pods existent. Par exemple :
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: ubuntusleep
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: ubuntusleep
template:
metadata:
labels:
app: ubuntusleep
spec:
containers:
- name: ubuntusleep
image: ubuntu:22.04
args: ["sleep","infinity"]
Pour la curiosité, le code source du contrôleur est ici : kubernetes/kubernetes
La documentation est ici : https://kubernetes.io/fr/docs/concepts/workloads/controllers/replicaset/
Essayez :
Créez un fichier yaml
replicaset-ubuntusleep.yamlcontenant le code ci-dessus. Deployez le (kubectl apply -f replicaset-ubuntusleep.yaml)Constatez la création des 3 pods (utilisez l’option
-o widede kubectl pour savoir sur quels nœuds sont les pods)Entrez dans les pods (ça c’est juste pour manipuler !)
Utilisez kubectl pour avoir des informations sur le replicaset :
kubectl get rs kubectl describe rs ubuntusleep
Supprimez un pod sans supprimer le replicaset. Que se passe-t-il ?
Modifiez la valeur du nombre de replicas et appliquez de nouveau le fichier (
kubectl apply -f replicaset-ubuntusleep.yaml). Que se passe-t-il ?Peut on mettre le nombre de replicas à 0 ? Et à -1 ?
Selon vous, à quoi sert ‘selector’ ? Comment pouvez vous vérifier votre idée ?
Observez les labels d’un de vos pod avec la commande suivante :
kubectl get pod ubuntusleep-5778cd6867-dcm85 -o jsonpath={.metadata.labels}
Si vous changez les labels à donner à vos pods dans votre replicaset, cela modifie-t-il les pods déjà existants ? Et les nouveaux ?
Deployment#
Les replicaset c’est chouette, mais si on veut par exemple modifier la version de l’image de conteneurs déployés, ca ne va pas fonctionner. Idem avec les labels (vous l’avez vu précédemment)
Un objet de type deployment est un objet kubernetes qui va s’assurer de l’existence d’un ensemble de pods et gérer la migration d’une version à une autre (labels, version d’image,…).
La documentation est ici : https://kubernetes.io/fr/docs/concepts/workloads/controllers/deployment/
Par exemple :
apiVersion: apps/v1
kind: Deployment
metadata:
name: ubuntusleep
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: ubuntusleep
template:
metadata:
labels:
app: ubuntusleep
spec:
containers:
- name: ubuntusleep
image: ubuntu:22.04
args: ["sleep","infinity"]
Essayez :
Créez un fichier yaml
deployment-ubuntusleep.yamlcontenant le manifeste ci-dessus. Déployez-le :kubectl apply -f deployment-ubuntusleep.yaml
Constatez la création du
Deployment, duReplicaSetassocié et desPods:kubectl get deploy,rs,pod -o wide kubectl describe deploy ubuntusleep
Observez la stratégie de déploiement (par défaut RollingUpdate) :
kubectl get deploy ubuntusleep -o yaml
Mettez à jour l’image pour simuler un changement de version (ex:
ubuntu:24.04) et suivez le déploiement :kubectl set image deploy/ubuntusleep ubuntusleep=ubuntu:24.04 kubectl rollout status deploy ubuntusleep
Consultez l’historique des déploiements et les révisions :
kubectl rollout history deploy ubuntusleep kubectl rollout history deploy ubuntusleep --revision=1
Revenez en arrière (rollback) à la révision précédente :
kubectl rollout undo deploy/ubuntusleep # ou vers une révision précise kubectl rollout undo deploy/ubuntusleep --to-revision=1
Expérimentez l’échelle (scaling) du déploiement :
kubectl scale deploy/ubuntusleep --replicas=5 kubectl get rs,pod -o wide
Supprimez le Deployment. Que deviennent le ReplicaSet et les Pods ?
kubectl delete -f deployment-ubuntusleep.yaml kubectl get deploy,rs,pod
Ingress#
Un objet de type ingress permet d’exposer un hôte http à l’extérieur du cluster et rediriger vers un service donné. (Ce qu’il se passe “en arrière plan”, c’est qu’il permet au reverse proxy interne au cluster -le ingress controller- de définir sa configuration) Par exemple :
Prérequis: Services et Pods de test#
Créez deux Pods et deux Services pour tester l’Ingress: un NGINX (port 80) et un serveur d’écho HTTP (port 8080).
apiVersion: v1
kind: Pod
metadata:
name: back
namespace: default
labels:
app: back
spec:
containers:
- name: http-echo
image: hashicorp/http-echo:0.2.3
args:
- "-text=Je suis dans le pod nommé BACK"
- "-listen=:80"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: back
namespace: default
spec:
selector:
app: back
ports:
- name: http
port: 80
targetPort: 80
type: ClusterIP
---
apiVersion: v1
kind: Pod
metadata:
name: front
namespace: default
labels:
app: front
spec:
containers:
- name: http-echo
image: hashicorp/http-echo:0.2.3
args:
- "-text=Je suis dans le pod nommé FRONT"
- "-listen=:8080"
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: front
namespace: default
spec:
selector:
app: front
ports:
- name: http
port: 8080
targetPort: 8080
type: ClusterIP
Enregistrez ce contenu dans un fichier
services-pods-echo.yaml, puis appliquez-le:kubectl apply -f services-pods-echo.yaml kubectl get pod,svc -o wide
Expérimentations#
Créez le manifest suivant dans un fichier
ingress-echo.yaml, en remplaçant 192.168.49.2 par l’IP d’un des noeuds de votre cluster (minikube node list):apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: front namespace: default spec: rules: - host: echo.192.168.49.2.nip.io http: paths: - path: / pathType: Prefix backend: service: name: front port: number: 8080
Déployez le :
kubectl apply -f ingress-echo.yaml kubectl get ingress
Vérifiez la prise en compte par le contrôleur d’ingress :
kubectl describe ingress front
Testez l’accès HTTP en vous rendant avec votre navigateur à l’adresse echo.192.168.49.2.nip.io (en remplaçant 192.168.49.2 par l’IP de votre cluster)
Ajoutez une règle pour accéder au back sur le chemin
/backpuis testez.
Autres objets de type “contrôleur”#
D’autres types de “contrôleurs” existent, cependant pour les besoins standards d’un développeur, il n’y a pas besoin de les connaître. Cependant, si vous le souhaitez, allez jeter un oeil aux suivants :