Aller au contenu

Progressive delivery : canary et blue-green avec Argo Rollouts

Objectifs pédagogiques

À la fin de ce TP, vous devez être capables de :

  • installer Argo Rollouts et son plugin kubectl en versions épinglées et vérifiées
  • livrer une nouvelle version en canary, par paliers, avec une validation manuelle
  • faire annuler automatiquement une release défectueuse par une analyse, sans intervention humaine
  • livrer en blue-green avec un Service de preview et une analyse avant bascule
  • reconstituer l'historique des livraisons à partir des traces du cluster
  • conditionner un job GitLab CI à l'issue de la livraison progressive, avec des droits en lecture seule

Prérequis

  • Docker, kind v0.33.0 et kubectl 1.36 ou plus récent (le cluster du TP est en Kubernetes 1.37)
  • 4 Go de mémoire disponibles pour Docker et un accès à Docker Hub, quay.io et GitHub
  • Les cours GitOps avec Argo CD et Helm

Testé

Ce TP a été joué de bout en bout sur un cluster kind v0.33.0 (Kubernetes v1.37.0) avec Argo Rollouts v1.10.1, son plugin kubectl v1.10.1 et les images argoproj/rollouts-demo et curlimages/curl:8.23.0. Pour l'atelier 6, le script d'attente a tourné avec le kubeconfig en lecture seule, et le before_script du job (outils épinglés, empreintes vérifiées, yq) dans un conteneur alpine:3.22 relié au cluster. Le job lui-même, avec son git push vers le dépôt GitOps, n'a pas été exécuté dans GitLab.

Un cluster jetable, un kubeconfig isolé

Toutes les commandes utilisent un fichier kubeconfig propre au TP, désigné par la variable KUBECONFIG. Votre ~/.kube/config et vos autres clusters ne sont jamais modifiés. Dans chaque nouveau terminal, replacez-vous dans le dossier du TP et réexportez KUBECONFIG et PATH.

Contexte

L'audit de SecurePay relève qu'une release défectueuse est restée 40 minutes en production : le RollingUpdate s'est terminé avec des probes au vert, puis une partie des paiements a échoué jusqu'à ce qu'un client alerte le support. Vous préparez la livraison progressive de l'API : exposer chaque nouvelle version à une fraction du trafic, la mesurer, et l'annuler automatiquement si elle dégrade le service.

Le TP utilise l'application de démonstration argoproj/rollouts-demo, qui répond sur /color avec sa couleur. Ses variantes bad-* renvoient environ 85 % d'erreurs HTTP 500 sur /color, tout en restant prêtes au sens de la readinessProbe : c'est exactement la situation de l'incident.

Atelier 1 : Cluster jetable et installation

Travail demandé

  1. Créez le dossier du TP et le cluster, avec un kubeconfig dédié :

    mkdir -p ~/tp-rollouts && cd ~/tp-rollouts
    export KUBECONFIG="$PWD/kubeconfig"
    kind create cluster --name rollouts-tp --kubeconfig "$KUBECONFIG" \
      --image kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
    
  2. Installez le contrôleur Argo Rollouts v1.10.1 :

    kubectl create namespace argo-rollouts
    kubectl apply --server-side -n argo-rollouts \
      -f https://github.com/argoproj/argo-rollouts/releases/download/v1.10.1/install.yaml
    kubectl -n argo-rollouts rollout status deployment/argo-rollouts --timeout=180s
    

    Pourquoi --server-side

    Avec un kubectl apply classique, les CRD rollouts et analysisruns sont refusées (metadata.annotations: Too long: may not be more than 262144 bytes) : l'annotation last-applied-configuration dépasse la limite. L'application côté serveur ne pose pas cette annotation.

  3. Installez le plugin kubectl dans le dossier du TP, après vérification de son empreinte. Choisissez la plateforme parmi linux-amd64, linux-arm64, darwin-amd64 et darwin-arm64 :

    VERSION=v1.10.1
    BIN=kubectl-argo-rollouts-linux-amd64
    curl -fsSLO "https://github.com/argoproj/argo-rollouts/releases/download/${VERSION}/${BIN}"
    curl -fsSLO "https://github.com/argoproj/argo-rollouts/releases/download/${VERSION}/argo-rollouts-checksums.txt"
    grep " ${BIN}$" argo-rollouts-checksums.txt | sha256sum -c -    # macOS : shasum -a 256 -c -
    mkdir -p bin && mv "$BIN" bin/kubectl-argo-rollouts && chmod +x bin/kubectl-argo-rollouts
    export PATH="$PWD/bin:$PATH"
    kubectl argo rollouts version
    
  4. Créez le namespace de travail et un pod client, d'où vous enverrez les requêtes :

    kubectl create namespace demo
    kubectl run client -n demo --image=curlimages/curl:8.23.0 --command -- sleep infinity
    kubectl wait -n demo --for=condition=Ready pod/client --timeout=120s
    

