10 — Stratégies de déploiement#
Choisir la mauvaise stratégie de déploiement peut transformer une mise en production de routine en incident majeur. Ce chapitre présente les stratégies disponibles, leurs garanties respectives, et les critères qui guident le choix selon le type de service et le niveau de risque acceptable.
Rolling update#
Principe#
Le rolling update remplace progressivement les instances de l’ancienne version par la nouvelle, un sous-ensemble à la fois. C’est la stratégie par défaut de Kubernetes.
# deployment-rolling.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 6
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # pods supplémentaires autorisés pendant la mise à jour
maxUnavailable: 1 # pods indisponibles maximum pendant la mise à jour
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: ghcr.io/myorg/myapp:v2.1.0
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
Paramètres clés#
maxSurge: nombre (ou pourcentage) de pods supplémentaires pouvant exister pendant la mise à jour.maxSurge: 2sur 6 replicas signifie 8 pods maximum au pic.maxUnavailable: pods que Kubernetes peut terminer avant que leurs remplaçants soient prêts.maxUnavailable: 0garantit une disponibilité totale (au prix d’une mise à jour plus lente).Les readiness probes sont critiques : Kubernetes n’envoie du trafic à un nouveau pod que lorsqu’il répond
200 OK. Sans readiness probe, le trafic est routé vers un pod qui démarre encore.
Rollback automatique#
# Rollback immédiat vers la révision précédente
kubectl rollout undo deployment/myapp
# Rollback vers une révision spécifique
kubectl rollout undo deployment/myapp --to-revision=3
# Suivi en temps réel
kubectl rollout status deployment/myapp
Compatibilité base de données
Pendant un rolling update, l’ancienne et la nouvelle version du code coexistent. Si la nouvelle version nécessite un schéma de BDD incompatible avec l’ancienne, le rolling update provoquera des erreurs. La solution est le pattern d’expansion-contraction (expand/contract) : ajouter la colonne en mode nullable, déployer la nouvelle version, puis supprimer l’ancienne colonne dans un déploiement ultérieur.
Blue-Green#
Architecture blue-green#
Deux environnements identiques existent en permanence : blue (version actuelle) et green (nouvelle version). La bascule du trafic est instantanée via un changement de routage (service Kubernetes, load balancer, DNS).
# service-switch.yaml — bascule blue vers green
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
version: green # changer "blue" en "green" pour basculer
ports:
- port: 80
targetPort: 8080
---
# deployment-green.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
spec:
replicas: 6
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: myapp
image: ghcr.io/myorg/myapp:v2.1.0
Avantages et inconvénients#
Avantages#
Bascule instantanée : zéro downtime, pas de période de coexistence des versions
Rollback trivial : rechanger le sélecteur du Service vers
blueTests de fumée complets sur green avant toute exposition
Inconvénients#
Coût doublé pendant la période de transition (deux environnements complets)
Le problème de compatibilité BDD reste entier (même plus critique, car la bascule est instantanée pour 100% du trafic)
Nécessite une synchronisation des données d’état si l’application est stateful
Canary#
Déploiement progressif#
Le canary expose progressivement la nouvelle version à une fraction croissante du trafic, permettant de valider son comportement sur du trafic réel avant une promotion complète.
Trafic → Load balancer ──95%──► v1 (stable)
└──5%──► v2 (canary)
# Canary avec Nginx Ingress Controller
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10% du trafic
spec:
rules:
- host: api.myapp.fr
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-v2
port:
number: 80
Métriques de promotion#
La promotion d’un canary doit être conditionnée à des métriques observées, pas à une simple durée :
Taux d’erreur HTTP (5xx) inférieur au seuil de la version stable
Latence P99 dans les limites du SLO
Absence d’anomalies dans les logs (nouveaux patterns d’erreur)
Métriques métier stables (taux de conversion, panier moyen…)
Rollback automatique sur SLO breach#
Argo Rollouts
Argo Rollouts est le contrôleur Kubernetes de référence pour les déploiements canary et blue-green avancés. Il intègre nativement l’analyse de métriques (Prometheus, Datadog, New Relic) et le rollback automatique. Le CRD Rollout remplace le Deployment standard et offre un contrôle granulaire des étapes de promotion.
Shadow deployment#
Le shadow deployment (ou traffic mirroring) duplique le trafic de production vers la nouvelle version sans que les réponses de celle-ci n’atteignent les utilisateurs. La version shadow reçoit les mêmes requêtes réelles et ses réponses sont comparées ou simplement ignorées.
# Istio VirtualService — traffic mirroring
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp
http:
- route:
- destination:
host: myapp-v1
port:
number: 8080
weight: 100
mirror:
host: myapp-v2
port:
number: 8080
mirrorPercentage:
value: 100.0
Cas d’usage idéaux : valider la performance d’une nouvelle implémentation (refactoring majeur, changement de langage), tester des modèles ML sur du trafic réel, valider la compatibilité BDD sans risque.
Limite : les effets de bord (écriture en BDD, envoi d’emails, appels à des API tierces) doivent être neutralisés côté shadow, sous peine de doublons.
A/B testing vs canary#
Ces deux approches sont souvent confondues mais répondent à des objectifs différents :
Dimension |
Canary |
A/B testing |
|---|---|---|
Objectif |
Stabilité technique |
Comportement utilisateur |
Critère de succès |
Taux d’erreur, latence |
Taux de conversion, engagement |
Sélection du segment |
Aléatoire (pondération de trafic) |
Ciblée (cohorte, segment) |
Durée |
Heures à jours |
Jours à semaines |
Rollback |
Automatique sur métriques |
Décision produit |
Le canary est un outil d’ingénierie de fiabilité. L’A/B testing est un outil de product management. Les feature flags (chapitre 09) sont souvent le mécanisme d’implémentation de l’A/B testing.
Visualisation comparative des stratégies#
Arbre de décision : choisir sa stratégie#
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
fig, ax = plt.subplots(figsize=(13, 9))
ax.set_xlim(0, 13)
ax.set_ylim(0, 9)
ax.axis("off")
ax.set_title("Arbre de décision — choix de stratégie de déploiement", fontsize=13, fontweight="bold")
COLORS = {
"question": "#4C72B0",
"rolling": "#55A868",
"bluegreen": "#DD8452",
"canary": "#C44E52",
"shadow": "#8172B2",
"ab": "#937860",
}
def box(ax, x, y, w, h, text, color, fontsize=9):
b = FancyBboxPatch((x - w/2, y - h/2), w, h,
boxstyle="round,pad=0.12",
facecolor=color, edgecolor="white", linewidth=1.8, alpha=0.90)
ax.add_patch(b)
ax.text(x, y, text, ha="center", va="center", fontsize=fontsize,
color="white", fontweight="bold", multialignment="center")
def arr(ax, x1, y1, x2, y2, label="", color="#555"):
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle="->", color=color, lw=1.8))
if label:
mx, my = (x1 + x2) / 2, (y1 + y2) / 2
ax.text(mx + 0.1, my, label, fontsize=8, color=color)
box(ax, 6.5, 8.3, 3.2, 0.8, "Service stateful ?", COLORS["question"], 10)
box(ax, 3.5, 6.8, 3.0, 0.8, "Rollback trivial requis ?", COLORS["question"], 10)
box(ax, 9.5, 6.8, 2.4, 0.8, "Rolling update\n(StatefulSet)", COLORS["rolling"], 9)
arr(ax, 6.5, 7.9, 4.0, 7.2, "Non")
arr(ax, 6.5, 7.9, 9.5, 7.2, "Oui")
box(ax, 2.0, 5.3, 2.8, 0.8, "Trafic fort\net critique ?", COLORS["question"], 10)
box(ax, 5.5, 5.3, 2.4, 0.8, "Rolling update", COLORS["rolling"], 9)
arr(ax, 3.5, 6.4, 2.5, 5.7, "Oui")
arr(ax, 3.5, 6.4, 5.5, 5.7, "Non")
box(ax, 0.9, 3.7, 2.2, 0.8, "Budget\ndouble infra ?", COLORS["question"], 9)
box(ax, 3.5, 3.7, 2.2, 0.8, "Canary", COLORS["canary"], 10)
arr(ax, 2.0, 4.9, 1.4, 4.1, "Oui")
arr(ax, 2.0, 4.9, 3.5, 4.1, "Non")
box(ax, 0.9, 2.3, 2.2, 0.8, "Blue-Green", COLORS["bluegreen"], 10)
arr(ax, 0.9, 3.3, 0.9, 2.7, "Oui")
box(ax, 9.5, 5.3, 2.4, 0.8, "Validation sans\nrisque ? Shadow", COLORS["shadow"], 9)
box(ax, 9.5, 3.7, 2.4, 0.8, "Test fonctionnel ?\nA/B + feature flag", COLORS["ab"], 9)
ax.text(0.9, 1.8, "Bascule instantanée,\nrollback = re-switch",
fontsize=8, color=COLORS["bluegreen"], ha="center", style="italic")
ax.text(3.5, 3.1, "5 %→25 %→100 %,\nrollback auto sur SLO",
fontsize=8, color=COLORS["canary"], ha="center", style="italic")
ax.text(5.5, 4.7, "Simple, robuste,\nparfait pour la majorité",
fontsize=8, color=COLORS["rolling"], ha="center", style="italic")
plt.savefig("_static/10_decision_arbre.png", dpi=120, bbox_inches="tight")
plt.show()
Simulation canary avec rollback automatique#
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
np.random.seed(7)
STEPS = 40
WEIGHT_STEPS = [0.05, 0.10, 0.25, 0.50, 1.00]
ERROR_THRESHOLD = 0.05 # seuil de rollback : 5 % d'erreurs
def simulate_canary(bug_start_step=None, bug_severity=0.20):
"""
Simule un déploiement canary avec rollback automatique.
bug_start_step : si fourni, introduit un bug à ce palier.
"""
weights = []
error_rates = []
rolled_back = False
rollback_step = None
current_weight_idx = 0
current_weight = 0.0
for step in range(STEPS):
if not rolled_back and step % 8 == 0 and current_weight_idx < len(WEIGHT_STEPS):
current_weight = WEIGHT_STEPS[current_weight_idx]
current_weight_idx += 1
if rolled_back:
current_weight = 0.0
weights.append(current_weight)
base_error = np.random.beta(1, 30)
if bug_start_step and step >= bug_start_step and not rolled_back:
error = base_error + np.random.uniform(bug_severity * 0.8, bug_severity * 1.2)
else:
error = base_error
weighted_error = error * current_weight + base_error * (1 - current_weight)
error_rates.append(weighted_error)
if (not rolled_back
and step >= 3
and np.mean(error_rates[-3:]) > ERROR_THRESHOLD):
rolled_back = True
rollback_step = step
current_weight = 0.0
return weights, error_rates, rollback_step
steps = np.arange(STEPS)
w_ok, e_ok, rb_ok = simulate_canary()
w_bug, e_bug, rb_bug = simulate_canary(bug_start_step=18, bug_severity=0.18)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
fig.suptitle("Simulation canary : nominal vs rollback automatique", fontsize=13, fontweight="bold")
ax1.set_title("Déploiement nominal — promotion réussie", fontsize=11)
ax1.fill_between(steps, w_ok, alpha=0.25, color="#4C72B0", label="Poids canary")
ax1.plot(steps, w_ok, color="#4C72B0", lw=2)
ax1.fill_between(steps, e_ok, alpha=0.35, color="#C44E52", label="Taux d'erreur")
ax1.plot(steps, e_ok, color="#C44E52", lw=1.5)
ax1.axhline(y=ERROR_THRESHOLD, color="#C44E52",
linestyle="--", lw=1.2, alpha=0.7,
label=f"Seuil rollback ({ERROR_THRESHOLD*100:.0f} %)")
ax1.set_ylabel("Fraction")
ax1.set_ylim(0, 1.1)
ax1.legend(loc="upper left", fontsize=9)
ax2.set_title("Déploiement avec bug — rollback automatique déclenché", fontsize=11)
ax2.fill_between(steps, w_bug, alpha=0.25, color="#4C72B0", label="Poids canary")
ax2.plot(steps, w_bug, color="#4C72B0", lw=2)
ax2.fill_between(steps, e_bug, alpha=0.35, color="#C44E52", label="Taux d'erreur")
ax2.plot(steps, e_bug, color="#C44E52", lw=1.5)
ax2.axhline(y=ERROR_THRESHOLD, color="#C44E52",
linestyle="--", lw=1.2, alpha=0.7,
label=f"Seuil rollback ({ERROR_THRESHOLD*100:.0f} %)")
if rb_bug:
ax2.axvline(x=rb_bug, color="#DD8452", lw=2, linestyle="-",
label=f"Rollback déclenché (step {rb_bug})")
ax2.text(rb_bug + 0.5, 0.95, f"Rollback\nstep {rb_bug}",
fontsize=9, color="#DD8452", va="top")
ax2.set_xlabel("Palier de déploiement (×2 min)")
ax2.set_ylabel("Fraction")
ax2.set_ylim(0, 1.1)
ax2.legend(loc="upper left", fontsize=9)
plt.savefig("_static/10_canary_rollback.png", dpi=120, bbox_inches="tight")
plt.show()
if rb_bug:
exposition = sum(w_bug[:rb_bug]) / len(w_bug)
print(f"Exposition cumulée au canary défaillant : {exposition*100:.1f} % du trafic total")
print(f"Rollback déclenché à l'étape {rb_bug} sur {STEPS}")
Exposition cumulée au canary défaillant : 0.4 % du trafic total
Rollback déclenché à l'étape 3 sur 40
Résumé#
Le rolling update est la stratégie par défaut : simple, supportée nativement par Kubernetes, sans coût d’infrastructure supplémentaire — mais les versions coexistent pendant la mise à jour.
Les paramètres
maxSurgeetmaxUnavailabledéfinissent le compromis entre vitesse de déploiement et disponibilité ; les readiness probes sont indispensables pour garantir ce contrat.Le blue-green offre un rollback instantané et une bascule atomique, au prix d’un doublement de l’infrastructure pendant la transition.
Le canary expose progressivement le risque (5 % → 25 % → 100 %) et permet de valider le comportement sur du trafic réel avec un impact limité en cas de régression.
Le shadow deployment est la stratégie la plus sûre pour valider des changements profonds : aucun utilisateur n’est exposé, mais les effets de bord doivent être neutralisés côté shadow.
L’A/B testing et le canary répondent à des objectifs différents : stabilité technique pour le canary, comportement utilisateur pour l’A/B — les feature flags sont le mécanisme commun.
Le rollback automatique sur SLO breach transforme le canary en filet de sécurité actif : dès que le taux d’erreur dépasse le seuil, le poids canary revient à zéro sans intervention humaine.
Argo Rollouts est le contrôleur Kubernetes de référence pour les stratégies canary et blue-green avancées avec analyse de métriques intégrée.
Le problème de compatibilité des migrations de BDD est transversal à toutes les stratégies : le pattern expand/contract et les migrations réversibles sont non négociables.
Le choix de stratégie dépend de trois axes : tolérance au risque, budget d’infrastructure et exigences de rollback — l’arbre de décision synthétise ces critères.