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. -> ServicesLe 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
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.
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
replicasdu 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.
- 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
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
Interaction entre les services et les pods :
- L'utilisateur envoie une requête HTTP à l'application
Guestbookvia son service. - Le service
GuestbookSvcachemine cette requête vers le podGuestbook. - Le pod
Guestbookcommunique 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.
- L'utilisateur envoie une requête HTTP à l'application
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 nodespour programmer de nouveaux pods Redis, ce qui permet de répartir la charge. - Ensuite, les requêtes suivantes faites par l'application
Guestbooksont 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.
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: 3000Voici un déploiement adapté à redis
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
templatereprend le manifeste du pod.
-> Commencez par supprimer les précédents pods
kubectl delete pods redis
kubectl delete pods guestbook-> Déployez redis
kubectl apply -f redis-deployment.yaml
# deployment.apps/redis created-> Vérifiez le bon fonctionnement de la commande
kubectl get deployments.apps
# NAME READY UP-TO-DATE AVAILABLE AGE
# redis 1/1 1 1 25s-> Inspectez le déploiement
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
kubectl apply -f guestbook-deployment.yaml
# deployment.apps/guestbook created-> Constatez l'état du déploiement
kubectl get deployments.apps
# NAME READY UP-TO-DATE AVAILABLE AGE
# guestbook 3/3 3 3 4m52s
# redis 1/1 1 1 10mLes 3 pods sont démarrés, et disponibles
-> Observez l'état du service guestbook précédemment déployé
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
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 22mSupprimez le premier de la liste
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 3sMise en pratique du self healing
Que se passe-t-il en cas de crash d'un noeud ?
- Le controller manager observe en permanence l'état des noeuds pour s'assurer que
l'état observématche avecl'état désiré. - Si le
worker node 2tombe 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
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
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
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
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
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
kubectl edit deployment.apps guestbookLe manifeste de déploiement est alors directement édité sur le cluster
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: 8Cette 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.
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: 1Explication 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 :
kubectl apply -f deployment.yamlCela 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 :
kubectl set image deployment/my-app nginx=nginx:1.17Cela 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 :
kubectl rollout status deployment/my-appPuisque 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 :
kubectl rollout pause deployment/my-appCela 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é :
kubectl rollout resume deployment/my-appCela 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 :
kubectl rollout undo deployment/my-app