Ce que vous devez observer

  • sha256sum affiche OK pour le binaire téléchargé
  • kubectl argo rollouts version affiche kubectl-argo-rollouts: v1.10.1+356117c
  • kubectl get crd | grep argoproj liste cinq CRD, dont rollouts.argoproj.io et analysisruns.argoproj.io

Critère de réussite

Le contrôleur tourne, le plugin répond, et aucune commande n'a touché votre configuration kubectl habituelle.

Atelier 2 : Un premier canary, validé à la main

Travail demandé

  1. Créez services.yaml. Le Service rollouts-demo sert les utilisateurs et sélectionne toutes les versions ; le Service rollouts-demo-canary est géré par le contrôleur et ne cible que les pods canary :

    apiVersion: v1
    kind: Service
    metadata: {name: rollouts-demo, namespace: demo}
    spec:
      selector: {app: rollouts-demo}
      ports: [{name: http, port: 80, targetPort: http}]
    ---
    apiVersion: v1
    kind: Service
    metadata: {name: rollouts-demo-canary, namespace: demo}
    spec:
      selector: {app: rollouts-demo}
      ports: [{name: http, port: 80, targetPort: http}]
    
  2. Créez rollout-canary.yaml :

    apiVersion: argoproj.io/v1alpha1
    kind: Rollout
    metadata: {name: rollouts-demo, namespace: demo}
    spec:
      replicas: 5
      revisionHistoryLimit: 5
      selector:
        matchLabels: {app: rollouts-demo}
      template:
        metadata:
          labels: {app: rollouts-demo}
        spec:
          containers:
            - name: rollouts-demo
              image: argoproj/rollouts-demo:blue
              ports: [{name: http, containerPort: 8080}]
              readinessProbe: {httpGet: {path: /, port: http}}   # ne teste pas /color
              resources: {requests: {cpu: 10m, memory: 32Mi}, limits: {memory: 64Mi}}
      strategy:
        canary:
          canaryService: rollouts-demo-canary
          steps:
            - setWeight: 20
            - pause: {}
            - setWeight: 40
            - pause: {duration: 10s}
            - setWeight: 60
            - pause: {duration: 10s}
            - setWeight: 80
            - pause: {duration: 10s}
    
  3. Vérifiez le manifeste, appliquez-le et attendez la première version :

    kubectl argo rollouts lint -f rollout-canary.yaml
    kubectl apply -f services.yaml -f rollout-canary.yaml
    kubectl argo rollouts status rollouts-demo -n demo --timeout 3m
    
  4. Livrez la version yellow. Le fichier reste la référence, comme le dépôt GitOps le sera en production : modifiez l'image dans le fichier (à la main ou avec sed), puis appliquez-le :

    sed -i.bak 's#rollouts-demo:blue#rollouts-demo:yellow#' rollout-canary.yaml
    kubectl apply -f rollout-canary.yaml
    kubectl argo rollouts get rollout rollouts-demo -n demo
    
  5. Mesurez la répartition du trafic vu par les utilisateurs, puis validez la suite de la livraison :

    kubectl exec -n demo client -- sh -c \
      'for i in $(seq 100); do curl -s http://rollouts-demo/color; echo; done | sort | uniq -c'
    kubectl argo rollouts promote rollouts-demo -n demo
    kubectl argo rollouts status rollouts-demo -n demo --timeout 3m
    
  6. Facultatif : lancez kubectl argo rollouts dashboard dans un second terminal et ouvrez http://localhost:3100/rollouts pour suivre la livraison suivante.

