Skip to content

Scaling et self healing

Une gestion façon "bétail"

Bien que l'analogie puisse paraître violente, sur Kubernetes on traitera les pods comme du bétail et non comme des animaux de compagnie que l'on apprécie. Si un animal meurt au sein du bétail, c'est triste, mais on continue à avancer. C'est l'idée avec les pods qui peuvent augmenter leur volume à la demande et s'autosoigner quand cela est nécessaire. Pour l'utilisateur cette mécanique est transparente et quoi qu'il arrive il pourra continuer à utiliser l'application.

L'objet deployment

Le deployment est un objet clé qui permet de décrire et de gérer les applications conteneurisées de manière déclarative. C’est l'un des objets les plus couramment utilisés pour déployer des applications sur Kubernetes.

Qu'est-ce qu'un Deployment ?

Un Deployment est un contrôleur Kubernetes qui gère un ou plusieurs Pods et s’assure que le bon nombre d'exemplaires de ces Pods sont en cours d’exécution à tout moment. Les points principaux d’un Deployment sont les suivants :

  • Déclaratif : Vous spécifiez l'état souhaité de votre application (par exemple, combien de réplicats de votre Pod vous voulez) dans un fichier de configuration YAML ou JSON. Kubernetes se charge de rendre cet état réel.
  • Auto-réparation : Si un Pod associé à un Deployment tombe en panne, Kubernetes le remplacera automatiquement pour garantir que l’état souhaité est respecté.
  • Mises à jour progressives (rolling updates) : Lorsqu'une nouvelle version d’une application est déployée, le Deployment met à jour les Pods progressivement, de sorte que l’application reste disponible pendant la mise à jour.
  • Rollback : Il est possible de revenir à une version précédente si une mise à jour échoue.

Relation entre Deployment, Pods et Services

-> Pods

  • Le Deployment spécifie le nombre de Pods à exécuter (appelés réplicats). Par exemple, si vous spécifiez replicas: 3, Kubernetes s’assurera qu'il y a toujours trois instances de votre application en cours d'exécution, en créant ou en supprimant des Pods au besoin. -> Services

  • Le Service interagit avec un Deployment en servant de point d’accès aux Pods gérés par le Deployment. Il crée un load balancing entre les différentes réplicats des Pods déployés, ce qui permet de distribuer la charge réseau uniformément.

  • Le Service et le Deployment ne sont pas directement liés dans la configuration, mais le Service cible les Pods créés par un Deployment grâce aux labels. Ces labels permettent de sélectionner dynamiquement les Pods correspondant à un critère (généralement le même que celui des Pods gérés par le Deployment).

