infra / kubernetes
Production readiness : requests/limits, PodDisruptionBudget, affinité
Explication
Ce que vous allez apprendre
- Comprendre pourquoi une démo qui fonctionne n'est pas la même chose qu'un déploiement prêt pour la prod
- Forcer une répartition résiliente des Pods avec l'anti-affinité et les
topologySpreadConstraints - Comprendre ce qu'un PodDisruptionBudget protège réellement (et ce qu'il ne protège pas)
- Appliquer un
securityContextrestrictif par défaut (non-root, lecture seule, capacités limitées) - Utiliser une
PriorityClasspour garantir la survie des workloads critiques en cas de pénurie
Dans quel contexte ?
Une équipe a déployé son API avec 6 replicas, confiante que cette redondance suffit à assurer la disponibilité du service. Un soir, le node qui héberge par hasard 4 de ces 6 Pods tombe en panne matérielle — Kubernetes avait placé la majorité des replicas sur la même machine, sans que personne ne l'ait explicitement décidé. Le service continue de fonctionner de justesse avec seulement 2 Pods restants, au bord de la surcharge. Cette dernière leçon rassemble les réglages qui transforment une résilience "accidentelle" comme celle-ci en une résilience réellement garantie par configuration.
Faire fonctionner n'est pas la même chose que "prêt pour la prod"
D'abord, toutes les leçons précédentes ont montré comment faire fonctionner des objets Kubernetes individuellement. Cette dernière leçon rassemble les bonnes pratiques transversales.
Ce qui distingue une démo d'un vrai cluster de production
Ce qui distingue un cluster de démonstration d'un cluster réellement prêt pour la production, c'est que les pannes, les mises à jour d'infrastructure et les pics de charge y sont la norme, pas l'exception.
Le risque de tout placer au même endroit
Même avec 6 replicas, si Kubernetes les place tous par hasard sur le même node ou dans la même zone géographique, une seule panne matérielle peut tout emporter.
La solution : forcer une répartition intelligente
L'anti-affinité et les topologySpreadConstraints forcent explicitement une répartition des Pods sur des machines et des zones différentes, transformant une résilience "accidentelle" en résilience garantie par configuration.
| Bonne pratique | Protège contre |
|---|---|
Anti-affinité / topologySpreadConstraints | Tous les replicas concentrés sur le même node/zone |
PodDisruptionBudget | Une maintenance planifiée qui coupe trop de Pods d'un coup |
securityContext restrictif | Une surface d'attaque inutilement large en cas de compromission |
PriorityClass | La perte de workloads critiques lors d'une pénurie de ressources |
Piège fréquent
Le PodDisruptionBudget est souvent mal compris : il ne protège absolument pas contre un crash inattendu ou une panne matérielle. Il protège uniquement contre les opérations volontaires et planifiées (maintenance d'un node, mise à niveau du cluster), en garantissant qu'un minimum de Pods reste disponible pendant ces opérations précises.
Un objet souvent mal compris : le PodDisruptionBudget
Une fois les Pods bien répartis, il reste un point conceptuel essentiel : le PodDisruptionBudget (PDB) ne protège absolument pas contre un crash inattendu.
Ce que le PDB protège réellement
Il protège contre les opérations VOLONTAIRES et planifiées, comme la maintenance d'un node ou une mise à niveau du cluster, en garantissant qu'un minimum de Pods reste toujours disponible pendant ces opérations.
Une sécurité qui doit être la base, pas un ajout
Faire tourner les conteneurs avec un utilisateur non-root, un système de fichiers en lecture seule, et aucune capacité Linux superflue réduit drastiquement la surface d'attaque en cas de compromission.
Où placer ces réglages dans le cycle de développement
Ces réglages de securityContext devraient être la configuration par défaut de tout déploiement de production, pas une case cochée seulement après un audit de sécurité.
Bonne pratique
Ajoute runAsNonRoot: true, readOnlyRootFilesystem: true et capabilities: drop: ["ALL"] dès le premier manifeste d'un nouveau projet, avant même le premier déploiement en production. Les ajouter après coup, une fois l'application déjà en service, oblige souvent à corriger des hypothèses cachées dans le code (écriture de fichiers temporaires, port privilégié...).
Garantir la survie de ce qui compte vraiment
Enfin, en cas de pénurie sévère de ressources sur le cluster, la PriorityClass permet de dire explicitement à Kubernetes quels workloads doivent survivre en premier, une garantie utile pour que les services critiques ne soient jamais sacrifiés avant des tâches secondaires.
Ce cours touche ici à sa fin : tu es maintenant capable de faire tourner une application, de son premier Pod jusqu'à un déploiement réellement prêt pour la production.
Commandes & code
Production readiness (niveau expert)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deployment
namespace: production
spec:
replicas: 6
strategy:
rollingUpdate:
maxSurge: 2
maxUnavailable: 0 # 0 indisponibilité tolérée en prod critique
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
# Anti-affinité : répartit les Pods sur des nodes différents (évite un point de panne unique)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api
topologyKey: kubernetes.io/hostname
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values: ["compute-optimized"]
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api
terminationGracePeriodSeconds: 30
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: api
image: myorg/api:1.5.0
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi" # limit mémoire = protection OOM ; CPU limit peut throttler
livenessProbe:
httpGet: { path: /health/live, port: 3000 }
periodSeconds: 10
readinessProbe:
httpGet: { path: /health/ready, port: 3000 }
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]# PodDisruptionBudget : protège la disponibilité pendant les opérations VOLONTAIRES
# (drain de node, mise à jour du cluster) - n'a AUCUN effet sur un crash involontaire
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
namespace: production
spec:
minAvailable: 4 # ou "maxUnavailable: 2" - jamais les deux en même temps
selector:
matchLabels:
app: api# Un "kubectl drain" respecte le PDB : il refuse d'évincer un Pod si ça viole le budget
kubectl drain node-3 --ignore-daemonsets --delete-emptydir-data
# Vérifier la répartition réelle des Pods sur les nodes (anti-affinité effective ?)
kubectl get pods -o wide -l app=api
# Préemption / priorité : garantir que les workloads critiques ne sont jamais évincés en premier# PriorityClass - les Pods critiques survivent en premier en cas de pression sur les ressources
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "Réservé aux workloads critiques de production"Résumé
requests/limitscorrects partout : évite l'OOMKill (mémoire) et le throttling excessif (CPU).podAntiAffinity+topologySpreadConstraintsévitent qu'une seule panne de node/zone tue le service.PodDisruptionBudgetprotège les opérations de maintenance volontaires (drain, upgrade de nodes).securityContext(runAsNonRoot,readOnlyRootFilesystem,cap drop ALL) doit être systématique.PriorityClassgarantit que les workloads critiques survivent en cas de pénurie de ressources.
Exercices pratiques
Mission : garantir qu'une panne de node ne coupe plus jamais la majorité du service
Objectif : Forcer une répartition résiliente des Pods sur des nodes différents, ajouter un PodDisruptionBudget adapté, et comprendre précisément ce qu'il protège ou non.
Contexte
L'API api-deployment tourne avec 6 replicas dans le namespace production, sans aucune règle d'anti-affinité. Un soir, le node qui héberge par hasard 4 de ces 6 Pods tombe en panne matérielle : Kubernetes avait placé la majorité des replicas sur la même machine, sans que personne ne l'ait explicitement décidé. Le service survit de justesse avec seulement 2 Pods restants, au bord de la surcharge. Il faut transformer cette résilience accidentelle en résilience garantie par configuration.