Ce que vous devez observer

  • La création initiale passe directement à 100 % : il n'y a pas de version précédente à comparer
  • Après l'application de yellow, le Rollout est Paused (CanaryPauseStep), à l'étape 1/8, avec 1 pod canary sur 5 :
Status:          ॥ Paused
Message:         CanaryPauseStep
Strategy:        Canary
  Step:          1/8
  SetWeight:     20
  ActualWeight:  20
Images:          argoproj/rollouts-demo:blue (stable)
                 argoproj/rollouts-demo:yellow (canary)
  • La boucle curl renvoie environ 80 "blue" et 20 "yellow" : le poids est approché par le nombre de pods
  • kubectl get svc rollouts-demo-canary -n demo -o jsonpath='{.spec.selector}' montre le label rollouts-pod-template-hash ajouté par le contrôleur
  • Après promote, les paliers 40, 60 et 80 % s'enchaînent toutes les 10 s, puis le Rollout est Healthy avec yellow en stable

Critère de réussite

Une nouvelle version a été exposée à 20 % des requêtes, puis promue jusqu'à 100 % par une décision explicite.

Atelier 3 : Une analyse qui annule la release défectueuse

Contexte

Une pause manuelle ne vaut que si quelqu'un regarde. Vous remplacez la décision humaine par une mesure : un Job envoie 50 requêtes aux seuls pods canary et échoue si moins de 95 % répondent 200.

Travail demandé

  1. Créez analysis-template.yaml :

    apiVersion: argoproj.io/v1alpha1
    kind: AnalysisTemplate
    metadata:
      name: http-success-rate
      namespace: demo
    spec:
      args:
        - name: service
      metrics:
        - name: http-success-rate
          initialDelay: 10s     # laisse au Service le temps de cibler les nouveaux pods
          interval: 10s
          count: 3
          failureLimit: 1       # un échec toléré, le deuxième annule la release
          provider:
            job:
              spec:
                backoffLimit: 0
                ttlSecondsAfterFinished: 600
                template:
                  spec:
                    restartPolicy: Never
                    containers:
                      - name: probe
                        image: curlimages/curl:8.23.0
                        command: [sh, -c]
                        args:
                          - |
                            total=50; ok=0
                            for i in $(seq $total); do
                              code=$(curl -s -o /dev/null -w '%{http_code}' http://{{args.service}}/color)
                              [ "$code" = "200" ] && ok=$((ok + 1))
                            done
                            echo "{{args.service}} : $ok/$total réponses 200"
                            [ $((ok * 100 / total)) -ge 95 ]
    
  2. Dans rollout-canary.yaml, remplacez le bloc steps par :

          steps:
            - setWeight: 20
            - analysis:
                templates:
                  - templateName: http-success-rate
                args:
                  - name: service
                    value: rollouts-demo-canary
            - setWeight: 60
            - pause: {duration: 10s}
    
  3. Appliquez les deux fichiers. Seule la stratégie change : aucune nouvelle révision n'est créée.

    kubectl apply -f analysis-template.yaml -f rollout-canary.yaml
    
  4. Livrez la version défectueuse bad-red et attendez l'issue :

    sed -i.bak 's#rollouts-demo:yellow#rollouts-demo:bad-red#' rollout-canary.yaml
    kubectl apply -f rollout-canary.yaml
    kubectl argo rollouts status rollouts-demo -n demo --timeout 5m; echo "code de sortie : $?"
    
  5. Identifiez la cause et vérifiez ce que voient les utilisateurs :

    kubectl argo rollouts get rollout rollouts-demo -n demo
    kubectl get analysisrun -n demo
    kubectl logs -n demo -l analysisrun.argoproj.io/uid --prefix
    kubectl exec -n demo client -- sh -c \
      'for i in $(seq 20); do curl -s http://rollouts-demo/color; echo; done | sort | uniq -c'
    
  6. Revenez à un état désiré cohérent, comme le ferait une MR de revert, puis livrez une version saine :

    sed -i.bak 's#rollouts-demo:bad-red#rollouts-demo:yellow#' rollout-canary.yaml
    kubectl apply -f rollout-canary.yaml
    kubectl argo rollouts status rollouts-demo -n demo --timeout 1m
    sed -i.bak 's#rollouts-demo:yellow#rollouts-demo:green#' rollout-canary.yaml
    kubectl apply -f rollout-canary.yaml
    kubectl argo rollouts status rollouts-demo -n demo --timeout 5m
    

Ce que vous devez observer

  • Une trentaine de secondes après l'application de bad-red, le Rollout est annulé et status sort en erreur :
Degraded - 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)
code de sortie : 1
  • L'arbre montre l'AnalysisRun Failed et ses deux Jobs en échec ; les journaux des Jobs donnent le taux mesuré, par exemple rollouts-demo-canary : 8/50 réponses 200
  • Le canary est réduit à 0 et les utilisateurs ne reçoivent plus que "yellow", sans aucune intervention
  • Le retour à yellow rend le Rollout Healthy en quelques secondes, sans rejouer ni les paliers ni l'analyse : le contrôleur réutilise le ReplicaSet stable, qui prend le numéro de révision 4
  • La version green franchit l'analyse (trois Jobs Successful, 50/50 réponses 200) puis atteint 100 %