Impact sur l'autoscaling et le downscaling

  1. Autoscaling (HPA - Horizontal Pod Autoscaler) :

    • Kubernetes prend en charge l’autoscaling horizontal des Pods via un objet appelé Horizontal Pod Autoscaler (HPA). Le HPA ajuste dynamiquement le nombre de réplicats d’un Deployment en fonction des besoins de ressources, par exemple la charge CPU ou mémoire.
    • Le HPA surveille les métriques (via le serveur de métriques de Kubernetes) et modifie le nombre de Pods dans le Deployment en conséquence.
      • Scaling Up : Si l’application rencontre une charge accrue (par exemple, une augmentation de l'utilisation du CPU), HPA peut augmenter le nombre de réplicats du Deployment pour faire face à cette charge.
      • Scaling Down : Si la charge diminue, HPA peut réduire le nombre de réplicats pour éviter une consommation inutile de ressources.
  2. Downscaling :

    • Le downscaling réduit le nombre de réplicats dans un Deployment lorsque la demande diminue. Cela peut être fait manuellement en modifiant l'attribut replicas du Deployment ou automatiquement via HPA.
    • Par exemple, si le HPA détecte que la charge CPU moyenne sur les Pods est inférieure à un seuil défini, il réduira le nombre de Pods. Cela permet d’économiser les ressources (comme le CPU ou la RAM) lorsque la demande est faible.
  3. Ressources minimales et maximales :

    • Lorsque vous configurez un HPA pour un Deployment, vous définissez souvent un nombre minimum et maximum de Pods (par exemple, entre 2 et 10 Pods), ce qui permet de gérer les besoins en fonction de la charge.
    • Kubernetes ajuste ensuite automatiquement le nombre de Pods à l'intérieur de cette plage en fonction des métriques surveillées.

Mécanismes d'update et de rollback

  • Rolling Update : Un Deployment met à jour les Pods de manière progressive pour garantir que les interruptions de service soient minimisées. Par exemple, au lieu de supprimer tous les anciens Pods en une seule fois, il peut les mettre à jour un par un.
  • Rollback : Si une mise à jour échoue (par exemple, en raison de problèmes avec la nouvelle version de l'application), Kubernetes permet de revenir automatiquement ou manuellement à la version précédente du Deployment.

Mécanique d'upscale

schéma du scaling d'une application dans Kubernetes

  • Interaction entre les services et les pods :

    • L'utilisateur envoie une requête HTTP à l'application Guestbook via son service.
    • Le service GuestbookSvc achemine cette requête vers le pod Guestbook.
    • Le pod Guestbook communique avec le service Redis pour récupérer les données.
    • Le service Redis (RedisSvc) envoie la requête au pod Redis, qui retourne les données via les mêmes canaux.
  • Processus de scaling :

    • Le pod Redis envoie une requête au Control Plane lorsqu'il y a une charge élevée (ou selon une règle d'auto-scaling).
    • Le Control Plane interagit avec les worker nodes pour programmer de nouveaux pods Redis, ce qui permet de répartir la charge.
    • Ensuite, les requêtes suivantes faites par l'application Guestbook sont traitées par les nouveaux pods Redis après l'auto-scaling.

Mise en pratique du scaling & zoom sur l'objet deployment

Voici un déploiement adapté au pod guestbook.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: guestbook
  labels:
    app: guestbook
spec:
  selector:
    matchLabels:
      app: guestbook
      tier: frontend
  replicas: 3
  template:
    metadata:
      labels:
        app: guestbook
        tier: frontend
    spec:
      containers:
        - name: guestbook
          image: registry.gitlab.com/gonzalez-marc/guestbook:latest
          imagePullPolicy: Always
          ports:
            - name: http-server
              containerPort: 3000

Voici un déploiement adapté à redis

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  selector:
    matchLabels:
      app: redis
      tier: backend
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
        role: master
        tier: backend
    spec:
      containers:
      - name: master
        image: redis
        ports:
        - containerPort: 6379
  • L'api utilisée est sensiblement différente.
  • La section template reprend le manifeste du pod.

-> Commencez par supprimer les précédents pods

sh
kubectl delete pods redis
kubectl delete pods guestbook

-> Déployez redis

sh
kubectl apply -f redis-deployment.yaml
# deployment.apps/redis created

-> Vérifiez le bon fonctionnement de la commande

sh
kubectl get deployments.apps
# NAME    READY   UP-TO-DATE   AVAILABLE   AGE
# redis   1/1     1            1           25s

-> Inspectez le déploiement

sh
kubectl describe deployments.apps redis
# Name:                   redis
# Namespace:              default
# CreationTimestamp:      Sat, 12 Oct 2024 16:42:30 +0200
# Labels:                 <none>
# Annotations:            deployment.kubernetes.io/revision: 1
# Selector:               app=redis,tier=backend
# Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
# StrategyType:           RollingUpdate
# MinReadySeconds:        0
# RollingUpdateStrategy:  25% max unavailable, 25% max surge
# Pod Template:
#   Labels:  app=redis
#            role=master
#            tier=backend
#   Containers:
#    master:
#     Image:         redis
#     Port:          6379/TCP
#     Host Port:     0/TCP
#     Environment:   <none>
#     Mounts:        <none>
#   Volumes:         <none>
#   Node-Selectors:  <none>
#   Tolerations:     <none>
# Conditions:
#   Type           Status  Reason
#   ----           ------  ------
#   Available      True    MinimumReplicasAvailable
#   Progressing    True    NewReplicaSetAvailable
# OldReplicaSets:  <none>
# NewReplicaSet:   redis-94997cf6f (1/1 replicas created)
# Events:
#   Type    Reason             Age    From                   Message
#   ----    ------             ----   ----                   -------
#   Normal  ScalingReplicaSet  2m35s  deployment-controller  Scaled up replica set redis-94997cf6f to 1

-> Déployez ensuite guestbook

sh
kubectl apply -f guestbook-deployment.yaml
# deployment.apps/guestbook created

-> Constatez l'état du déploiement

sh
kubectl get deployments.apps
# NAME        READY   UP-TO-DATE   AVAILABLE   AGE
# guestbook   3/3     3            3           4m52s
# redis       1/1     1            1           10m

Les 3 pods sont démarrés, et disponibles

-> Observez l'état du service guestbook précédemment déployé

sh
kubectl describe svc frontend
# Name:                     frontend
# Namespace:                default
# Labels:                   app=guestbook
#                           tier=frontend
# Annotations:              <none>
# Selector:                 app=guestbook
# Type:                     LoadBalancer
# IP Family Policy:         SingleStack
# IP Families:              IPv4
# IP:                       10.105.8.91
# IPs:                      10.105.8.91
# Port:                     <unset>  3000/TCP
# TargetPort:               http-server/TCP
# NodePort:                 <unset>  30812/TCP
# Endpoints:                10.244.0.18:3000,10.244.0.19:3000,10.244.0.20:3000
# Session Affinity:         None
# External Traffic Policy:  Cluster
# Internal Traffic Policy:  Cluster
# Events:                   <none>

La section Endpoints a désormais 3 ips. Chacune des requêtes entrantes sur le système seront routées sur l'un ou l'autre des nœuds

-> Ralancez le tunnel sur minikube pour constater le bon fonctionnement de l'application : http://10.105.8.91:3000/

-> Introduisez maintenant un peu de chaos en supprimant un des pods déployés

Listez les pods en observant la liste en continue

sh
kubectl get pods -w
# NAME                        READY   STATUS    RESTARTS   AGE
# guestbook-5b67f6d74-fnshp   1/1     Running   0          13m
# guestbook-5b67f6d74-pqhfc   1/1     Running   0          73s
# guestbook-5b67f6d74-zr9gk   1/1     Running   0          13m
# redis-94997cf6f-pf769       1/1     Running   0          22m

Supprimez le premier de la liste

sh
kubectl delete po guestbook-5b67f6d74-fnshp
# guestbook-5b67f6d74-fnshp   1/1     Terminating   0          14m
# guestbook-5b67f6d74-pszc9   0/1     Pending       0          0s
# guestbook-5b67f6d74-pszc9   0/1     Pending       0          0s
# guestbook-5b67f6d74-pszc9   0/1     ContainerCreating   0          0s
# guestbook-5b67f6d74-fnshp   0/1     Error               0          14m
# guestbook-5b67f6d74-fnshp   0/1     Error               0          14m
# guestbook-5b67f6d74-fnshp   0/1     Error               0          14m
# guestbook-5b67f6d74-pszc9   1/1     Running             0          3s

Mise en pratique du self healing

Que se passe-t-il en cas de crash d'un noeud ?

schéma du self healing d'une application dans Kubernetes

  • Le controller manager observe en permanence l'état des noeuds pour s'assurer que l'état observé matche avec l'état désiré.
  • Si le worker node 2 tombe alors il replanifie immédiatement les pods manquants.
  • Le scheduler en prend information et déploie les pods sur les noeuds existants.

Mise en pratique

-> Ajoutez un noeud à votre cluster

sh
minikube node list
# minikube        192.168.49.2
minikube node add
# Adding node m02 to cluster minikube as [worker]
# Cluster was created without any CNI, adding a node to it might cause broken networking.
# Starting "minikube-m02" worker node in "minikube" cluster
# Pulling base image v0.0.45 ...
# Creating docker container (CPUs=2, Memory=2200MB) ...
# Preparing Kubernetes v1.31.0 on Docker 27.2.0 ...
# Verifying Kubernetes components...
# Successfully added m02 to minikube!

-> Listez les pods, de manière détaillée, tout devrait être fonctionnel

sh
kubectl get pods -o wide
# NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
# guestbook-5b67f6d74-pqhfc   1/1     Running   0          42m   10.244.0.21   minikube   <none>           <none>
# guestbook-5b67f6d74-pszc9   1/1     Running   0          39m   10.244.0.22   minikube   <none>           <none>
# guestbook-5b67f6d74-zr9gk   1/1     Running   0          54m   10.244.0.20   minikube   <none>           <none>
# redis-94997cf6f-pf769       1/1     Running   0          64m   10.244.0.13   minikube   <none>           <none>

On constate qu'ils tournent tous sur le premier noeud déclaré

-> Supprimez 2 pods

sh
kubectl delete pods guestbook-5b67f6d74-pqhfc
# pod "guestbook-5b67f6d74-pqhfc" deleted
kubectl delete pods guestbook-5b67f6d74-zr9gk
# pod "guestbook-5b67f6d74-zr9gk" deleted

-> Si tout s'est bien passé ils seront recréés automatiquement et répartis sur les différents noeuds

sh
kubectl get pods -o wide
# NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
# guestbook-5b67f6d74-5fcsg   1/1     Running   0          11m   10.244.1.4   minikube-m02   <none>           <none>
# guestbook-5b67f6d74-kh5jz   1/1     Running   0          11m   10.244.1.3   minikube-m02   <none>           <none>
# guestbook-5b67f6d74-t4nnk   1/1     Running   0          11m   10.244.0.3   minikube       <none>           <none>
# redis-94997cf6f-n8c4g       1/1     Running   0          11m   10.244.1.2   minikube-m02   <none>           <none>

Aller plus loin avec l'upscale

Kubernetes propose plusieurs façons pour upscale :

-> Via la commande scale

sh
kubectl scale deployment guestbook --replicas 8
# NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
# guestbook-5b67f6d74-9z875   1/1     Running   0          24m   10.244.0.2   minikube       <none>           <none>
# guestbook-5b67f6d74-hf6gk   1/1     Running   0          7s    10.244.2.3   minikube-m03   <none>           <none>
# guestbook-5b67f6d74-mt97k   1/1     Running   0          7s    10.244.2.4   minikube-m03   <none>           <none>
# guestbook-5b67f6d74-qrr56   1/1     Running   0          7s    10.244.2.2   minikube-m03   <none>           <none>
# guestbook-5b67f6d74-rncgd   1/1     Running   0          7s    10.244.1.3   minikube-m02   <none>           <none>
# guestbook-5b67f6d74-rxxjt   1/1     Running   0          7s    10.244.1.4   minikube-m02   <none>           <none>
# guestbook-5b67f6d74-sn6q7   1/1     Running   0          24m   10.244.0.4   minikube       <none>           <none>
# guestbook-5b67f6d74-tz6j5   1/1     Running   0          18m   10.244.1.2   minikube-m02   <none>           <none>
# redis-94997cf6f-pvjft       1/1     Running   0          24m   10.244.0.3   minikube       <none>           <none>

-> Via la commande edit

sh
kubectl edit deployment.apps guestbook

Le manifeste de déploiement est alors directement édité sur le cluster

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"guestbook"},"name":"guestbook","namespace":"default"},"spec":{"replicas":3,"selector":{"matchLabels":{"app":"guestbook","tier":"frontend"}},"template":{"metadata":{"labels":{"app":"guestbook","tier":"frontend"}},"spec":{"containers":[{"image":"registry.gitlab.com/gonzalez-marc/guestbook:latest","imagePullPolicy":"Always","name":"guestbook","ports":[{"containerPort":3000,"name":"http-server"}]}],"imagePullSecrets":[{"name":"regcred"}]}}}}
  creationTimestamp: "2024-10-12T19:49:56Z"
  generation: 2
  labels:
    app: guestbook
  name: guestbook
  namespace: default
  resourceVersion: "5417"
  uid: a2749894-b9de-47ab-911e-9b361fe576d9
