## Introduction La planification de capacité dans Kubernetes consiste à prévoir la quantité de CPU, de mémoire et d'autres ressources dont vos pods auront besoin, afin que le cluster puisse les planifier sans gaspillage ni panne. La configuration avancée des pods est là où cela devient concret : vous ne vous contentez pas de définir une limite de mémoire et d'espérer le meilleur. Vous observez l'utilisation réelle, ajustez les requêtes et les limites, gérez les conteneurs d'initialisation, tenez compte de la surcharge du contexte de sécurité et planifiez les échecs. Cet article s'adresse aux développeurs, aux ingénieurs DevOps et aux équipes techniques de startups déjà familiarisés avec les concepts de base de Kubernetes comme les pods et les déploiements, et qui ont maintenant besoin de prendre des décisions éclairées sur le dimensionnement des charges de travail. Nous couvrirons l'inventaire des versions et de l'environnement, un chemin de configuration sûr, la vérification et les diagnostics, les modes de défaillance et la récupération, ainsi qu'une liste de contrôle opérationnelle concise. L'objectif est la sécurité opérationnelle : observer avant de changer, limiter le rayon d'impact, utiliser des espaces réservés au lieu de secrets, vérifier le résultat et documenter comment récupérer si l'état attendu n'est pas atteint. ## Inventaire des versions et de l'environnement Avant de toucher à la configuration d'un pod pour la planification de capacité, sachez ce que vous exécutez. Cela signifie capturer la version de Kubernetes, le runtime de conteneur, la topologie du cluster et l'utilisation actuelle des ressources. ### Vérifier la version de Kubernetes et la disponibilité des API Exécutez : ```bash kubectl version --short kubectl get nodes -o wide kubectl get pods -A -o wide ``` Si vous utilisez un service Kubernetes managé tel que EKS, GKE ou AKS, la version est liée au plan de contrôle ; vous devrez peut-être utiliser leur CLI ou leur console pour vérifier les canaux de mise à niveau. Par exemple, sur EKS : ```bash eksctl get cluster --name my-cluster ``` Assurez-vous que les ressources d'API dont vous avez besoin sont disponibles. Pour les requêtes et limites de ressources, elles existent depuis les premières versions de Kubernetes (v1.0), mais pour des champs comme `startupProbe` (v1.16+) ou les requêtes `ephemeral-storage` (v1.8+ en alpha, stable en v1.25), vous devez disposer d'au moins cette version. Vérifiez rapidement avec : ```bash kubectl api-versions | grep -E 'apps/v1|v1$' ``` ### Capturer l'utilisation actuelle des ressources des pods Ne devinez pas ce dont vos pods ont besoin. Utilisez le serveur de métriques Kubernetes. S'il n'est pas installé, installez-le (dans la plupart des clusters managés, c'est un add-on en un clic). Une fois que metrics-server est en cours d'exécution, obtenez l'utilisation en direct : ```bash kubectl top pods -n votre-namespace kubectl top nodes ``` Exemple de sortie : ``` NAME CPU(cores) MEMORY(bytes) web-app-6d4f7c9b8-abcde 150m 320Mi worker-9f7c8d6b5-xyz12 20m 180Mi ``` Ces chiffres sont instantanés et ne suffisent pas pour la planification de capacité. Vous avez besoin d'historique. Installez Prometheus et Grafana, ou utilisez une offre managée comme Datadog, New Relic ou GKE Cloud Monitoring. Avec Prometheus, vous pouvez interroger l'utilisation typique : ```promql avg_over_time(container_memory_working_set_bytes{namespace="votre-namespace", container!="POD"}[7d]) ``` Cela donne la mémoire de l'ensemble de travail moyenne sur les 7 derniers jours, ce qui est une base de référence plus réaliste que le pic actuel. ### Inspecter les spécifications existantes des pods Regardez ce qui est actuellement configuré : ```bash kubectl get pod -n -o yaml ``` Recherchez les champs `resources` existants. S'ils sont absents, c'est un signal d'alarme : sans requêtes, le planificateur ne peut pas prendre de bonnes décisions ; sans limites, un pod incontrôlable peut affamer ses voisins. Enregistrez toute la configuration pertinente dans un inventaire versionné, même s'il s'agit d'une simple feuille de calcul ou d'un dépôt Git avec les manifestes actuels. ### Documenter les prérequis et les dépendances Listez toutes les configurations au niveau du cluster qui affectent la planification de capacité : - Capacité des nœuds et teintes/tolérances : `kubectl describe nodes` - Quotas de ressources et plages de limites du namespace : `kubectl get resourcequota,limitrange -n ` - Niveaux de Pod Security Admission (PSA) (si vous utilisez v1.23+) : `kubectl get ns -o yaml | grep pod-security` - Politiques réseau qui pourraient affecter la collecte de métriques - Tous les webhooks d'admission personnalisés qui modifient les spécifications des pods Par exemple, une LimitRange peut imposer des valeurs par défaut ou des valeurs max/min : ```yaml apiVersion: v1 kind: LimitRange metadata: name: default-limits spec: limits: - default: cpu: "1" memory: "1Gi" defaultRequest: cpu: "0.5" memory: "512Mi" max: cpu: "2" memory: "2Gi" min: cpu: "50m" memory: "100Mi" type: Container ``` Si une telle LimitRange existe, les pods sans requêtes/limites explicites reçoivent les valeurs par défaut, et les pods dépassant le maximum sont rejetés. Sachez cela avant de modifier la spécification de votre pod. ## Chemin de configuration sûr Maintenant que vous avez l'inventaire de l'environnement, vous pouvez concevoir un plan de capacité sûr. Le principe est : commencer petit, observer, ajuster et toujours avoir un chemin de retour en arrière. ### Commencer par les requêtes et les limites de ressources Les requêtes de ressources sont ce que le planificateur utilise pour placer les pods. Les limites sont le plafond strict. Les définir correctement évite à la fois la surallocation (trop de pods sur un nœud provoquant un throttling du CPU ou des OOM kills) et la sous-utilisation (gaspillage d'argent). Utilisez l'utilisation observée de l'inventaire des versions et de l'environnement. Un point de départ courant : - **Requête CPU** : définissez le 75e percentile de l'utilisation observée sur une semaine. Pour de nombreux services web, cela peut être de 100m à 500m. - **Requête mémoire** : définissez la mémoire de l'ensemble de travail moyenne plus un petit tampon (par exemple, 10-20 %). Utilisez `container_memory_working_set_bytes`, pas `container_memory_usage_bytes`, car l'ensemble de travail exclut le cache de pages. - **Limite CPU** : définissez une valeur supérieure à la requête, peut-être 2 à 3 fois la requête, mais soyez conscient du throttling du CPU. Si votre application peut monter en charge, laissez une marge ; si elle est gourmande en CPU et sensible à la latence, vous pouvez définir la limite égale à la requête pour éviter le throttling. - **Limite mémoire** : définissez une valeur supérieure au pic d'utilisation attendu, mais pas trop élevée pour qu'une fuite passe inaperçue. Une règle empirique est limite = requête * 1,5 ou 2, plus une marge pour les pics. Exemple de manifeste pour une application web typique : ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: web-app spec: replicas: 3 selector: matchLabels: app: web-app template: metadata: labels: app: web-app spec: containers: - name: app image: myregistry/web-app:1.4.2 ports: - containerPort: 8080 resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi" readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 15 periodSeconds: 20 ``` Notez les sondes : elles sont essentielles pour la planification de capacité car un pod qui n'est pas prêt ne doit pas recevoir de trafic, et un pod bloqué mais pas mort peut provoquer de fausses hypothèses de capacité. ### Considérer les conteneurs d'initialisation et les sidecars Les conteneurs d'initialisation s'exécutent avant les conteneurs d'application et peuvent consommer des ressources de manière agressive pendant le démarrage. Ils peuvent également avoir leurs propres requêtes et limites. Si votre conteneur d'initialisation effectue des migrations de base de données ou de gros téléchargements de données, il peut avoir besoin de plus de mémoire que l'application principale au départ. Définissez-les explicitement : ```yaml initContainers: - name: init-db image: busybox:1.36 command: ['sh', '-c', 'echo "Initializing DB"; sleep 10'] resources: requests: cpu: "50m" memory: "64Mi" limits: cpu: "100m" memory: "128Mi" ``` Les conteneurs sidecar (par exemple, les proxys de maillage de services comme Envoy, les agents de journalisation) font partie du pod et doivent être inclus dans le calcul total des ressources. Pour un pod avec un sidecar Istio, les requêtes typiques du sidecar sont : ```yaml resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi ``` Ajoutez-les aux requêtes de l'application principale lors du calcul de la capacité des nœuds. ### Utiliser PriorityClass et Pod Disruption Budgets La planification de capacité ne concerne pas seulement les ressources ; elle concerne la disponibilité. Définissez une PriorityClass pour les pods critiques afin que, lors d'une pression sur les nœuds, les pods de priorité inférieure soient évincés en premier : ```yaml apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 1000 globalDefault: false description: "Pour les services de production critiques." ``` Puis dans la spécification de votre pod : ```yaml priorityClassName: high-priority ``` Les Pod Disruption Budgets (PDB) garantissent que les perturbations volontaires (par exemple, les drainages de nœuds) ne mettent pas hors service trop de réplicas : ```yaml apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: web-app-pdb spec: minAvailable: 2 selector: matchLabels: app: web-app ``` Avec 3 réplicas, cela permet à un pod d'être indisponible à la fois, donc pendant un drainage de nœud, le cluster respecte vos besoins de disponibilité. ### Contexte de sécurité et son effet sur la capacité Les paramètres du contexte de sécurité peuvent avoir un impact sur la capacité. Par exemple, exécuter un conteneur en tant qu'utilisateur non root avec un système de fichiers racine en lecture seule ne consomme pas directement du CPU ou de la mémoire, mais si vous avez également `allowPrivilegeEscalation: false` et que vous supprimez des capacités, certaines applications peuvent se comporter différemment (plus de CPU en raison de l'absence d'accélération). Plus important encore, si vous utilisez des volumes hostPath ou des plugins de périphériques (comme les GPU), la planification de capacité doit tenir compte de ces ressources spéciales. Exemple de contexte de sécurité qui pourrait affecter l'allocation de GPU : ```yaml securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 seccompProfile: type: RuntimeDefault resources: limits: nvidia.com/gpu: 1 ``` Si vous demandez un GPU, le planificateur doit trouver un nœud avec cette ressource. C'est une configuration avancée des pods car vous ne planifiez pas seulement le CPU et la mémoire. ### Appliquer les changements de manière incrémentale Ne changez pas tous les pods à la fois. Utilisez des déploiements canaris ou modifiez simplement un réplica à la fois si possible. Pour un déploiement, vous pouvez utiliser la stratégie de mise à jour progressive avec `maxUnavailable: 0` et `maxSurge: 1` pour ajouter un nouveau pod avant de terminer les anciens, minimisant ainsi l'impact. Avant d'appliquer en production, testez dans un environnement de staging avec des contraintes de ressources similaires. Utilisez `kubectl apply --dry-run=client` pour valider la syntaxe, et un dry-run côté serveur pour vérifier l'admission : ```bash kubectl apply -f new-deployment.yaml --dry-run=server ``` Puis appliquez et surveillez : ```bash kubectl apply -f new-deployment.yaml kubectl rollout status deployment/web-app -n votre-namespace ``` Si quelque chose ne va pas, annulez : ```bash kubectl rollout undo deployment/web-app -n votre-namespace ``` Conservez le manifeste précédent dans le contrôle de version. ## Vérification et diagnostics Après avoir appliqué de nouveaux paramètres de ressources, vérifiez que les pods sont planifiés, en cours d'exécution et fonctionnent comme prévu. Utilisez une approche systématique. ### Vérifier la planification et l'état des pods ```bash kubectl get pods -n votre-namespace -o wide ``` Recherchez les pods `Pending`, qui indiquent des ressources insuffisantes ou d'autres contraintes de planification. Décrivez le pod pour plus de détails : ```bash kubectl describe pod -n votre-namespace ``` Dans les événements, vous pourriez voir des messages comme : ``` 0/3 nodes are available: 3 Insufficient memory. ``` Cela vous indique que votre requête est trop élevée pour les nœuds actuels. ### Confirmer l'utilisation réelle des ressources par rapport aux requêtes Après que le pod a fonctionné pendant un certain temps, comparez l'utilisation réelle aux requêtes : ```bash kubectl top pod -n votre-namespace ``` Si l'utilisation réelle est constamment inférieure aux requêtes, vous surprovisionnez peut-être et gaspillez de la capacité. Si elle est proche des limites, vous risquez un throttling ou un OOM. ### Utiliser des tableaux de bord de surveillance Configurez des tableaux de bord Grafana pour visualiser l'utilisation des ressources au fil du temps. Métriques clés : - Utilisation CPU vs requête vs limite - Utilisation mémoire (ensemble de travail) vs requête vs limite - Throttling CPU (`container_cpu_cfs_throttled_seconds_total`) - Événements OOM kill (`kube_pod_container_status_last_terminated_reason="OOMKilled"`) - Utilisation de la capacité des nœuds Exemple d'alerte Prometheus pour la mémoire proche de la limite : ```yaml - alert: PodMemoryNearLimit expr: (sum(container_memory_working_set_bytes{container!="POD"}) by (pod, namespace) / sum(kube_pod_container_resource_limits{resource="memory"}) by (pod, namespace)) > 0.85 for: 10m labels: severity: warning annotations: summary: "Le pod {{ $labels.pod }} utilise plus de 85 % de sa limite de mémoire" ``` ### Valider la vivacité et la préparation Un pod qui s'exécute mais n'est pas prêt est inutile pour la capacité. Vérifiez la préparation : ```bash kubectl get pods -n votre-namespace -o jsonpath='{range .items[*]}{.metadata.name}{" ready="}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}' ``` Si un pod n'est pas prêt, déboguez la sonde de préparation. Pour les sondes HTTP, utilisez `kubectl port-forward` pour tester le point de terminaison manuellement : ```bash kubectl port-forward pod/ 8080:8080 -n votre-namespace curl localhost:8080/healthz ``` ### Tester les scénarios de défaillance Testez de manière proactive ce qui se passe lorsqu'un pod est tué ou qu'un nœud meurt. Utilisez `kubectl delete pod ` pour simuler un crash et regardez le déploiement le recréer. Assurez-vous que le nouveau pod est planifié et devient prêt. Simulez une pression sur le nœud en le cordonnant et en le drainant : ```bash kubectl cordon kubectl drain --ignore-daemonsets --delete-emptydir-data ``` Observez que les pods sont replanifiés selon leurs priorités et PDB. ## Modes de défaillance et récupération Même avec une planification minutieuse, des pannes surviennent. Connaissez les modes de défaillance courants liés à la capacité et comment récupérer. ### OOM Kills (assassinats par dépassement de mémoire) Symptôme : redémarrage du pod avec la raison `OOMKilled`. Vérifiez : ```bash kubectl get pod -n votre-namespace -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}' ``` Cela signifie souvent que la limite de mémoire est trop basse pour le pic d'utilisation de la charge de travail. Récupération : 1. Augmentez temporairement la limite de mémoire pour arrêter l'hémorragie (si l'application peut le supporter). 2. Analysez les modèles d'utilisation de la mémoire pour trouver la cause (par exemple, fuite de mémoire, gros processus par lots). 3. Corrigez l'application s'il s'agit d'une fuite, ou ajustez la requête/limite pour correspondre au pic réel, en laissant une marge. 4. Envisagez d'utiliser `startupProbe` si l'application a besoin de plus de temps et de mémoire pendant l'initialisation. Exemple de patch pour augmenter la limite de mémoire : ```bash kubectl patch deployment web-app -n votre-namespace -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","resources":{"limits":{"memory":"1Gi"}}}]}}}}' ``` ### Throttling du CPU Symptôme : métriques de throttling CPU élevées, pics de latence de l'application. Vérifiez : ```bash kubectl top pod -n votre-namespace ``` Si l'utilisation du CPU est à la limite, le conteneur est throttlé. Récupération : - Augmentez la limite CPU, ou définissez-la égale à la requête pour éviter le throttling si votre charge de travail est sensible à la latence. - Optimisez l'application pour utiliser moins de CPU. - Ajoutez plus de réplicas pour répartir la charge. ### Pods en attente en raison de ressources insuffisantes Si les pods restent `Pending`, décrivez-les pour voir pourquoi. Raisons courantes : CPU/mémoire insuffisants, aucun nœud correspondant pour les sélecteurs de nœuds/affinités/teintes, ou quotas de ressources dépassés. Récupération : - Augmentez le nombre de nœuds (augmentez le nombre de nœuds ou la taille des nœuds). - Réduisez les requêtes de ressources du pod. - Supprimez ou ajustez nodeSelector/affinité pour permettre la planification sur d'autres nœuds. - Vérifiez les quotas de ressources du namespace et augmentez le quota ou supprimez les ressources inutilisées. Exemple de vérification de quota : ```bash kubectl get resourcequota -n votre-namespace kubectl describe resourcequota -n votre-namespace ``` ### Évictions sous pression du nœud Lorsqu'un nœud manque de mémoire ou de disque, le kubelet évince les pods. Les pods sans requêtes ou à faible priorité sont évincés en premier. Récupération : - Identifiez les pods évincés : `kubectl get pods -n votre-namespace --field-selector=status.phase=Failed` et recherchez la raison `Evicted`. - Supprimez les pods évincés pour nettoyer : `kubectl delete pod -n votre-namespace` - Réduisez la pression mémoire du nœud en ajoutant des nœuds ou en réduisant les charges de travail. - Assurez-vous que les pods critiques ont une priorité plus élevée et des requêtes adéquates. ### Défaillance d'un nœud Si un nœud tombe en panne de manière inattendue, les pods qui s'y trouvent deviennent `Terminating` et sont replanifiés si le nœud ne récupère pas. Cependant, si vous avez des charges de travail avec état avec stockage local, des données peuvent être perdues. La récupération dépend de votre stockage : si vous utilisez des volumes persistants avec stockage distant (par exemple, EBS, GCE PD), le pod peut être replanifié et rattacher le volume. Si vous utilisez `emptyDir`, les données sont perdues. Testez la récupération en simulant une défaillance de nœud dans un environnement de test : arrêtez un nœud ou utilisez `kubectl delete node ` (dans un cluster de test) et observez comment les pods sont redistribués. ### Requêtes/limites mal configurées menant à une surallocation Définir des requêtes trop basses peut conduire à une surallocation des nœuds. Lorsqu'un nœud est suralloué et que tous les pods essaient d'utiliser leurs requêtes simultanément, le nœud peut manquer de ressources réelles, provoquant des évictions ou des OOM kills. Récupération : - Augmentez les requêtes pour refléter plus précisément l'utilisation réelle. - Utilisez LimitRange pour définir des requêtes minimales dans le namespace. - Activez le plugin `NodeResourcesFit` du planificateur Kubernetes avec une notation appropriée pour réduire la surallocation. ## Liste de contrôle opérationnelle Utilisez cette liste de contrôle avant et après avoir apporté des modifications de configuration de capacité. Remplacez les valeurs d'exemple par les vôtres. | Étape | Commande ou action | Résultat attendu | Responsable | |------|-------------------|-----------------|-------| | 1. Capturer la base de référence | `kubectl top pods -n prod` et noter l'utilisation maximale sur 7 jours | Un tableau de l'utilisation actuelle CPU/mémoire | Priya Shah, SRE | | 2. Vérifier les spécifications actuelles des pods | `kubectl get pods -n prod -o yaml` et examiner `resources` | Requêtes/limites actuelles documentées | Priya Shah | | 3. Vérifier le serveur de métriques | `kubectl get deployment metrics-server -n kube-system` | Le serveur de métriques est en cours d'exécution | Marcus Chen | | 4. Définir de nouvelles requêtes/limites de ressources dans le manifeste | Modifier le YAML de déploiement avec de nouvelles valeurs, par exemple, requête CPU 200m, mémoire 256Mi ; limite CPU 500m, mémoire 512Mi | Manifeste mis à jour et versionné | Marcus Chen | | 5. Dry-run apply | `kubectl apply -f new-deployment.yaml --dry-run=server` | Aucune erreur, validation réussie | Priya Shah | | 6. Appliquer le changement | `kubectl apply -f new-deployment.yaml` | Déploiement mis à jour, le déploiement commence | Priya Shah | | 7. Surveiller le déploiement | `kubectl rollout status deployment/web-app -n prod` | « deployment successfully rolled out » | Marcus Chen | | 8. Vérifier l'utilisation des ressources du pod après 30 min | `kubectl top pods -n prod --sort-by=memory` | Nouveaux pods avec utilisation dans les limites | Priya Shah | | 9. Examiner les métriques de throttling/OOM | Prometheus : `rate(container_cpu_cfs_throttled_seconds_total[5m])` et événements OOM kill | Aucun throttling ou OOM kill significatif | Marcus Chen | | 10. Tester la résilience | `kubectl delete pod web-app- -n prod` et observer la recréation | Le nouveau pod est planifié et devient prêt | Priya Shah | | 11. Documenter la récupération | Mettre à jour le runbook avec les commandes d'annulation | Le runbook contient les étapes `kubectl rollout undo` | Marcus Chen | ## Conclusion La configuration avancée des pods pour la planification de capacité dans Kubernetes n'est pas une tâche ponctuelle mais une pratique continue. En suivant les étapes décrites dans cet article - de l'inventaire de l'environnement et de la configuration sûre à la vérification, à la gestion des défaillances et aux listes de contrôle opérationnelles - vous pouvez éviter les pièges courants qui conduisent à des pannes d'application et à un gaspillage de ressources. Rappelez-vous : observez toujours avant de changer, faites de petits ajustements incrémentaux, utilisez des métriques concrètes pour guider vos décisions et ayez un plan de retour en arrière. Les commandes et exemples fournis ici sont un point de départ ; adaptez-les à votre environnement spécifique et aux caractéristiques de votre charge de travail. Comme prochaine étape, choisissez une vérification à faible risque dans la liste de contrôle, exécutez-la dans un namespace non productif et enregistrez les résultats. Appliquez ensuite la même discipline à vos charges de travail de production, une à la fois. Avec une planification de capacité minutieuse, vos clusters Kubernetes seront plus stables, rentables et prêts pour la croissance.