Critère de réussite

Une release défectueuse est annulée automatiquement, et vous savez dire, à partir du message d'abort et de l'AnalysisRun, quelle métrique a échoué et avec quelle valeur.

Atelier 4 : Blue-green avec preview et analyse avant bascule

Travail demandé

  1. Créez bluegreen.yaml : deux Services, bg-active pour la production et bg-preview pour la version candidate, et un second Rollout :

    apiVersion: v1
    kind: Service
    metadata: {name: bg-active, namespace: demo}
    spec:
      selector: {app: rollouts-demo-bg}
      ports: [{name: http, port: 80, targetPort: http}]
    ---
    apiVersion: v1
    kind: Service
    metadata: {name: bg-preview, namespace: demo}
    spec:
      selector: {app: rollouts-demo-bg}
      ports: [{name: http, port: 80, targetPort: http}]
    ---
    apiVersion: argoproj.io/v1alpha1
    kind: Rollout
    metadata: {name: rollouts-demo-bg, namespace: demo}
    spec:
      replicas: 3
      revisionHistoryLimit: 5
      selector:
        matchLabels: {app: rollouts-demo-bg}
      template:
        metadata:
          labels: {app: rollouts-demo-bg}
        spec:
          containers:
            - name: rollouts-demo
              image: argoproj/rollouts-demo:blue
              ports: [{name: http, containerPort: 8080}]
              readinessProbe: {httpGet: {path: /, port: http}}
              resources: {requests: {cpu: 10m, memory: 32Mi}, limits: {memory: 64Mi}}
      strategy:
        blueGreen:
          activeService: bg-active
          previewService: bg-preview
          autoPromotionEnabled: false
          scaleDownDelaySeconds: 30
          prePromotionAnalysis:
            templates:
              - templateName: http-success-rate
            args:
              - name: service
                value: bg-preview
    
  2. Appliquez-le, puis livrez la version green et comparez les deux Services une fois l'analyse terminée :

    kubectl apply -f bluegreen.yaml
    kubectl argo rollouts status rollouts-demo-bg -n demo --timeout 2m
    sed -i.bak 's#rollouts-demo:blue#rollouts-demo:green#' bluegreen.yaml
    kubectl apply -f bluegreen.yaml
    kubectl argo rollouts get rollout rollouts-demo-bg -n demo --watch    # Ctrl+C une fois en pause
    kubectl exec -n demo client -- sh -c \
      'for s in bg-active bg-preview; do echo "$s : $(curl -s http://$s/color)"; done'
    
  3. Promouvez, puis observez l'ancienne version pendant 30 s :

    kubectl argo rollouts promote rollouts-demo-bg -n demo
    kubectl argo rollouts get rollout rollouts-demo-bg -n demo
    
  4. Livrez bad-red, et tentez de forcer la bascule pendant l'analyse :

    sed -i.bak 's#rollouts-demo:green#rollouts-demo:bad-red#' bluegreen.yaml
    kubectl apply -f bluegreen.yaml
    sleep 10 && kubectl argo rollouts promote rollouts-demo-bg -n demo
    kubectl argo rollouts status rollouts-demo-bg -n demo --timeout 3m; echo "code de sortie : $?"
    kubectl exec -n demo client -- sh -c 'curl -s http://bg-active/color; echo'
    
  5. Revenez à green dans le fichier et appliquez-le.