spec:
  progressDeadlineSeconds: 600
  replicas: 8
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: guestbook
      tier: frontend
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: guestbook
        tier: frontend
    spec:
      containers:
      - image: registry.gitlab.com/gonzalez-marc/guestbook:latest
        imagePullPolicy: Always
        name: guestbook
        ports:
        - containerPort: 3000
          name: http-server
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      imagePullSecrets:
      - name: regcred
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  availableReplicas: 8
  conditions:
  - lastTransitionTime: "2024-10-12T19:49:56Z"
    lastUpdateTime: "2024-10-12T19:50:02Z"
    message: ReplicaSet "guestbook-5b67f6d74" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  - lastTransitionTime: "2024-10-12T21:00:29Z"
    lastUpdateTime: "2024-10-12T21:00:29Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  observedGeneration: 2
  readyReplicas: 8
  replicas: 8
  updatedReplicas: 8

Cette méthode est adaptée pour faire du debug mais une seule source de vérité devrait faire foi quand il s'agit de gérer de l'orchestration ; c'est la clé de la stabilité

Rollout

Dans Kubernetes, un manifeste YAML est utilisé pour décrire l'état désiré de vos objets Kubernetes, y compris les déploiements et les rollouts. Vous pouvez configurer, démarrer, arrêter, ou annuler un rollout à l'aide de fichiers manifestes.

