Progressive delivery : Canary et Blue-Green avec Argo Rollouts
Objectifs pédagogiques
- Expliquer pourquoi un RollingUpdate ne protège pas d'une release défectueuse
- Concevoir une stratégie canary ou blue-green adaptée au service et à la plateforme
- Traduire un SLO en critère de promotion avec AnalysisTemplate et AnalysisRun
- Faire interrompre automatiquement une release défaillante et identifier le composant en cause
- Articuler Argo Rollouts, Argo CD et GitLab CI sans perdre Git comme source de vérité
- Produire la trace d'audit de chaque déploiement : qui, quoi, quand, avec quel résultat
Pourquoi livrer progressivement
L'incident SecurePay
Constat de l'audit : une release défectueuse est restée 40 minutes en production, sans rollback automatique
T0 fusion de la MR, Argo CD synchronise, RollingUpdate terminé, probes au vert
T0+3 min une partie des paiements échoue en HTTP 500
T0+25 min l'astreinte est alertée par le support client
T0+40 min revert, synchronisation, retour à la normale
Impact d'une release défectueuse ≈ part du trafic exposé × durée d'exposition
| RollingUpdate | Canary avec analyse |
| Trafic exposé | 100 % | 20 % |
| Détection et retour arrière | humains, 40 min | automatiques, 1 à 2 min |
| Impact | 100 % × 40 min | 20 % × 2 min, soit 100 fois moins |
- Chronologie reconstituée à but pédagogique
- Indicateurs DORA en jeu : Change Failure Rate et Failed Deployment Recovery Time
Les limites du RollingUpdate
maxSurge et maxUnavailable règlent la vitesse du remplacement, pas l'exposition - Aucune pause entre deux paliers, aucun critère métier : seules les probes décident
- Une
readinessProbe au vert ne dit rien du taux d'erreur des paiements progressDeadlineSeconds marque le déploiement en échec, sans revenir en arrière - Le retour arrière (
kubectl rollout undo ou revert Git) attend qu'un humain détecte le problème - La livraison progressive ne réduit pas le nombre de releases défectueuses, elle réduit leur coût
Argo Rollouts
Architecture
Dépôt GitOps ──► Argo CD ──► Rollout (CRD)
│
contrôleur argo-rollouts
┌──────────────┬──────────┴───────┬────────────────────┐
▼ ▼ ▼ ▼
ReplicaSet ReplicaSet AnalysisRun Routeur de trafic
stable canary / preview Prometheus, web, (optionnel) Gateway API,
Job Istio, Traefik
- CRD :
Rollout, AnalysisTemplate, ClusterAnalysisTemplate, AnalysisRun, Experiment - Plugin
kubectl argo rollouts : get, set image, promote, abort, retry, status, lint - Dashboard :
kubectl argo rollouts dashboard, et l'arbre du Rollout dans l'interface Argo CD - Installation épinglée : chart
argo/argo-rollouts 2.43.8, soit Argo Rollouts v1.10.1
Du Deployment au Rollout
apiVersion: argoproj.io/v1alpha1 # au lieu de apps/v1
kind: Rollout # au lieu de Deployment
metadata:
name: securepay-api
spec:
replicas: 5
selector:
matchLabels:
app.kubernetes.io/name: securepay-api
template: {} # pod template inchangé
strategy:
canary: {} # ou blueGreen
- Le pod template, les probes et les ressources restent identiques
- Alternative sans réécriture :
spec.workloadRef pointe vers le Deployment existant, avec scaleDown: progressively
Canary
Le canary pas à pas
strategy:
canary:
canaryService: securepay-api-canary # ne cible que les pods canary
steps:
- setWeight: 20 # 20 % de la nouvelle version
- pause: {} # attente d'un promote manuel
- setWeight: 50
- analysis: # critère automatique, bloquant
templates:
- templateName: success-rate
- setWeight: 80
- pause: {duration: 2m} # reprise automatique
- La dernière étape est implicite : 100 %, puis la nouvelle version devient
stable - Autres étapes :
setCanaryScale, setHeaderRoute, setMirrorRoute, experiment - Analyse en arrière-plan :
strategy.canary.analysis, active pendant toutes les étapes - La première création d'un Rollout passe directement à 100 % : il n'y a rien à comparer
Par réplicas ou par routage du trafic
| Par réplicas | Avec routeur de trafic |
| Poids | approché par le nombre de pods : 1 sur 5 = 20 % | exact, indépendant du nombre de pods |
| 1 % du trafic | impossible sans 100 pods | possible |
| Routage par en-tête | non | oui, setHeaderRoute |
| Prérequis | aucun | Gateway API, Istio, Traefik, NGINX, ALB... |
trafficRouting:
plugins:
argoproj-labs/gatewayAPI: # plugin déclaré dans argo-rollouts-config
httpRoute: securepay-api # le contrôleur réécrit les poids de la route
- Le mode par réplicas suffit pour démarrer : c'est celui du TP
- Le projet ingress-nginx est en fin de vie : privilégier Gateway API pour une nouvelle plateforme
Blue-Green
Le principe
avant : Service active ──► ReplicaSet v1 (bleu) ◄── utilisateurs
Service preview ──► ReplicaSet v2 (vert) ◄── tests, analyse
promote : Service active ──► ReplicaSet v2 (vert) bascule instantanée
ensuite : ReplicaSet v1 conservé scaleDownDelaySeconds, puis réduit à 0
- Deux versions complètes coexistent : le coût en ressources double pendant la livraison
- La bascule est atomique : 0 % puis 100 % du trafic, sans palier
- Le retour arrière est immédiat tant que l'ancienne version tourne encore
La stratégie blue-green
strategy:
blueGreen:
activeService: securepay-api-active # trafic de production
previewService: securepay-api-preview # nouvelle version, avant bascule
autoPromotionEnabled: false # attend un promote après l'analyse
scaleDownDelaySeconds: 30 # ancienne version gardée 30 s
prePromotionAnalysis: # échec : la bascule n'a jamais lieu
templates:
- templateName: success-rate
args:
- name: service
value: securepay-api-preview
postPromotionAnalysis: # échec : active revient sur v1
templates:
- templateName: success-rate
- Vérifié en TP : un
promote lancé pendant l'analyse ne contourne pas son échec postPromotionAnalysis ne peut rebasculer que si v1 n'a pas encore été réduite
Canary ou blue-green ?
| Canary | Blue-Green |
| Exposition | progressive, par paliers | aucune, puis totale |
| Ressources | quelques pods en plus | double pendant la livraison |
| Validation avant exposition | sur une fraction réelle du trafic | sur le Service preview, sans client |
| Cas typique | API à fort trafic, métriques fiables | trafic faible, tests de recette |
- Dans les deux cas, deux versions tournent en même temps : le schéma de base doit rester compatible
- Migrations en expand and contract : ajouter, migrer, puis retirer dans une release ultérieure
AnalysisTemplate et AnalysisRun
- AnalysisTemplate : métriques et critères réutilisables, paramétrés par
args ; ClusterAnalysisTemplate pour tout le cluster - AnalysisRun : une exécution créée par le Rollout, qui conserve chaque mesure
| Champ | Rôle |
interval, count, initialDelay | rythme et nombre de mesures |
successCondition, failureCondition | expressions évaluées sur result |
failureLimit | échecs tolérés avant l'abort |
consecutiveErrorLimit | erreurs de mesure consécutives tolérées |
inconclusiveLimit | mesures indécises tolérées, au-delà le Rollout se met en pause |
- Un échec dit que la release est mauvaise ; une erreur dit que la mesure est impossible
- Fail-open ou fail-closed en l'absence de données : voir le cours d'auto-remédiation
Provider prometheus
args:
- name: canary-hash # valeur fournie par le Rollout
metrics:
- name: payments-success-rate
interval: 30s
count: 6
failureLimit: 1
successCondition: len(result) > 0 && result[0] >= 0.995
provider:
prometheus:
address: http://kube-prometheus-stack-prometheus.monitoring.svc:9090
query: |
sum(rate(http_requests_total{rollouts_pod_template_hash="{{args.canary-hash}}", code!~"5.."}[1m]))
/
sum(rate(http_requests_total{rollouts_pod_template_hash="{{args.canary-hash}}"}[1m]))
- Côté Rollout :
valueFrom: {podTemplateHashValue: Latest} passe le hash des pods canary - Le ServiceMonitor recopie ce label de pod (
podTargetLabels) : la requête isole le canary - Le seuil reprend le SLO : 99,5 % de paiements réussis
Providers web et job
- name: smoke-test
successCondition: result == "UP"
provider:
web:
url: "http://{{args.service}}/health"
jsonPath: "{$.status}"
---
- name: http-success-rate
provider:
job:
spec: # un Job Kubernetes : son code de sortie est le verdict
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: probe
image: curlimages/curl:8.23.0
command: [sh, -c, "... exit 1 si moins de 95 % de réponses 200"]
web : un appel HTTP et une condition sur le JSON renvoyé job : un Job réussi ou échoué ; idéal pour un test fonctionnel ou un k6 court - Autres providers : Datadog, New Relic, CloudWatch, InfluxDB, Graphite, Kayenta, plugins
L'abort automatique et le composant en cause
- Analyse en échec : canary réduit à 0, 100 % sur la version stable, Rollout
Degraded - Observé en TP : une trentaine de secondes entre l'application de la release et l'abort
- La production est saine, la livraison a échoué : c'est l'auto-remédiation sans humain
RolloutAborted: Rollout aborted update to revision 3: Step-based analysis phase
error/failed: Metric "http-success-rate" assessed Failed due to failed (2) > failureLimit (1)
kubectl argo rollouts get rollout securepay-api -n securepay # arbre, révisions, AnalysisRun
kubectl describe analysisrun <nom> -n securepay # chaque mesure et sa valeur
kubectl get events -n securepay --field-selector involvedObject.kind=Rollout
- Le message nomme la révision, la phase (step, pre, post) et la métrique en échec
- Une métrique par composant ou par SLI (
payments-success-rate, db-latency-p95) : son nom désigne le coupable
GitOps & pipeline
Argo CD et Argo Rollouts
| Argo CD | Argo Rollouts |
| applique l'état décrit dans Git | orchestre la progression de la release |
détecte la dérive, selfHeal | analyse les métriques, décide de l'abort |
- Santé vue par Argo CD : Rollout en pause =
Suspended, Rollout annulé = Degraded - Actions dans l'interface Argo CD :
resume, promote-full, abort, retry, skip-current-step - L'abort ne modifie que le statut : Argo CD reste
Synced, Git décrit toujours la release défectueuse - Retour à un état cohérent : MR de revert ; observé en TP, le Rollout redevient
Healthy sans rejouer les paliers - À proscrire :
undo ou set image en production (dérive annulée par selfHeal), retry sans correction retry se justifie quand l'échec venait de la mesure elle-même, par exemple Prometheus indisponible
La pipeline de bout en bout
MR applicative ─► build, tests, scans ─► image signée (digest)
│
job deploy ─► commit du digest dans le dépôt GitOps
│ Argo CD synchronise
▼
Rollout : 20 % ─► analyse ─► 60 % ─► 100 %, ou abort
│
job verify ─► kubectl argo rollouts status ─► vert ou rouge
│
environnement GitLab : qui, quel commit, quand, résultat
- La pipeline ne pousse rien dans le cluster : elle écrit dans Git, puis observe
- En production, le commit passe par la MR de promotion revue et approuvée
Attendre l'issue du Rollout dans GitLab CI
verify:production:
stage: verify
resource_group: production # une livraison à la fois
environment:
name: production
script:
- export KUBECONFIG="$KUBECONFIG_READONLY" # variable de type File
# 1. attendre qu'Argo CD ait appliqué la nouvelle image
- |
until [ "$(kubectl get rollout securepay-api -n securepay \
-o jsonpath='{.spec.template.spec.containers[0].image}')" = "$IMAGE" ]; do
sleep 10
done
# 2. attendre la fin de la livraison progressive
- kubectl argo rollouts status securepay-api -n securepay --timeout 15m
status sort en 0 si Healthy, en 1 si Degraded ou si le délai expire ; sans l'étape 1, il peut lire l'ancienne révision - Droits en lecture seule :
rollouts, analysisruns, experiments, replicasets, deployments, pods (sans l'un d'eux, status reste bloqué) - Une
pause: {} fait attendre status jusqu'au délai : la réserver aux livraisons hors CI
Traçabilité & audit
Qui a déployé quoi, quand, avec quel résultat ?
| Source | Ce qu'elle prouve | Rétention |
| Git : MR, commits, reverts | qui a demandé quoi, qui a approuvé | permanente |
| Environnements GitLab | quel commit et quelle pipeline, statut | permanente |
| Historique Argo CD | quelle révision Git synchronisée, quand | revisionHistoryLimit (10) |
| Révisions du Rollout | ReplicaSets et images successives | revisionHistoryLimit (10) |
| AnalysisRuns | mesures, métrique en échec, motif | analysis.*RunHistoryLimit |
| Événements Kubernetes | paliers, abort, bascules de Service | 1 h par défaut |
- Notifications Argo Rollouts :
on-rollout-aborted, on-analysis-run-failed, on-rollout-completed vers Slack ou un webhook - Les sources éphémères s'exportent (événements vers Loki, sorties archivées avec la MR de revert)
- Un abort automatique reste un incident de livraison : il donne lieu à un postmortem
Anti-patterns à proscrire
- Une analyse qui ne mesure que
/health : elle valide une release qui échoue sur le métier - Une métrique globale qui dilue le canary dans le trafic stable
- Une analyse fail-open sur un service de paiement
pause: {} partout : la livraison progressive redevient manuelle - Corriger un abort avec
kubectl au lieu d'un revert dans Git - Un job CI avec des droits d'écriture sur le cluster pour « surveiller » le déploiement
- Des migrations de schéma incompatibles avec l'ancienne version
À retenir
- La livraison progressive réduit le produit trafic exposé × durée, pas le nombre de bugs
- Canary : exposition par paliers ; blue-green : bascule atomique, retour arrière immédiat
- Une AnalysisTemplate traduit le SLO en critère de promotion, et son échec déclenche l'abort
- Le message d'abort et l'AnalysisRun désignent la métrique, donc le composant, en échec
- Git reste la source de vérité : un abort se solde par une MR de revert
- La pipeline écrit dans Git puis observe le Rollout, avec des droits en lecture seule
Pour aller plus loin