Ce que vous devez observer

  • Après l'application de green, le Rollout est Paused (BlueGreenPause) : bg-active répond "blue", bg-preview répond "green", et l'AnalysisRun ...-pre est Successful
  • Après promote, bg-active sélectionne le nouveau ReplicaSet ; l'ancien reste affiché delay:29s, puis ScaledDown
  • Avec bad-red, l'analyse sur bg-preview échoue : Blue/green pre-promotion analysis phase error/failed. Le promote lancé pendant l'analyse n'a pas forcé la bascule : bg-active répond toujours "green"

Pour aller plus loin

Remplacez prePromotionAnalysis par postPromotionAnalysis (avec value: bg-active) et passez autoPromotionEnabled à true, puis livrez bad-red. La bascule a lieu, l'analyse échoue sur bg-active, et le contrôleur rebascule bg-active sur l'ancienne version, encore présente grâce à scaleDownDelaySeconds : lors du test, le retour a eu lieu en une trentaine de secondes.

Critère de réussite

Une version candidate est validée sur le Service de preview avant d'être exposée, et une version défectueuse n'atteint jamais les utilisateurs.

Atelier 5 : Reconstituer l'historique des livraisons

Contexte

Après un incident, l'auditeur demande : quelles versions ont été livrées, lesquelles ont été annulées, pourquoi, et quand ? Vous répondez avec les seules traces du cluster.

Travail demandé

  1. Listez les révisions, avec leur image :

    kubectl get rs -n demo -l app=rollouts-demo -o custom-columns=\
    'REVISION:.metadata.annotations.rollout\.argoproj\.io/revision,IMAGE:.spec.template.spec.containers[0].image,REPLICAS:.spec.replicas,CREATION:.metadata.creationTimestamp'
    
  2. Listez les analyses et leur motif d'échec :

    kubectl get analysisrun -n demo -o custom-columns=\
    'NOM:.metadata.name,ROLLOUT:.metadata.ownerReferences[0].name,PHASE:.status.phase,MESSAGE:.status.message'
    
  3. Extrayez les événements d'avertissement des Rollouts, puis archivez tous les événements du namespace :

    kubectl get events -n demo --field-selector type=Warning,involvedObject.kind=Rollout \
      -o custom-columns='HEURE:.lastTimestamp,OBJET:.involvedObject.name,RAISON:.reason,MESSAGE:.message'
    mkdir -p preuves && kubectl get events -n demo -o yaml > "preuves/events-$(date +%Y%m%d-%H%M).yaml"
    
  4. Remplissez un journal des livraisons : date, Rollout, révision, image, stratégie, résultat (promue, annulée), motif, preuve.

