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).

  1. Créez les manifestes deployment-nginx.yaml et service-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
    
  2. Déployez ces ressources :

    kubectl apply -f deployment-nginx.yaml
    kubectl apply -f service-nginx.yaml
    kubectl get deploy,svc,pod
    
  3. Testez la résolution DNS depuis un autre Pod :

    Lancez un Pod temporaire et utilisez curl pour contacter le service nginx-service par 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 Service de type NodePort est également accessible via son ClusterIP. 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#

  1. 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
    
  2. Depuis le Pod dans test-ns, essayez de contacter nginx-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.yaml contenant le code ci-dessus. Deployez le (kubectl apply -f replicaset-ubuntusleep.yaml)

  • Constatez la création des 3 pods (utilisez l’option -o wide de 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.yaml contenant le manifeste ci-dessus. Déployez-le :

    kubectl apply -f deployment-ubuntusleep.yaml
    
  • Constatez la création du Deployment, du ReplicaSet associé et des Pods :

    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 /back puis 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 :