Déploiements Sans Interruption avec Kubernetes
Implémentation de mises à jour progressives et déploiements blue-green pour des releases en production sans accroc.
Pourquoi le Zéro Interruption est Important
Aux débuts du déploiement logiciel, mettre un système hors ligne pour maintenance faisait simplement partie du processus. Vous planifiez une fenêtre de maintenance, informez les utilisateurs, mettez le système hors ligne, déployez la nouvelle version, et le remettiez en ligne. Pour les systèmes critiques, cela signifiait se réveiller à 2h du matin un dimanche.
Les plateformes SaaS modernes ne peuvent pas se permettre d'interruption. Quand vous gérez un service global avec des utilisateurs à travers les fuseaux horaires, il n'y a pas de "bon moment" pour une panne. Chaque minute d'interruption signifie revenus perdus, utilisateurs frustrés et confiance endommagée. Pour beaucoup d'entreprises, même quelques secondes d'interruption pendant les heures de pointe peuvent coûter des milliers d'euros.
Kubernetes fournit des primitives puissantes pour les déploiements sans interruption, mais elles doivent être utilisées correctement. Dans cet article, nous explorerons comment implémenter les mises à jour progressives, les déploiements blue-green et les releases canary—tout sans impact utilisateur.
Comprendre les Mises à Jour Progressives Kubernetes
Les mises à jour progressives sont la stratégie de déploiement par défaut de Kubernetes. Au lieu de fermer tous les pods en même temps et d'en démarrer de nouveaux, Kubernetes remplace progressivement les anciens pods par de nouveaux, assurant que votre service reste disponible tout au long du déploiement.
Voici comment cela fonctionne :
- Kubernetes crée un nouveau pod avec la version mise à jour
- Il attend que le nouveau pod soit prêt (passe les contrôles de santé)
- Seulement après que le nouveau pod soit sain, il termine un ancien pod
- Ce processus se répète jusqu'à ce que tous les pods soient mis à jour
Les paramètres clés qui contrôlent ce comportement sont :
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # Nombre max de pods supplémentaires pendant la mise à jour
maxUnavailable: 0 # Ne jamais permettre que des pods soient indisponibles
Définir maxUnavailable: 0 est critique pour zéro interruption. Cela garantit que Kubernetes ne termine jamais un ancien pod jusqu'à ce qu'un nouveau soit complètement prêt à gérer le trafic.
Le Rôle Critique des Sondes de Disponibilité
Les mises à jour progressives ne fonctionnent que si Kubernetes sait quand un pod est réellement prêt à servir le trafic. C'est là qu'interviennent les sondes de disponibilité. Une sonde de disponibilité indique à Kubernetes si un pod doit recevoir du trafic.
Voici une configuration de sonde de disponibilité prête pour la production :
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
Votre endpoint /health/ready devrait vérifier :
- Connexions base de données : L'app peut-elle se connecter à la base de données ?
- Dépendances critiques : Redis, files de messages, etc. sont-ils accessibles ?
- Initialisation : L'app a-t-elle fini de charger la configuration, réchauffer les caches ?
- Disponibilité des ressources : L'app a-t-elle les ressources dont elle a besoin ?
Une erreur courante est de rendre la sonde de disponibilité trop simple (juste retourner 200 OK) ou identique à la sonde de vivacité. La sonde de disponibilité devrait être plus complète—c'est acceptable qu'un pod soit vivant mais pas prêt à servir le trafic.
Gérer les Migrations de Base de Données en Toute Sécurité
Les migrations de base de données sont l'un des aspects les plus délicats des déploiements sans interruption. Vous ne pouvez pas simplement exécuter les migrations dans le démarrage de votre application car :
- Plusieurs pods pourraient essayer d'exécuter les migrations simultanément
- Les migrations pourraient ne pas être rétrocompatibles avec l'ancien code
- Les migrations longues pourraient bloquer le déploiement
Voici un pattern sûr utilisant les Jobs Kubernetes :
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration-v2.5.0
spec:
backoffLimit: 0 # Ne pas réessayer les migrations échouées
template:
spec:
restartPolicy: Never
initContainers:
- name: wait-for-db
image: postgres:15-alpine
command: ['sh', '-c', 'until pg_isready -h $DB_HOST -U $DB_USER; do sleep 2; done']
containers:
- name: migrate
image: myapp:v2.5.0
command: ["./run-migrations.sh"]
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
Le workflow :
- Exécuter la migration comme Job Kubernetes avant de déployer la nouvelle version
- Attendre que la migration se termine avec succès
- Déployer la nouvelle version de l'application
- Seulement après que la nouvelle version soit stable, nettoyer les anciens chemins de code
Pour les migrations complexes, utilisez le pattern expand-contract :
- Expand : Ajouter de nouvelles colonnes/tables sans supprimer les anciennes
- Deploy : Déployer du code qui écrit à la fois dans l'ancien et le nouveau schéma
- Migrate : Remplir rétroactivement les données de l'ancien vers le nouveau schéma
- Contract : Supprimer les anciennes colonnes/tables dans une release ultérieure
Déploiements Blue-Green avec Kubernetes
Les mises à jour progressives sont excellentes, mais parfois vous avez besoin d'encore plus de contrôle. Les déploiements blue-green maintiennent deux environnements complets : blue (production actuelle) et green (nouvelle version). Le trafic est basculé instantanément de blue vers green une fois que l'environnement green est validé.
Dans Kubernetes, vous implémentez blue-green avec des labels et des sélecteurs de service :
# Déploiement Blue (production actuelle)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-blue
spec:
replicas: 10
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: myapp
image: myapp:v1.0.0
---
# Déploiement Green (nouvelle version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
spec:
replicas: 10
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: myapp
image: myapp:v2.0.0
---
# Le service pointe vers blue initialement
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
version: blue # Changer vers 'green' pour basculer le trafic
ports:
- port: 80
targetPort: 8080
Le processus de déploiement :
- Déployer l'environnement green à côté de blue
- Exécuter des tests smoke contre green (sans trafic production)
- Basculer le sélecteur de service de
version: blueversversion: green - Surveiller pour détecter des problèmes
- Si des problèmes surviennent, rollback instantané en rebasculant vers blue
- Après validation, démolir l'environnement blue
L'avantage ? Basculement de trafic instantané et rollback instantané. L'inconvénient ? Vous avez besoin de 2x les ressources pendant le déploiement.
Releases Canary : Atténuation Progressive du Risque
Les releases canary combinent le meilleur des deux mondes. Vous déployez la nouvelle version pour un petit pourcentage d'utilisateurs, surveillez les problèmes, et augmentez progressivement le trafic si tout va bien.
Bien que Kubernetes n'ait pas de support canary natif, vous pouvez l'implémenter avec plusieurs déploiements et division de trafic pondérée :
# Version stable (90% du trafic)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-stable
spec:
replicas: 9
selector:
matchLabels:
app: myapp
track: stable
---
# Version Canary (10% du trafic)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-canary
spec:
replicas: 1
selector:
matchLabels:
app: myapp
track: canary
---
# Le service route vers les deux
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp # Correspond à stable et canary
ports:
- port: 80
targetPort: 8080
Avec 9 replicas stables et 1 replica canary, environ 10% du trafic va vers la nouvelle version. Augmentez progressivement les replicas canary tout en diminuant les replicas stables :
- Début : 9 stable, 1 canary (10% trafic canary)
- Après 1 heure : 7 stable, 3 canary (30% trafic canary)
- Après 4 heures : 5 stable, 5 canary (50% trafic canary)
- Après 8 heures : 0 stable, 10 canary (100% trafic canary)
Pour une division de trafic plus sophistiquée (pourcentages exacts, routage basé sur en-têtes, etc.), utilisez un service mesh comme Istio ou Linkerd.
Drainage de Connexions et Arrêt Gracieux
Quand Kubernetes termine un pod, votre application doit le gérer gracieusement. Sans gestion appropriée de l'arrêt, les requêtes en cours échoueront, causant des erreurs pour vos utilisateurs.
Kubernetes envoie un signal SIGTERM avant de tuer un pod. Votre application devrait :
- Arrêter d'accepter de nouvelles requêtes (faire échouer les contrôles de santé)
- Compléter les requêtes existantes (avec un timeout)
- Fermer les connexions base de données proprement
- Vider les logs et métriques
- Sortir avec code 0
Voici un exemple Node.js :
const express = require('express');
const app = express();
const server = app.listen(8080);
let isShuttingDown = false;
// Endpoint de contrôle de santé
app.get('/health/ready', (req, res) => {
if (isShuttingDown) {
res.status(503).send('Shutting down');
} else {
res.status(200).send('OK');
}
});
// Gestionnaire d'arrêt gracieux
process.on('SIGTERM', () => {
console.log('SIGTERM reçu, démarrage arrêt gracieux');
isShuttingDown = true;
// Arrêter d'accepter de nouvelles connexions
server.close(() => {
console.log('Serveur HTTP fermé');
// Fermer les connexions base de données
db.close().then(() => {
console.log('Connexions base de données fermées');
process.exit(0);
});
});
// Forcer l'arrêt après 30 secondes
setTimeout(() => {
console.error('Arrêt forcé après timeout');
process.exit(1);
}, 30000);
});
Configurer Kubernetes pour donner à votre app assez de temps :
spec:
terminationGracePeriodSeconds: 60 # Donner 60s à l'app pour s'arrêter
containers:
- name: myapp
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # Laisser k8s mettre à jour les endpoints
Le hook preStop avec un sleep de 5 secondes est crucial. Il donne à Kubernetes le temps de retirer le pod des endpoints de service avant que votre app arrête d'accepter des connexions. Sans cela, vous pourriez encore recevoir du trafic après le début de l'arrêt.
Stratégies de Surveillance et Rollback
Le déploiement sans interruption n'est pas complet sans surveillance appropriée et rollback automatisé. Vous devez détecter les problèmes rapidement et faire un rollback automatiquement.
Métriques clés à surveiller pendant le déploiement :
- Taux d'erreur : réponses 5xx, erreurs applicatives
- Latence : temps de réponse p50, p95, p99
- Débit : requêtes par seconde
- Utilisation des ressources : CPU, mémoire, pools de connexions
- Métriques métier : taux de conversion, succès checkout, etc.
Implémenter le rollback automatisé avec le statut de rollout Kubernetes :
#!/bin/bash
# Déployer nouvelle version
kubectl apply -f deployment.yaml
# Attendre le rollout
if ! kubectl rollout status deployment/myapp --timeout=5m; then
echo "Déploiement échoué, rollback en cours"
kubectl rollout undo deployment/myapp
exit 1
fi
# Surveiller le taux d'erreur pendant 5 minutes
for i in {1..30}; do
ERROR_RATE=$(curl -s "http://metrics/api/error-rate?service=myapp")
if (( $(echo "$ERROR_RATE > 1.0" | bc -l) )); then
echo "Taux d'erreur trop élevé ($ERROR_RATE%), rollback en cours"
kubectl rollout undo deployment/myapp
exit 1
fi
sleep 10
done
echo "Déploiement réussi"
Pièges du Monde Réel et Leçons Apprises
1. Drainage de Connexions Load Balancer
Si vous utilisez un load balancer cloud (AWS ALB, GCP Load Balancer), configurez le drainage de connexions. Le load balancer doit arrêter d'envoyer du trafic à un pod avant que Kubernetes ne le termine.
service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60"
2. Problèmes d'Affinité de Session
Si votre app utilise des sessions sticky, les mises à jour progressives peuvent casser les sessions utilisateur. Solutions :
- Utiliser un store de sessions externe (Redis) au lieu de sessions en mémoire
- Implémenter une logique de migration de session
- Rendre les sessions optionnelles (dégradation gracieuse)
3. Connexions WebSocket
Les connexions WebSocket ne se drainent pas automatiquement. Vous devez :
- Envoyer un message de fermeture aux clients avant l'arrêt
- Implémenter une logique de reconnexion côté client
- Utiliser un
terminationGracePeriodSecondsplus long (120s+)
4. Verrous Distribués et Élection de Leader
Si votre app utilise l'élection de leader (un seul pod traite certaines tâches), assurez un transfert approprié pendant le déploiement. Utilisez des leases Kubernetes ou des implémentations de verrous distribués qui gèrent les changements de nœuds gracieusement.
Tout Assembler
Les déploiements sans interruption avec Kubernetes nécessitent une orchestration minutieuse de plusieurs composants :
- Choisissez votre stratégie : Mise à jour progressive pour la plupart des cas, blue-green pour les releases critiques, canary pour les rollouts graduels
- Implémentez des contrôles de santé appropriés : Sondes de disponibilité et vivacité qui reflètent fidèlement l'état de l'application
- Gérez les migrations avec soin : Utilisez des jobs pour les migrations, implémentez le pattern expand-contract
- Implémentez un arrêt gracieux : Gérez SIGTERM, drainez les connexions, définissez des timeouts appropriés
- Surveillez activement : Suivez le taux d'erreur, la latence et les métriques métier pendant le déploiement
- Automatisez le rollback : Ne comptez pas sur l'intervention manuelle pendant les incidents
L'investissement initial dans la mise en place de déploiements sans interruption est rentabilisé immédiatement. Vous pouvez déployer plusieurs fois par jour sans vous soucier de l'impact utilisateur, répondre aux incidents plus rapidement, et mieux dormir la nuit en sachant que vos déploiements ne vous réveilleront pas avec des pannes de production.
Besoin d'aide pour implémenter des déploiements sans interruption pour votre infrastructure ? Discutons-en. Nous aidons les équipes à concevoir des pipelines de déploiement robustes, migrer vers Kubernetes et construire des systèmes de production fiables.