Ce que vous devez observer

  • Chaque image livrée correspond à un ReplicaSet portant un numéro de révision ; yellow, revenue après l'abort, porte un numéro plus récent que bad-red
  • Chaque abort apparaît trois fois : message de l'AnalysisRun, événement AnalysisRunFailed, événement RolloutAborted
  • Les événements Kubernetes expirent au bout d'une heure par défaut : sans archivage, la chronologie fine est perdue

Critère de réussite

Votre journal permet à un tiers de retrouver, pour chaque livraison, la version, la décision et sa justification, sans accès au cluster.

Atelier 6 : Conditionner un job GitLab CI à l'issue du Rollout

Contexte

La pipeline ne déploie pas directement : elle écrit la nouvelle image dans le dépôt GitOps, Argo CD synchronise, puis Argo Rollouts conduit la livraison. Pour que la pipeline reflète le vrai résultat, un job attend l'issue du Rollout. Ce job n'a besoin que de lire le cluster.

Travail demandé

  1. Créez ci-reader.yaml : un ServiceAccount en lecture seule, avec exactement les droits dont le plugin a besoin.

    apiVersion: v1
    kind: ServiceAccount
    metadata: {name: ci-rollout-reader, namespace: demo}
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata: {name: ci-rollout-reader, namespace: demo}
    rules:
      - apiGroups: ["argoproj.io"]
        resources: ["rollouts", "analysisruns", "experiments"]
        verbs: ["get", "list", "watch"]
      - apiGroups: ["apps"]
        resources: ["replicasets", "deployments"]
        verbs: ["get", "list", "watch"]
      - apiGroups: [""]
        resources: ["pods"]
        verbs: ["get", "list", "watch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata: {name: ci-rollout-reader, namespace: demo}
    subjects: [{kind: ServiceAccount, name: ci-rollout-reader, namespace: demo}]
    roleRef: {apiGroup: rbac.authorization.k8s.io, kind: Role, name: ci-rollout-reader}
    
  2. Fabriquez le kubeconfig que recevrait la CI, avec un jeton de courte durée :

    kubectl apply -f ci-reader.yaml
    TOKEN=$(kubectl create token ci-rollout-reader -n demo --duration=1h)
    SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
    kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' \
      | base64 -d > ca.crt
    export CI_KUBECONFIG="$PWD/kubeconfig-ci"
    kubectl --kubeconfig "$CI_KUBECONFIG" config set-cluster tp --server="$SERVER" \
      --certificate-authority=ca.crt --embed-certs
    kubectl --kubeconfig "$CI_KUBECONFIG" config set-credentials ci --token="$TOKEN"
    kubectl --kubeconfig "$CI_KUBECONFIG" config set-context ci --cluster=tp --user=ci --namespace=demo
    kubectl --kubeconfig "$CI_KUBECONFIG" config use-context ci
    kubectl --kubeconfig "$CI_KUBECONFIG" auth can-i patch rollouts
    
  3. Créez wait-rollout.sh. Il attend d'abord que la nouvelle image soit appliquée (la synchronisation Argo CD prend jusqu'à quelques minutes), puis l'issue de la livraison :

    #!/bin/sh
    # Usage : wait-rollout.sh <namespace> <rollout> <image attendue>
    set -eu
    NS="$1"; ROLLOUT="$2"; IMAGE="$3"
    for i in $(seq 1 60); do
      LIVE=$(kubectl get rollout "$ROLLOUT" -n "$NS" \
        -o jsonpath='{.spec.template.spec.containers[0].image}')
      [ "$LIVE" = "$IMAGE" ] && break
      echo "En attente de la synchronisation (image actuelle : $LIVE)"; sleep 10
    done
    [ "$LIVE" = "$IMAGE" ] || { echo "Image $IMAGE jamais appliquée"; exit 1; }
    kubectl argo rollouts status "$ROLLOUT" -n "$NS" --timeout 10m
    
  4. Simulez la pipeline. Le script tourne avec le kubeconfig de la CI ; une commande en arrière-plan, avec vos droits d'administrateur, joue le rôle d'Argo CD et applique la release 20 s plus tard :

    chmod +x wait-rollout.sh
    sed -i.bak 's#rollouts-demo:green#rollouts-demo:purple#' rollout-canary.yaml
    (sleep 20; kubectl apply -f rollout-canary.yaml) &
    KUBECONFIG="$CI_KUBECONFIG" ./wait-rollout.sh demo rollouts-demo argoproj/rollouts-demo:purple
    echo "code de sortie : $?"
    sed -i.bak 's#rollouts-demo:purple#rollouts-demo:bad-orange#' rollout-canary.yaml
    (sleep 20; kubectl apply -f rollout-canary.yaml) &
    KUBECONFIG="$CI_KUBECONFIG" ./wait-rollout.sh demo rollouts-demo argoproj/rollouts-demo:bad-orange
    echo "code de sortie : $?"
    
  5. Transposez dans GitLab CI. Le job écrit le digest dans le dépôt GitOps, puis attend le Rollout ; il est enregistré comme déploiement de l'environnement production.

    deploy:production:
      stage: deploy
      image: alpine:3.22
      resource_group: production            # une seule livraison à la fois
      environment:
        name: production
        url: https://api.securepay.local
      needs:
        - job: build                        # fournit IMAGE_DIGEST par un rapport dotenv
          artifacts: true
      rules:
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
          when: manual
      variables:
        KUBECTL_VERSION: v1.35.10
        ROLLOUTS_VERSION: v1.10.1
      before_script:
        - apk add --no-cache curl git yq-go
        - curl -fsSLO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
        - curl -fsSLO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256"
        - echo "$(cat kubectl.sha256)  kubectl" | sha256sum -c -
        - install -m 0755 kubectl /usr/local/bin/kubectl
        - BIN=kubectl-argo-rollouts-linux-amd64
        - curl -fsSLO "https://github.com/argoproj/argo-rollouts/releases/download/${ROLLOUTS_VERSION}/${BIN}"
        - curl -fsSLO "https://github.com/argoproj/argo-rollouts/releases/download/${ROLLOUTS_VERSION}/argo-rollouts-checksums.txt"
        - grep " ${BIN}$" argo-rollouts-checksums.txt | sha256sum -c -
        - install -m 0755 "$BIN" /usr/local/bin/kubectl-argo-rollouts
      script:
        # 1. Écrire le digest dans le dépôt GitOps (jeton d'accès au projet, portée write_repository)
        - git clone "https://oauth2:${GITOPS_TOKEN}@gitlab.com/<groupe>/securepay-platform.git" gitops
        - DIGEST="$IMAGE_DIGEST" yq -i '.image.digest = strenv(DIGEST)' gitops/charts/securepay-api/values-production.yaml
        - git -C gitops -c user.name="gitlab-ci" -c user.email="[email protected]"
            commit -am "deploy: securepay-api ${IMAGE_DIGEST} (pipeline ${CI_PIPELINE_ID})"
        - git -C gitops push origin HEAD:main
        # 2. Attendre l'issue du Rollout, avec le kubeconfig en lecture seule (variable de type File)
        - export KUBECONFIG="$KUBECONFIG_READONLY"
        - sh ci/wait-rollout.sh securepay securepay-api "${CI_REGISTRY_IMAGE}@${IMAGE_DIGEST}"
    

    Production et revue des changements

    Pousser directement sur la branche principale du dépôt GitOps contourne la revue. Pour la production de SecurePay, la modification passe par la MR de promotion du cours multi-environnements GitOps : le même script d'attente s'exécute alors dans la pipeline du dépôt GitOps, après la fusion.

