Les opérateurs Kubernetes rencontrent fréquemment des scénarios où la gestion de base des pods et l'exposition des services ne suffisent plus. Les charges de travail de production exigent un contrôle d'ordonnancement granulaire, des patterns de stockage résilients, une segmentation réseau sécurisée et des domaines de défaillance observables. Cet article couvre cinq concepts avancés de Kubernetes — les contraintes de répartition topologique des pods, les classes de priorité avec préemption, les workflows de snapshot et restauration CSI, les contrôles d'égresse NetworkPolicy, et l'autoscaling par métriques personnalisées — avec des commandes versionnées, les patterns de sortie attendus et les étapes de vérification de récupération. Chaque section suit une approche « observer d'abord » : capturer l'état actuel, appliquer le plus petit changement justifié, vérifier le signal, et documenter le chemin de retour.
Contraintes de répartition topologique des pods pour le contrôle des domaines de défaillance
Les contraintes de répartition topologique (topology spread constraints) distribuent les pods à travers des domaines de défaillance tels que les zones, les nœuds ou des étiquettes personnalisées. Contrairement à l'anti-affinité des pods, qui peut laisser des pods non ordonnançables lorsque les contraintes entrent en conflit, la répartition topologique utilise un mécanisme de notation qui favorise un placement équilibré tout en restant ordonnançable.
Prérequis : Kubernetes 1.19+ (bêta), 1.24+ (stable). Les nœuds doivent porter l'étiquette topologique topology.kubernetes.io/zone ou une étiquette personnalisée telle que failure-domain.example.com/rack.
Observer l'état actuel :
kubectl get nodes --show-labels | grep -E 'zone|rack'
kubectl get pods -n production -l app=payment-api -o wide
Observation attendue : Pods regroupés sur deux des trois zones disponibles, laissant une zone vide.
Plus petit changement justifié : Ajouter une topologySpreadConstraint à la spec du Deployment avec maxSkew: 1, topologyKey: topology.kubernetes.io/zone, et whenUnsatisfiable: ScheduleAnyway.
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: payment-api
Vérifier le résultat : Après le déploiement, kubectl get pods -n production -l app=payment-api -o jsonpath='{.items[*].spec.nodeName}' | xargs -n1 kubectl get node --show-labels devrait montrer des pods répartis sur les trois zones avec une différence maximale d'un pod par zone.
Récupération : Revenir à la révision précédente du Deployment avec kubectl rollout undo deployment/payment-api -n production et confirmer que la distribution des pods revient à l'état antérieur.
Classes de priorité et préemption pour les charges de travail critiques
Les classes de priorité (PriorityClass) assignent une précédence d'ordonnancement aux pods. Lorsque les ressources du cluster sont épuisées, l'ordonnanceur préempte les pods de priorité inférieure pour admettre ceux de priorité supérieure. Ce mécanisme protège les composants du control plane et les services critiques pour le revenu pendant les contentions.
Prérequis : Kubernetes 1.14+ (bêta), 1.19+ (stable). Les ressources PriorityClass doivent exister avant que les pods ne les référencent.
Observer l'état actuel :
kubectl get priorityclasses
kubectl describe priorityclass system-cluster-critical
kubectl get pods -A -o custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,PRIORITY:.spec.priorityClassName,NODE:.spec.nodeName
Observation attendue : Aucune PriorityClass personnalisée pour la charge de travail order-processing ; ses pods s'exécutent à la priorité par défaut (0) et sont évincés en premier lors de pression.
Plus petit changement justifié : Créer une PriorityClass nommée business-critical avec value: 1000000 et preemptionPolicy: PreemptLowerPriority, puis patcher le Deployment pour la référencer.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: business-critical
value: 1000000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "Le traitement des commandes ne doit pas être préempté par les jobs batch"
kubectl patch deployment order-processor -n production -p '{"spec":{"template":{"spec":{"priorityClassName":"business-critical"}}}}'
Vérifier le résultat : Simuler une pression de ressources en déployant un DaemonSet de faible priorité (value: 100) qui demande tout le CPU restant. L'ordonnanceur devrait préempter les pods du DaemonSet pour placer de nouveaux répliques order-processor. Confirmer avec kubectl get events --field-selector reason=Preempted -n production.
Récupération : Supprimer la PriorityClass (ce qui échoue si elle est référencée) après avoir retiré la référence du Deployment, puis restaurer le Deployment. Vérifier qu'aucun événement de préemption orphelin ne subsiste.
Snapshot et restauration CSI pour les charges de travail stateful
Les snapshots CSI (Container Storage Interface) fournissent des copies à un instant T des PersistentVolumes sans temps d'arrêt de l'application. Combinés avec VolumeSnapshotContent et VolumeSnapshotClass, ils permettent des workflows de sauvegarde, de clonage et de reprise après sinistre.
Prérequis : Kubernetes 1.20+ (bêta), 1.22+ (stable). Le driver CSI doit supporter la capacité VOLUME_SNAPSHOT (ex. : AWS EBS CSI driver v1.14+, GCE PD CSI driver v1.5+, Azure Disk CSI driver v1.5+). Une VolumeSnapshotClass doit exister dans le cluster.
Observer l'état actuel :
kubectl get volumesnapshotclass
kubectl get pvc -n data-platform -l app=postgres
kubectl describe pvc postgres-data-0 -n data-platform
Observation attendue : Un PVC unique postgres-data-0 lié à un PV provisionné par ebs.csi.aws.com, aucun snapshot existant.
Plus petit changement justifié : Créer un VolumeSnapshot référençant le PVC, puis restaurer vers un nouveau PVC pour un environnement de staging.
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: postgres-snap-2024-01-15
namespace: data-platform
spec:
volumeSnapshotClassName: csi-aws-ebs-snapclass
source:
persistentVolumeClaimName: postgres-data-0
kubectl apply -f postgres-snapshot.yaml
kubectl wait --for=condition=Ready volumesnapshot/postgres-snap-2024-01-15 -n data-platform --timeout=10m
Restauration :
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-staging-restore
namespace: data-platform
spec:
storageClassName: gp3
dataSource:
name: postgres-snap-2024-01-15
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
Vérifier le résultat : kubectl get pvc postgres-staging-restore -n data-platform affiche Bound. Déployer un pod PostgreSQL temporaire montant ce PVC et exécuter SELECT count(*) FROM orders; pour valider l'intégrité des données.
Récupération : Supprimer le PVC de staging et le VolumeSnapshot. Le PVC et le PV d'origine restent intacts. Documenter la politique de rétention des snapshots (ex. : snapshots quotidiens conservés 30 jours) dans le runbook.
Contrôles d'égresse NetworkPolicy pour une segmentation zero-trust
Les ressources NetworkPolicy restreignent le trafic pod-à-pod et pod-vers-externe. Les règles d'égresse (egress) sont souvent négligées mais sont essentielles pour prévenir l'exfiltration de données et limiter le rayon d'impact lorsqu'une charge de travail est compromise.
Prérequis : Kubernetes 1.8+ avec un plugin CNI qui applique les NetworkPolicy (Calico, Cilium, Weave Net, Antrea). kube-proxy ne doit pas être en mode IPVS strict-ARP sans support CNI.
Observer l'état actuel :
kubectl get networkpolicy -n finance
kubectl exec -n finance deploy/api-gateway -- curl -s -o /dev/null -w "%{http_code}" http://external-payment-provider.example.com
Observation attendue : Aucune NetworkPolicy n'existe dans l'espace de noms finance ; l'égresse vers toute destination réussit.
Plus petit changement justifié : Appliquer une politique de refus par défaut en égresse, puis autoriser uniquement les endpoints externes requis par CIDR ou DNS (via politique FQDN Cilium ou GlobalNetworkPolicy Calico avec sélecteur DNS).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-gateway-egress
namespace: finance
spec:
podSelector:
matchLabels:
app: api-gateway
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 203.0.113.0/24 # CIDR du fournisseur de paiement
ports:
- protocol: TCP
port: 443
- to:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 9090
Vérifier le résultat : La commande curl de l'étape d'observation retourne maintenant 000 (connexion refusée/timeout). Les destinations autorisées retournent 200. Vérifier les logs Cilium/Calico pour les verdicts DENY sur les tentatives d'égresse inattendues.
Récupération : Supprimer la NetworkPolicy avec kubectl delete networkpolicy api-gateway-egress -n finance. Confirmer que l'égresse est restaurée immédiatement. Si vous utilisez Cilium, vérifier que cilium policy get n'affiche aucun trou d'application.
Autoscaling par métriques personnalisées avec Prometheus Adapter
L'Horizontal Pod Autoscaler (HPA) supporte les métriques personnalisées via l'API Custom Metrics. Le Prometheus Adapter expose les requêtes Prometheus comme métriques scalables, permettant l'autoscaling sur des signaux métier (profondeur de file d'attente, latence de requête, taux d'erreur) plutôt que sur le CPU seul.
Prérequis : Kubernetes 1.16+ (API Custom Metrics stable). Prometheus Operator v0.50+ avec prometheus-adapter déployé. Les métriques doivent être étiquetées avec namespace et pod pour le scaling au niveau pod.
Observer l'état actuel :
kubectl get apiservice v1beta1.custom.metrics.k8s.io
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/shopping/pods/*/http_requests_per_second" | jq .
Observation attendue : Service API disponible. La métrique retourne {"items":[]} car aucun pod n'expose l'ensemble d'étiquettes requis.
Plus petit changement justifié : S'assurer que l'application émet un compteur Prometheus http_requests_total avec les étiquettes namespace, pod, service. Configurer la règle Prometheus Adapter pour dériver un taux par seconde.
# Extrait de config map prometheus-adapter
rules:
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
name:
matches: "^(.*)_total$"
as: "${1}_per_second"
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
kubectl rollout restart deployment/prometheus-adapter -n monitoring
Créer le HPA :
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: checkout-service-hpa
namespace: shopping
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout-service
minReplicas: 3
maxReplicas: 50
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"
Vérifier le résultat : Générer de la charge avec hey -z 5m -c 50 https://checkout.example.com. Surveiller kubectl get hpa checkout-service-hpa -n shopping -w — les répliques devraient passer de 3 vers 50 alors que la métrique dépasse 100 req/s par pod. Quand la charge cesse, le scale-down survient après la fenêtre de stabilisation (5 minutes par défaut).
Récupération : Supprimer le HPA. Revenir à la config Prometheus Adapter et redémarrer. Confirmer que l'API custom metrics ne sert plus la métrique.
Conclusion
Les opérations avancées sur Kubernetes exigent de sortir des configurations par défaut pour adopter des patterns versionnés, observables et réversibles. Les contraintes de répartition topologique éliminent les défaillances silencieuses de zone unique. Les classes de priorité avec préemption protègent les chemins critiques pour le revenu pendant les contentions. Les snapshots CSI transforment le stockage en un actif récupérable plutôt qu'en une responsabilité. Les règles d'égresse NetworkPolicy imposent des frontières zero-trust qui survivent au compromis d'une charge de travail. L'autoscaling par métriques personnalisées aligne le coût d'infrastructure sur les signaux de demande métier. Chaque technique suit la même discipline : observer l'état actuel avec des commandes concrètes, appliquer le plus petit changement justifié, vérifier le signal attendu, et documenter la procédure de retour avant que l'incident ne survienne. Commencez par implémenter un pattern dans un cluster de staging, mesurez les signaux de vérification, et promouvez seulement quand la procédure de récupération est testée et documentée.