Retour au cours

infra / kubernetes

Production readiness : requests/limits, PodDisruptionBudget, affinité

Leçon 201 exercice

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 securityContext restrictif par défaut (non-root, lecture seule, capacités limitées)
  • Utiliser une PriorityClass pour 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 pratiqueProtège contre
Anti-affinité / topologySpreadConstraintsTous les replicas concentrés sur le même node/zone
PodDisruptionBudgetUne maintenance planifiée qui coupe trop de Pods d'un coup
securityContext restrictifUne surface d'attaque inutilement large en cas de compromission
PriorityClassLa 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)

yaml
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"]
yaml
# 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
bash
# 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
yaml
# 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/limits corrects partout : évite l'OOMKill (mémoire) et le throttling excessif (CPU).
  • podAntiAffinity + topologySpreadConstraints évitent qu'une seule panne de node/zone tue le service.
  • PodDisruptionBudget protège les opérations de maintenance volontaires (drain, upgrade de nodes).
  • securityContext (runAsNonRoot, readOnlyRootFilesystem, cap drop ALL) doit être systématique.
  • PriorityClass garantit que les workloads critiques survivent en cas de pénurie de ressources.

Exercices pratiques

1 disponible
1

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.

Résoudre l’exercice →