Chaos Engineering : je casse mon cluster exprès
Mon homelab marchait bien. Trop bien. Et c’est exactement ça le problème.
Quand tout fonctionne, tu ne sais pas ce qui se passe quand ça casse. Tu ne sais pas si tes alertes marchent, si tes dashboards affichent les bonnes métriques, si ton agent IA sait vraiment réparer les trucs.
Alors j’ai décidé de tout casser. Volontairement. Méthodiquement.
Le principe du Chaos Engineering
Netflix a popularisé le concept avec Chaos Monkey : un programme qui kill des instances de production au hasard. L’idée : si ton système survit à des pannes aléatoires, il survivra aux vraies pannes.
Les principes :
- Définir l’état normal (les métriques de base)
- Formuler une hypothèse (“si je kill ce pod, le service reste disponible”)
- Injecter la panne
- Observer ce qui se passe
- Analyser les résultats
- Améliorer ce qui a cassé
Sur un homelab single-node, on ne peut pas tester la perte d’un node. Mais on peut tester beaucoup d’autres choses.
Expérience 1 : Kill un pod au hasard
Hypothèse
Kubernetes devrait redémarrer automatiquement n’importe quel pod killé, grâce aux Deployments et leur replicas spec.
L’injection
# Lister les pods running
kubectl get pods -A --field-selector=status.phase=Running -o json | \
jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)"' | \
shuf -n 1
Résultat : monitoring/prometheus-server-6d4b8c9f5-x7k2p
OK, pas le plus facile pour commencer. On kill Prometheus.
kubectl delete pod prometheus-server-6d4b8c9f5-x7k2p -n monitoring --grace-period=0
Observation
Ce qui s’est passé :
- T+0s : Pod terminé
- T+1s : Kubernetes crée un nouveau pod (
prometheus-server-6d4b8c9f5-m3n9q) - T+3s : Nouveau pod en
ContainerCreating - T+8s : Nouveau pod en
Running - T+15s : Prometheus scrappe de nouveau les métriques
Trou dans les métriques : ~15 secondes sans collecte. Grafana affiche un gap dans les graphes.
Alerte déclenchée ? Non. AlertManager n’a pas eu le temps de réagir (le for: 1m dans mes alertes exige 1 minute de problème continu).
Verdict
Kubernetes fait le job. Mais 15 secondes de trou, ça veut dire que si ça arrive pendant un incident réel, on perd des données de monitoring. Pour un service critique, il faudrait 2 replicas minimum.
Expérience 2 : Saturer la mémoire d’un namespace
Hypothèse
Les ResourceQuotas et LimitRanges devraient empêcher un pod gourmand de crasher les autres.
L’injection
Un pod qui consomme toute la RAM :
apiVersion: v1
kind: Pod
metadata:
name: memory-bomb
namespace: test-chaos
spec:
containers:
- name: stress
image: progrium/stress
args: ["--vm", "1", "--vm-bytes", "2G", "--timeout", "60s"]
resources:
limits:
memory: "512Mi" # Limite à 512Mi
requests:
memory: "256Mi"
Le container demande 2GB mais est limité à 512Mi.
kubectl apply -f memory-bomb.yaml
Observation
Ce qui s’est passé :
- T+0s : Pod démarré
- T+2s : Le processus
stresscommence à allouer de la mémoire - T+3s : Le container atteint la limite de 512Mi
- T+3s : OOMKilled — Kubernetes kill le container immédiatement
- T+4s : Le pod redémarre (RestartPolicy: Always)
- T+5s : Re-OOMKilled
- T+15s : CrashLoopBackOff
Impact sur les autres pods ? Aucun. Les resource limits ont parfaitement fonctionné. Les autres pods du namespace n’ont rien vu.
Dans Grafana : Le dashboard montre un spike de mémoire suivi d’un crash, puis la boucle de restart. Exactement ce qu’on attend.
Et sans resource limits ?
J’ai retesté sans le champ resources.limits :
kubectl run memory-bomb-unlimited --image=progrium/stress \
--namespace=test-chaos -- --vm 1 --vm-bytes 4G --timeout 60s
Résultat : Le container a consommé 4GB de RAM. D’autres pods du même node ont commencé à ralentir. Kubernetes a fini par OOMKill le pod le plus gourmand (le nôtre), mais ça a pris plus de temps et ça a impacté les voisins.
Verdict
Toujours mettre des resource limits. C’est pas optionnel. Sans ça, un pod peut affecter tout le node.
Expérience 3 : Corrompre un PVC
Hypothèse
Si les données d’un PersistentVolumeClaim sont corrompues, l’application devrait échouer proprement et Velero devrait pouvoir restaurer.
L’injection
Écrire des données corrompues dans le PVC de Redis :
# Trouver le pod Redis
kubectl get pods -n console -l app=redis
# Écrire du garbage dans le fichier de dump
kubectl exec -n console redis-0 -- sh -c \
'echo "CORRUPTED_DATA" > /data/dump.rdb'
# Forcer un restart
kubectl delete pod redis-0 -n console
Observation
Ce qui s’est passé :
- T+0s : Redis redémarre
- T+1s : Redis tente de charger
dump.rdb - T+1s :
Bad file format reading the append only file: make a backup - T+2s : Redis refuse de démarrer → CrashLoopBackOff
Impact : Toute la console web est inaccessible (pas de session, pas de cache, pas de quiz).
Restauration Velero :
# Lister les backups
velero backup get
# Restaurer juste le PVC de Redis
velero restore create redis-restore \
--from-backup daily-full-20260218 \
--include-namespaces console \
--include-resources persistentvolumeclaims,persistentvolumes \
--selector app=redis
Temps de restauration : ~2 minutes. Redis a redémarré proprement avec les données d’avant la corruption.
Verdict
Velero fonctionne. Mais 2 minutes de downtime pour Redis = 2 minutes de console inaccessible. Pour un vrai environnement de prod, il faudrait un Redis Sentinel ou un cluster Redis.
Expérience 4 : Supprimer un Deployment entier
Hypothèse
Si quelqu’un fait un kubectl delete deployment accidentel, ArgoCD ou un GitOps devrait restaurer automatiquement. Mais moi, j’ai pas encore de GitOps…
L’injection
# Supprimer le deployment de la console frontend
kubectl delete deployment console-ui -n console
Observation
Ce qui s’est passé :
- T+0s : Deployment supprimé, pods terminés
- T+5s : La console web est inaccessible
- T+5s : Nginx Proxy Manager retourne 502 Bad Gateway
Restauration ? Pas de GitOps, donc pas de restauration automatique.
Il faut redéployer manuellement :
# Rebuild et redéployer
cd web-console && ./build-and-deploy.sh --frontend-only
Temps de restauration : ~3 minutes (build + push + deploy).
Ou bien restaurer avec Velero :
velero restore create console-restore \
--from-backup daily-full-20260218 \
--include-namespaces console \
--include-resources deployments \
--selector app=console-ui
Temps : ~30 secondes. Beaucoup plus rapide.
Verdict
C’est ici que le manque de GitOps se fait sentir. Avec ArgoCD, le Deployment serait recréé automatiquement en quelques secondes. Sans ça, on dépend des backups Velero ou d’un redéploiement manuel.
Expérience 5 : Couper le réseau d’un pod
Hypothèse
Si un pod perd la connectivité réseau, les health checks devraient détecter le problème et Kubernetes devrait le redémarrer.
L’injection
On ne peut pas couper le réseau d’un pod directement sans Network Policies. Mais on peut simuler en bloquant le trafic sortant :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-egress
namespace: console
spec:
podSelector:
matchLabels:
app: console-api
policyTypes:
- Egress
egress: [] # Aucun trafic sortant autorisé
kubectl apply -f block-egress.yaml
Observation
Ce qui s’est passé :
- T+0s : NetworkPolicy appliquée
- T+1s : Le backend console ne peut plus joindre Redis, ni l’API K8s
- T+5s : Les requêtes HTTP échouent (timeout Redis)
- T+10s : La liveness probe échoue (si elle dépend d’une connexion Redis)
- T+30s : Kubernetes redémarre le pod
- T+31s : Le pod redémarre… mais ne peut toujours pas joindre Redis (la NetworkPolicy est encore là)
- T+60s : CrashLoopBackOff
Intéressant : le pod redémarre en boucle parce que la cause est externe (réseau bloqué). Kubernetes ne peut pas résoudre ça tout seul.
# Supprimer la NetworkPolicy pour restaurer
kubectl delete networkpolicy block-egress -n console
Le pod a redémarré normalement après ça.
Verdict
Les liveness probes fonctionnent, mais elles ne peuvent pas distinguer “le code a crashé” de “le réseau est coupé”. Dans les deux cas, Kubernetes redémarre le pod, ce qui ne résout rien si le problème est réseau.
Expérience 6 : Tester l’agent IA en chaos
Hypothèse
Mon agent IA devrait être capable de diagnostiquer et réparer les pannes créées par les expériences précédentes.
L’injection
Je kill un pod de la console et je demande à l’agent :
"Le pod console-api crash, répare-le"
Observation
L’agent a :
- Utilisé
kubectl_getpour voir les pods → trouvé le pod en CrashLoopBackOff - Utilisé
analyze_pod_failure→ identifié “Back-off restarting failed container” - Utilisé
get_pod_logs→ lu les logs du dernier crash - Identifié le problème (connexion Redis refusée, à cause de ma NetworkPolicy encore active)
- Utilisé
kubectl_getpour lister les NetworkPolicies - Identifié
block-egresscomme la cause - Recommandé de supprimer la NetworkPolicy
Temps total : 25 secondes. 6 tool calls, 3 itérations Claude.
Il n’a pas supprimé la NetworkPolicy lui-même (action destructive → demande confirmation), mais il a diagnostiqué correctement et proposé la solution.
Verdict
L’agent fonctionne comme prévu pour le troubleshooting. Il suit le pattern analyse → diagnostic → proposition de fix. Le garde-fou “demander confirmation pour les suppressions” a bien fonctionné.
Le tableau de bord chaos
Après toutes ces expériences, voici le récapitulatif :
| Expérience | Hypothèse | Résultat | Action nécessaire |
|---|---|---|---|
| Kill pod random | K8s redémarre | OK — 15s de downtime | Ajouter des replicas pour les services critiques |
| Saturer la RAM | Les limits protègent | OK — OOMKill immédiat | Toujours mettre des limits |
| Corrompre PVC | Velero restaure | OK — 2 min de restore | Backup plus fréquent pour Redis |
| Supprimer Deployment | Restauration auto | FAIL — Pas de GitOps | Implémenter ArgoCD |
| Couper le réseau | Health checks détectent | PARTIEL — Redémarre en boucle | Ajouter des readiness probes réseau |
| Agent IA en chaos | Diagnostic auto | OK — 25s de diagnostic | Rien, ça marche |
Ce que j’ai amélioré après
1. Resource Limits partout
J’ai audité tous mes Deployments. Ceux qui n’avaient pas de limits en ont maintenant :
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
2. Liveness et Readiness probes
Ajouté des probes sur tous les services qui n’en avaient pas :
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
La distinction est importante :
- Liveness : “est-ce que le process tourne ?” → si non, redémarrer
- Readiness : “est-ce que le service est prêt à recevoir du trafic ?” → si non, retirer du Service
3. Alertes plus rapides
J’ai réduit le for de certaines alertes critiques :
# Avant
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[5m]) > 0
for: 5m # Trop long !
# Après
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[5m]) > 0
for: 1m # Réagir plus vite
4. La liste “à faire”
Les expériences ont révélé des manques :
- ArgoCD pour le GitOps (restauration automatique des Deployments)
- Redis Sentinel pour la haute disponibilité de Redis
- NetworkPolicies par défaut pour isoler les namespaces
- Backup Redis toutes les heures au lieu de toutes les 6h
Comment reproduire chez vous
Si vous avez un homelab K8s, voici un script simple pour commencer :
#!/bin/bash
# chaos-monkey-lite.sh - Kill un pod random toutes les X minutes
INTERVAL=${1:-300} # 5 minutes par défaut
EXCLUDE_NS="kube-system|kube-public|velero"
while true; do
# Trouver un pod random (hors namespaces critiques)
VICTIM=$(kubectl get pods -A --field-selector=status.phase=Running -o json | \
jq -r --arg exclude "$EXCLUDE_NS" \
'.items[] | select(.metadata.namespace | test($exclude) | not) | "\(.metadata.namespace) \(.metadata.name)"' | \
shuf -n 1)
if [ -n "$VICTIM" ]; then
NS=$(echo $VICTIM | awk '{print $1}')
POD=$(echo $VICTIM | awk '{print $2}')
echo "$(date): Killing pod $POD in namespace $NS"
kubectl delete pod "$POD" -n "$NS" --grace-period=0
fi
sleep $INTERVAL
done
Lancez-le, ouvrez Grafana, et observez. C’est fascinant de voir comment le cluster réagit.
Ce que j’ai appris
1. On ne sait jamais si ça marche tant qu’on a pas testé. Mes alertes avaient l’air bien sur le papier. En pratique, certaines ne se déclenchaient jamais parce que le for était trop long.
2. Les resource limits ne sont pas optionnels. Un pod sans limits, c’est une bombe à retardement.
3. Le chaos engineering, c’est pas que pour Netflix. Même sur un homelab single-node, ça révèle des problèmes qu’on n’aurait jamais trouvés autrement.
4. GitOps manque cruellement. C’est le plus gros trou dans mon setup. Sans source de vérité déclarative, une suppression accidentelle demande une intervention manuelle.
5. L’observabilité rend le chaos utile. Sans Prometheus + Grafana + Loki, le chaos engineering c’est juste casser des trucs. Avec l’observabilité, c’est une méthode scientifique.
Le chaos engineering m’a rendu beaucoup plus confiant dans mon homelab. Pas parce que tout est parfait, mais parce que je sais exactement ce qui casse et comment le réparer.
Et le prochain gros chantier est clair : ArgoCD pour le GitOps. Quand ton cluster peut se reconstruire tout seul à partir d’un repo Git, tu dors vraiment tranquille.