Introduction
Les conteneurs d'initialisation Kubernetes s'exécutent avant les conteneurs d'application principaux dans un Pod. Ils sont idéaux pour les tâches de configuration telles que les migrations de base de données, l'attente de dépendances ou la récupération de configuration. Cependant, de nombreuses équipes traitent les conteneurs d'initialisation comme une réflexion après coup et négligent la planification de capacité. Cela conduit à des échecs de planification des Pods, à des conflits de ressources ou à des temps de démarrage lents. Ce guide explique comment estimer les ressources pour les conteneurs d'initialisation, définir des requêtes et des limites appropriées, et planifier la mise à l'échelle à l'aide d'exemples pratiques et de commandes. En suivant ces étapes, vous éviterez les pièges courants et assurerez une initialisation fiable des Pods en production.
Inventaire des versions et de l'environnement
Avant de modifier les ressources des conteneurs d'initialisation, confirmez la version de votre cluster et sa topologie. Les conteneurs d'initialisation sont stables depuis Kubernetes 1.6, mais les versions ultérieures offrent une meilleure gestion des ressources. Utilisez Kubernetes 1.18 ou plus récent pour ce guide.
Vérifiez la version de votre cluster :
kubectl version --short
Exemple de sortie :
Client Version: v1.24.0
Server Version: v1.24.0
Prérequis :
- Un cluster Kubernetes en cours d'exécution (mono-nœud ou multi-nœuds)
- kubectl configuré avec les droits d'administrateur de cluster ou d'administrateur d'espace de noms
- Compréhension de base des requêtes et limites de ressources des Pods
Pour ce guide, nous supposons un cluster à 3 nœuds avec 4 vCPU et 8 Go de RAM par nœud, exécutant Kubernetes v1.24. Votre topologie réelle variera, mais ces chiffres fournissent une référence concrète pour les exemples.
Comprendre la sémantique des ressources des conteneurs d'initialisation
Avant de plonger dans la configuration, il est crucial de comprendre comment Kubernetes gère les ressources pour les conteneurs d'initialisation. Ces connaissances évitent les erreurs de configuration et les surprises de planification.
Points clés :
- Les conteneurs d'initialisation s'exécutent séquentiellement, un à la fois, avant le démarrage de tout conteneur d'application.
- Chaque conteneur d'initialisation peut avoir ses propres requêtes et limites de ressources, indépendamment des autres conteneurs d'initialisation et des conteneurs d'application.
- Le planificateur calcule les besoins totaux en ressources d'un Pod comme la somme des requêtes des conteneurs d'application plus la requête maximale parmi tous les conteneurs d'initialisation. En effet, un seul conteneur d'initialisation s'exécute à la fois, mais le Pod doit réserver suffisamment de capacité pour le plus grand conteneur d'initialisation en plus des conteneurs d'application.
- Les limites sont appliquées par conteneur, et non par Pod. Un conteneur d'initialisation dépassant sa limite CPU est limité (throttling) ; dépassant sa limite mémoire, il est tué (OOMKilled).
- Si un conteneur d'initialisation échoue, le Pod le redémarre selon la restartPolicy (généralement Always), et toute l'initialisation du Pod reprend depuis le début.
Exemple : Supposons qu'un Pod a deux conteneurs d'application demandant chacun 200m CPU et 128Mo de mémoire, et deux conteneurs d'initialisation : init A demande 500m CPU et 256Mo de mémoire, init B demande 100m CPU et 64Mo de mémoire. La requête totale du Pod est la somme des requêtes des applications (400m CPU, 256Mo mémoire) plus la requête maximale d'initialisation (500m CPU, 256Mo mémoire) = 900m CPU et 512Mo mémoire. C'est ce que le planificateur utilise pour trouver un nœud avec suffisamment de ressources allouables.
Chemin de configuration sûr
Commencez avec un seul conteneur d'initialisation dans une charge de travail familière. Les requêtes et limites de ressources sont définies dans la spécification du Pod sous initContainers. Voici un manifeste de Pod complet avec un conteneur d'initialisation qui attend un service et un conteneur d'application exécutant nginx :
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'echo "Waiting for service"; sleep 10']
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
containers:
- name: myapp-container
image: nginx:1.14.2
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
Notez que le conteneur d'initialisation a des paramètres de ressources distincts du conteneur d'application. La requête totale du Pod est le maximum de toutes les requêtes de conteneurs, mais le planificateur considère la somme des requêtes des conteneurs d'application plus la plus grande requête des conteneurs d'initialisation. (Voir la section précédente pour plus de détails.)
Choix de portée :
- Utilisez
requestspour garantir une quantité minimale de ressources pour le conteneur. Le planificateur utilise les requêtes pour décider quel nœud peut héberger le Pod. - Utilisez
limitspour plafonner l'utilisation des ressources. Pour le CPU, dépasser la limite entraîne une limitation. Pour la mémoire, dépasser la limite peut provoquer l'arrêt du conteneur (OOMKilled). - Pour les conteneurs d'initialisation qui effectuent des tâches lourdes (par exemple, migrations de base de données), définissez des limites suffisamment élevées pour terminer rapidement, mais pas au point d'affamer les autres Pods. Commencez avec des valeurs modestes et ajustez en fonction de l'utilisation observée.
Approche pratique :
- Estimez l'utilisation maximale des ressources du conteneur d'initialisation en fonction de sa tâche. Par exemple, un outil de migration de base de données comme Flyway peut nécessiter 500m CPU et 512Mo de mémoire pour un changement de schéma important.
- Définissez les requêtes égales à votre estimation de base et les limites à 1,5 à 2 fois cette valeur pour permettre des pics.
- Déployez dans un environnement de test et mesurez l'utilisation réelle avec
kubectl topou des outils de surveillance. - Ajustez les requêtes et les limites en conséquence, en gardant toujours
requests <= limits.
Vérification et diagnostics
Après avoir appliqué la spécification du Pod, vérifiez que le conteneur d'initialisation se termine avec succès et que le Pod passe à l'état Running.
Appliquez le manifeste :
kubectl apply -f pod.yaml
pod/myapp-pod created
Surveillez le statut du Pod :
kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/1 0 5s
Après la fin du conteneur d'initialisation :
kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 25s
Consultez les journaux du conteneur d'initialisation :
kubectl logs myapp-pod -c init-myservice
Sortie attendue :
Waiting for service
Pour voir l'utilisation réelle des ressources, utilisez kubectl top (nécessite metrics-server) :
kubectl top pod myapp-pod --containers
Exemple de sortie :
POD NAME CPU(cores) MEMORY(bytes)
myapp-pod init-myservice 0m 0Mi
myapp-pod myapp-container 1m 3Mi
Si le conteneur d'initialisation échoue, par exemple avec OOMKilled, inspectez les événements :
kubectl describe pod myapp-pod
Regardez la section Events :
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 10s default-scheduler Successfully assigned default/myapp-pod to node1
Warning Failed 8s kubelet Error: failed to start container "init-myservice": Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: running with non-zero exit code: exit status 137
Le code de sortie 137 indique un arrêt OOM (128 + 9, SIGKILL). Augmentez les limites de mémoire en conséquence. Vérifiez également si le conteneur d'initialisation a dépassé sa limite CPU ; il sera limité mais pas tué.
Modes de défaillance et récupération
Les modes de défaillance courants incluent :
- OOMKilled : Le conteneur d'initialisation dépasse la limite de mémoire. Le conteneur est tué. Solution : augmenter la limite de mémoire ou réduire l'empreinte mémoire de la charge de travail du conteneur d'initialisation.
- Limitation CPU : Le conteneur d'initialisation atteint la limite CPU ; il s'exécute lentement mais n'échoue pas. Cela peut entraîner des retards de démarrage ou des délais d'attente. Solution : augmenter la limite CPU ou accepter un démarrage plus lent.
- Échec de planification : Le Pod ne peut pas être planifié car aucun nœud ne satisfait les requêtes de ressources du conteneur d'initialisation. Solution : réduire les requêtes ou ajouter des nœuds.
- Le conteneur d'initialisation ne se termine jamais : Il peut attendre une dépendance qui n'apparaît jamais. Solution : ajouter un délai d'attente à la commande ou utiliser une sonde de disponibilité.
Étapes de récupération détaillées :
- OOMKilled
- Augmentez
limits.memorydans la spécification du conteneur d'initialisation. - Exemple : si le conteneur d'initialisation a besoin de 200Mo mais que la limite est de 128Mo, passez la limite à 256Mo.
- Réappliquez le Pod.
- Limitation CPU
- Vérifiez l'utilisation du CPU avec
kubectl top pod --containerspendant que le conteneur d'initialisation s'exécute. - Si l'utilisation atteint constamment la limite CPU, augmentez
limits.cpu. - Remarque : les requêtes peuvent rester inchangées si la planification n'est pas un problème.
- Échec de planification
- Exécutez
kubectl describe pod <nom-du-pod>et recherchez des événements comme « 0/3 nodes are available: 3 Insufficient cpu. » - Réduisez les
requestsdu conteneur d'initialisation ou mettez à l'échelle les nœuds du cluster. - Alternativement, déplacez le Pod vers un espace de noms avec des quotas de ressources différents, le cas échéant.
- Le conteneur d'initialisation ne se termine jamais
- Consultez les journaux pour voir où il est bloqué :
kubectl logs <nom-du-pod> -c <nom-du-conteneur-init> - Ajoutez un délai d'attente dans la commande, par exemple,
timeout 60s ./wait-for-db.sh - Utilisez des commandes de conteneur d'initialisation qui se terminent en cas d'échec plutôt que de rester indéfiniment en attente.
Retour en arrière : Si les changements causent des problèmes, revenez aux valeurs de ressources précédentes en utilisant kubectl apply -f previous-pod.yaml ou kubectl edit pod myapp-pod et ajustez les ressources. Pour récupérer rapidement un Pod bloqué, supprimez-le :
kubectl delete pod myapp-pod
pod "myapp-pod" deleted
Puis réappliquez avec des ressources corrigées. Pour les problèmes persistants, vérifiez les journaux et les événements avant de redéployer.
Planification avancée de la capacité pour les conteneurs d'initialisation
Estimer les besoins en ressources à partir des caractéristiques de la charge de travail
La manière la plus précise de planifier la capacité est de mesurer la consommation réelle de ressources du conteneur d'initialisation dans des conditions réalistes. Voici une approche systématique :
- Identifiez le type de tâche du conteneur d'initialisation :
- Migration de base de données : l'utilisation du CPU et de la mémoire dépend de la taille du schéma et des transformations de données.
- Attente de dépendance : ressources minimales ; une simple boucle
sleepoucurl. - Récupération de configuration : l'utilisation des ressources dépend des E/S réseau et de la décompression.
- Génération de fichiers : le CPU et la mémoire évoluent avec la taille de sortie et le traitement.
- Exécutez le conteneur d'initialisation manuellement en dehors de Kubernetes ou dans un Pod de test avec des limites généreuses. Mesurez l'utilisation maximale à l'aide de
kubectl topoudocker stats.
- Appliquez un facteur de sécurité : définissez les requêtes au 90e centile de l'utilisation observée, les limites à 1,5 à 2 fois cette valeur.
Exemple : Conteneur d'initialisation de migration de base de données
Supposons que vous ayez besoin d'un conteneur d'initialisation exécutant Flyway pour appliquer des migrations de base de données avant le démarrage de l'application principale. Vous exécutez une migration de test sur une copie de la base de données et observez :
- CPU maximal : 350m
- Mémoire maximale : 400Mo
Vous définissez alors :
initContainers:
- name: db-migrate
image: flyway/flyway:9.22.3
command: ["flyway", "migrate"]
resources:
requests:
cpu: "400m"
memory: "450Mi"
limits:
cpu: "800m"
memory: "800Mi"
Cela donne une marge pour des migrations plus importantes sans surallouer inutilement les ressources du cluster.
Utiliser Vertical Pod Autoscaler (VPA) pour les conteneurs d'initialisation
VPA peut ajuster automatiquement les requêtes de ressources en fonction de l'historique d'utilisation. Cependant, le support de VPA pour les conteneurs d'initialisation est limité ; il cible principalement les conteneurs réguliers. À partir de Kubernetes 1.24, VPA ne gère pas directement les ressources des conteneurs d'initialisation. Vous pouvez utiliser VPA en mode recommandation pour obtenir des suggestions pour les conteneurs d'application et appliquer une logique similaire aux conteneurs d'initialisation manuellement.
Pour obtenir des recommandations VPA, installez VPA et créez un objet VPA pour le Pod. Les recommandations incluront les requêtes au niveau des conteneurs. Pour les conteneurs d'initialisation, vous devrez peut-être créer un VPA séparé ou ajuster manuellement en fonction de l'utilisation observée.
Planification de capacité à grande échelle
Lorsque vous exécutez de nombreux Pods avec des conteneurs d'initialisation, la consommation agrégée des ressources doit être prise en compte.
- Si chaque Pod démarre avec un conteneur d'initialisation qui utilise des ressources importantes, la demande de pointe se produit pendant les déploiements ou les événements de mise à l'échelle.
- Planifiez la capacité des nœuds pour gérer le nombre maximal de conteneurs d'initialisation simultanés plus les conteneurs d'application.
- Utilisez
kubectl describe nodespour voir les ressources allouables et les requêtes actuelles.
Exemple : Supposons que vous avez 10 répliques d'un déploiement, chaque Pod a un conteneur d'initialisation avec une requête de 500m CPU et 256Mo de mémoire, et un conteneur d'application avec une requête de 200m CPU et 128Mo de mémoire. La requête CPU totale par Pod est de 500m + 200m = 700m (puisqu'un seul conteneur d'initialisation à la fois, requête max d'init 500m, plus application 200m). Pour 10 Pods, la requête CPU totale est de 7 cœurs. Avec trois nœuds de 4 vCPU chacun (12 cœurs au total), vous pouvez planifier tous les Pods s'ils tiennent nœud par nœud. Mais si chaque Pod est planifié sur un nœud différent, chaque nœud doit accueillir au moins la requête d'un Pod. C'est gérable. Cependant, si vous avez 100 Pods, vous avez besoin de 70 cœurs, ce qui peut dépasser la capacité du cluster. Planifiez en conséquence.
Quotas de ressources et LimitRanges
Dans les clusters multi-locataires, les ResourceQuotas et les LimitRanges peuvent affecter la planification de capacité des conteneurs d'initialisation.
- Les ResourceQuotas peuvent limiter le total des requêtes/limites dans un espace de noms, y compris les conteneurs d'initialisation.
- Les LimitRanges peuvent définir des requêtes/limites par défaut si elles ne sont pas spécifiées, et appliquer des valeurs minimales/maximales.
Exemple de LimitRange :
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi
cpu: 500m
defaultRequest:
memory: 256Mi
cpu: 200m
max:
memory: 1Gi
cpu: 1
min:
memory: 64Mi
cpu: 50m
type: Container
Cela s'applique également aux conteneurs d'initialisation, sauf si type: InitContainer est spécifié séparément. Soyez conscient de ces contraintes lors de la définition des ressources des conteneurs d'initialisation.
Liste de contrôle opérationnelle
Utilisez cette liste de contrôle avant et après le déploiement de conteneurs d'initialisation pour assurer un fonctionnement sans heurts :
| Étape | Action | Résultat attendu |
|---|---|---|
| 1 | Vérifiez la version du cluster avec kubectl version --short | La version du serveur est 1.18 ou plus récente |
| 2 | Identifiez le but du conteneur d'initialisation | Définition claire de la tâche (par exemple, migration, attente de service) |
| 3 | Estimez les besoins en ressources en fonction de la complexité de la tâche et des données historiques | Estimations documentées pour le CPU et la mémoire |
| 4 | Définissez les requêtes et les limites | Requêtes <= limites ; valeurs basées sur des estimations avec facteur de sécurité |
| 5 | Appliquez la spécification du Pod avec kubectl apply -f pod.yaml | Pod créé avec succès |
| 6 | Surveillez le statut du Pod avec kubectl get pods -w | Le conteneur d'initialisation se termine ; le Pod passe à Running |
| 7 | Vérifiez les journaux du conteneur d'initialisation avec kubectl logs <pod> -c <init-container> | Sortie attendue, aucune erreur |
| 8 | Surveillez l'utilisation des ressources avec kubectl top pod --containers | Utilisation dans les limites, pas de limitation ni d'OOM |
| 9 | Testez sous charge en mettant à l'échelle les répliques | Tous les Pods sont planifiés et s'initialisent sans délai |
| 10 | Documentez les valeurs finales des ressources et mettez à jour les runbooks | Valeurs enregistrées pour référence future |
Passez régulièrement en revue les ressources des conteneurs d'initialisation, surtout après des changements de code ou de données. Les signaux de mise à l'échelle incluent une durée accrue du conteneur d'initialisation, des arrêts OOM ou des retards de planification.
Exemple concret : scénario de bout en bout
Parcourons un exemple complet de planification de capacité pour un conteneur d'initialisation qui attend qu'une base de données soit prête avant de démarrer une application web.
Cluster : 3 nœuds, chacun avec 4 vCPU et 8 Go de mémoire.
Application : Une application web avec deux conteneurs : serveur web (nginx) et serveur d'application (personnalisé). Le conteneur d'initialisation exécute un script qui vérifie la connectivité à la base de données en utilisant nc -z dans une boucle.
Spécification initiale du Pod sans limites de ressources :
apiVersion: v1
kind: Pod
metadata:
name: webapp-pod
spec:
initContainers:
- name: wait-for-db
image: busybox:1.28
command: ['sh', '-c', 'until nc -z db-service 5432; do echo waiting for db; sleep 2; done;']
containers:
- name: web
image: nginx:1.14.2
ports:
- containerPort: 80
- name: app
image: myapp:1.0
ports:
- containerPort: 8080
Ce Pod n'a pas de ressources explicites, il utilise donc les valeurs par défaut s'il existe un LimitRange, ou aucune requête/limite. Dans un cluster de production, cela pourrait conduire à une surallocation et à de mauvaises performances.
Étape 1 : Mesurer l'utilisation réelle
Exécutez le conteneur d'initialisation dans un Pod de test avec des limites généreuses :
initContainers:
- name: wait-for-db
image: busybox:1.28
command: ['sh', '-c', 'until nc -z db-service 5432; do echo waiting for db; sleep 2; done;']
resources:
requests:
cpu: "10m"
memory: "16Mi"
limits:
cpu: "100m"
memory: "64Mi"
Observez avec kubectl top pendant que le conteneur d'initialisation s'exécute. Il montre une utilisation très faible : CPU 1m, mémoire 2Mo. Cela a du sens car il s'agit simplement d'une boucle shell avec des vérifications réseau.
Étape 2 : Définir les ressources finales
Sur la base de la mesure, définissez :
resources:
requests:
cpu: "10m"
memory: "16Mi"
limits:
cpu: "50m"
memory: "32Mi"
Définissez également des ressources pour les conteneurs d'application :
containers:
- name: web
image: nginx:1.14.2
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
- name: app
image: myapp:1.0
resources:
requests:
cpu: "200m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
Requêtes totales du Pod : somme des conteneurs d'application = 100m+200m=300m CPU, 64Mo+128Mo=192Mo mémoire. La requête maximale d'initialisation est de 10m CPU, 16Mo mémoire. Total = 310m CPU et 208Mo mémoire. C'est bien dans la capacité du nœud.
Étape 3 : Test de mise à l'échelle
Créez un déploiement avec 10 répliques. La requête CPU totale pour tous les Pods est de 3100m = 3,1 cœurs. Le cluster a 12 cœurs au total, donc cela convient. Cependant, la planification dépend de la capacité par nœud. Chaque nœud a 4 cœurs ; un Pod nécessite 310m, donc un nœud peut héberger plusieurs Pods. Avec la planification par défaut, tous les Pods devraient se planifier sans problème.
Étape 4 : Surveiller et ajuster
Après le déploiement, vérifiez que les conteneurs d'initialisation se terminent rapidement et qu'il n'y a pas d'événements OOM. Ajustez si nécessaire.
Cet exemple démontre un conteneur d'initialisation léger. Pour des tâches plus lourdes, suivez l'exemple de migration de base de données dans la section avancée.
Conclusion
La planification de capacité pour les conteneurs d'initialisation Kubernetes est essentielle pour un démarrage fiable des Pods. En suivant les étapes de ce guide --- commencer par une base de référence, définir des requêtes et limites appropriées, vérifier avec les journaux et les métriques, et se préparer aux modes de défaillance courants --- vous pouvez éviter les pannes liées aux ressources. Commencez par un petit pilote mesurable comme suggéré, puis itérez en fonction de l'utilisation réelle. Utilisez la liste de contrôle opérationnelle pour maintenir la cohérence entre les déploiements. Avec une planification minutieuse, les conteneurs d'initialisation s'exécuteront efficacement sans gaspiller les ressources du cluster ni provoquer d'échecs de planification. N'oubliez pas de revoir régulièrement les ressources et de les ajuster à mesure que votre application évolue.