Ce que vous devez observer

  • auth can-i patch rollouts répond no : la CI ne peut ni promouvoir, ni annuler, ni modifier
  • Le script affiche quelques lignes En attente de la synchronisation, puis suit la livraison : code de sortie 0 pour purple, 1 pour bad-orange avec le message RolloutAborted
  • Si vous retirez une ressource du Role (par exemple pods), status affiche des erreurs forbidden et ne rend plus la main, même au-delà de son --timeout : seul le délai maximal du job GitLab l'interromprait. C'est pour cela que le Role les liste toutes

Critère de réussite

La pipeline échoue quand la livraison progressive échoue, sans jamais disposer de droits d'écriture sur le cluster, et chaque exécution laisse une entrée dans l'historique de l'environnement GitLab.

Adaptation SecurePay (optionnel)

Ces étapes décrivent la conversion du chart charts/securepay-api du dépôt securepay-platform, sans la réaliser dans ce TP.

  1. Activez Argo Rollouts : déplacez apps/catalog/argo-rollouts.yaml (chart 2.43.8, Argo Rollouts v1.10.1) dans apps/children/.
  2. Renommez templates/deployment.yaml en templates/rollout.yaml : apiVersion: argoproj.io/v1alpha1, kind: Rollout, pod template inchangé, et une strategy lue dans les valeurs ({{- toYaml .Values.rollout.strategy | nindent 4 }}).
  3. Passez replicaCount de 1 à 5 au moins : avec un seul pod, aucun palier intermédiaire n'est possible. La même correction supprime le point de défaillance unique relevé par l'audit.
  4. Ajoutez un Service securepay-api-canary sur le modèle de templates/service.yaml. Pour des poids exacts, le cluster k3d de SecurePay embarque Traefik, routeur supporté nativement (trafficRouting.traefik.weightedTraefikServiceName).
  5. Reprenez l'AnalysisTemplate Prometheus du TP d'auto-remédiation, et ajoutez une analyse web sur /health, qui renvoie {"status": "UP", ...}.
  6. Si un HPA existe, faites pointer son scaleTargetRef vers le Rollout, et ignorez /spec/replicas dans l'Application Argo CD.
  7. Alternative sans réécriture du template : un Rollout avec spec.workloadRef vers le Deployment existant et scaleDown: progressively.
  8. Rangez dans docs/preuves/ la sortie de kubectl argo rollouts get rollout lors d'un abort, et renseignez la fiche correspondante de docs/resilience.md.