Voici comment gérer des rollouts via un manifeste.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 10
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: nginx
        image: nginx:1.16
        ports:
        - containerPort: 80
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1

Explication de la stratégie

  • replicas: 10 : Nous avons augmenté le nombre de réplicas à 10 pour que la mise à jour prenne plus de temps.
  • maxUnavailable: 1 : Seul un pod peut être indisponible à la fois pendant le rollout.
  • maxSurge: 1 : Seul un nouveau pod sera lancé à la fois, ce qui ralentit le déploiement.

Étapes pour utiliser la pause avec ce manifeste

Appliquer le déploiement initial

Tout d'abord, appliquez le déploiement initial avec Nginx 1.16 :

yaml
kubectl apply -f deployment.yaml

Cela va lancer le déploiement de l'application avec 10 réplicas de nginx:1.16.

Déclencher un rollout

Mettons à jour l'image vers nginx:1.17 pour simuler une nouvelle version de l'application :

sh
kubectl set image deployment/my-app nginx=nginx:1.17

Cela démarrera un rollout progressif de nginx:1.17.

Surveiller l'état du déploiement

Vérifie l'état du déploiement pour voir s'il commence à déployer les nouveaux pods :

sh
kubectl rollout status deployment/my-app

Puisque nous avons configuré le déploiement pour se dérouler lentement (1 pod mis à jour à la fois), tu devrais voir des pods se déployer un par un.

Mettre le rollout en pause

Lorsque le déploiement est en cours, tu peux utiliser la commande suivante pour mettre le rollout en pause :

yaml
kubectl rollout pause deployment/my-app

Cela arrêtera temporairement le processus de mise à jour des pods.

Reprendre le rollout

Une fois que tu es prêt à reprendre la mise à jour, utilise la commande suivante pour reprendre le processus là où il s'était arrêté :

sh
kubectl rollout resume deployment/my-app

Cela permettra à Kubernetes de continuer à déployer les nouveaux pods de nginx:1.17 un par un.

Annuler le rollout si nécessaire

Si tu rencontres des problèmes avec la nouvelle version de l'application, tu peux annuler le rollout et revenir à la version précédente :

yaml
kubectl rollout undo deployment/my-app