Aller au contenu

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

Analyse & auto-remédiation


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