Dépannage

Symptôme Cause probable Correction
metadata.annotations: Too long à l'installation kubectl apply côté client relancer avec --server-side
unknown command "argo" for "kubectl" plugin absent du PATH export PATH="$PWD/bin:$PATH" dans le dossier du TP
Le Rollout reste Paused étape pause: {} kubectl argo rollouts promote
status attend jusqu'au délai Rollout en pause indéfinie normal : status ne rend la main que sur Healthy ou Degraded
AnalysisRun en Error plutôt qu'en Failed mesure impossible : nom de Service erroné, image du Job introuvable kubectl describe analysisrun, puis les journaux du Job
Jobs d'analyse en ImagePullBackOff quota Docker Hub, accès réseau kubectl describe pod, réessayer, ou précharger avec kind load docker-image
Première mesure incohérente Service canary pas encore à jour augmenter initialDelay
status affiche des erreurs forbidden et ne termine pas, même après --timeout Role incomplet ajouter pods, replicasets, deployments, experiments en lecture
Pods Pending mémoire insuffisante du nœud kind réduire replicas, libérer de la mémoire pour Docker

Nettoyage

kind delete cluster --name rollouts-tp --kubeconfig "$KUBECONFIG"

Questions de synthèse

  • Pourquoi la readinessProbe n'a-t-elle pas empêché l'incident SecurePay, ni la version bad-red du TP ?
  • Comment choisir entre canary et blue-green pour l'API de paiement, et pour un back-office peu sollicité ?
  • Que mesure exactement l'analyse du TP, et pourquoi cible-t-elle le Service canary plutôt que le Service utilisateurs ?
  • Après un abort, pourquoi faut-il un revert dans Git plutôt qu'un kubectl argo rollouts undo ?
  • Quelles traces permettent de prouver, un mois plus tard, qu'une release défectueuse a été annulée automatiquement ?
  • Pourquoi le job CI d'attente doit-il d'abord vérifier l'image appliquée avant d'appeler kubectl argo rollouts status ?