CI/CDGitOpsKubernetesIntermédiaireDéploiements

Réduire le lead time de déploiement avec le GitOps

Jerry RajaonarivoMis à jour le 30 sept. 20268 min de lecture

Des déploiements lents ne sont presque jamais un problème d'outillage. C'est un problème de confiance : les équipes ajoutent des validations manuelles parce qu'elles ne font pas confiance au pipeline pour détecter ce qu'un relecteur humain détecterait.

Le GitOps répond à ce problème en faisant du pipeline la piste d'audit. Chaque changement en production est un commit dans un dépôt Git, rattaché à une pull request relue, et le cluster est réconcilié automatiquement sur ce commit.

Où part le lead time

Le lead time, c'est le temps entre le merge d'un changement et sa mise en production. Avec un comité de changement hebdomadaire, le code est généralement prêt en quelques minutes, et l'essentiel du lead time se passe à attendre, d'abord le prochain créneau du comité, puis quelqu'un pour lancer le déploiement pendant la fenêtre de mise en production. Un changement mergé le lendemain de la réunion peut facilement attendre presque une semaine. Regrouper les changements rend aussi chaque release plus grosse et plus risquée, et les grosses releases finissent souvent par avoir des validations plus strictes, ce qui pousse les équipes à regrouper encore davantage.

Le pipeline ci-dessous supprime cette attente. Chaque merge part en production seul, et la question à laquelle répondait un approbateur humain (« est-ce que ça a l'air sain en production ? ») devient une vérification de métriques exécutée à chaque release.

Le pattern

  • Source de vérité : les manifestes applicatifs sont rangés dans un dépôt Git de configuration dédié, et non dans les scripts CI.
  • Réconciliation : Argo CD surveille le dépôt et aligne le cluster sur son contenu, donc personne ne lance de kubectl apply depuis son laptop. Flux fonctionne de la même manière ; j'utilise Argo CD ici.
  • Livraison progressive : les paliers du canary et leurs critères de réussite sont écrits sous forme de configuration dans le dépôt.
  • Rollback : le rollout s'interrompt automatiquement, et un git revert remet Git en phase avec ce qui tourne.

Étape 1 : la CI build l'image et met à jour le tag

Le workflow de release du dépôt applicatif s'exécute à chaque merge sur main. La protection de branche a déjà exigé que le job de tests passe sur la PR, donc ce workflow se contente de builder et pousser une image taguée avec le SHA du commit, puis d'écrire ce tag dans le dépôt de configuration. La revue a eu lieu sur la PR applicative, et le commit de mise à jour y renvoie via le SHA, si bien que chaque déploiement reste rattaché à une revue.

yaml
# dépôt applicatif : .github/workflows/release.yml
name: Release
 
on:
  push:
    branches: [main]
 
permissions:
  contents: read
  packages: write
 
concurrency:
  group: release-payments-api
  cancel-in-progress: false
 
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
 
      - uses: docker/setup-buildx-action@v4
 
      - uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - uses: docker/build-push-action@v7
        with:
          context: .
          push: true
          tags: ghcr.io/acme/payments-api:${{ github.sha }}
          cache-from: type=registry,ref=ghcr.io/acme/payments-api:buildcache
          cache-to: type=registry,ref=ghcr.io/acme/payments-api:buildcache,mode=max
 
      - name: Check out config repo
        uses: actions/checkout@v7
        with:
          repository: acme/gitops-config
          ssh-key: ${{ secrets.CONFIG_REPO_DEPLOY_KEY }}
          path: gitops-config
 
      - name: Bump image tag
        working-directory: gitops-config/apps/payments-api/overlays/production
        run: |
          kustomize edit set image ghcr.io/acme/payments-api=ghcr.io/acme/payments-api:${{ github.sha }}
          git config user.name "release-bot"
          git config user.email "release-bot@users.noreply.github.com"
          if git diff --quiet; then echo "Tag already deployed"; exit 0; fi
          git commit -am "payments-api: deploy ${{ github.sha }}"
          git pull --rebase
          git push

La deploy key n'a d'accès en écriture que sur le dépôt de configuration. Rien dans ce workflow ne peut non plus atteindre le cluster. La CI ne détient jamais d'identifiants du cluster, puisque c'est Argo CD qui récupère les changements depuis Git. concurrency empêche deux merges de ce service de se marcher dessus, et git pull --rebase gère le cas où le bot d'un autre service a poussé entre-temps sur le même dépôt de configuration. Kustomize est préinstallé sur les runners Ubuntu de GitHub. Si votre contrôle des changements exige une seconde approbation pour la production, ouvrez une PR sur le dépôt de configuration au lieu de pousser directement. Le pipeline reste le même, avec une étape de revue en plus. Cette étape coûte du lead time, donc je la réserve aux services qui en ont besoin.

Étape 2 : le dépôt de configuration et l'Application Argo CD

Le dépôt de configuration contient une base et un overlay de production. Après une mise à jour, le kustomization.yaml de l'overlay ressemble à ceci :

yaml
# gitops-config : apps/payments-api/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: payments
resources:
  - ../../base
images:
  - name: ghcr.io/acme/payments-api
    newTag: 3f9c2e1a7b4d5c6e8f0a1b2c3d4e5f6a7b8c9d0e

Et une Application Argo CD pointe sur cet overlay avec une synchronisation automatique :

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payments-api
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/acme/gitops-config.git
    targetRevision: main
    path: apps/payments-api/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: payments
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Par défaut, Argo CD interroge Git toutes les quelques minutes. Avec un webhook depuis le dépôt de configuration, la synchronisation démarre quelques secondes après le commit de mise à jour. selfHeal annule les modifications faites à la main dans le cluster, ce qui le garde aligné sur Git.

Étape 3 : un canary validé par Prometheus

Au lieu d'un Deployment, la base définit un Rollout Argo Rollouts. Il déplace le trafic par paliers pendant qu'une analyse tourne en arrière-plan. Si l'analyse échoue, le rollout est interrompu et le trafic revient de lui-même sur la version stable.

yaml
# gitops-config : apps/payments-api/base/rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: payments-api
spec:
  replicas: 5
  revisionHistoryLimit: 3
  selector:
    matchLabels:
      app: payments-api
  template:
    metadata:
      labels:
        app: payments-api
    spec:
      containers:
        - name: payments-api
          image: ghcr.io/acme/payments-api  # tag défini par l'overlay
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: http
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              memory: 256Mi
  strategy:
    canary:
      canaryService: payments-api-canary
      stableService: payments-api-stable
      analysis:
        templates:
          - templateName: http-success-rate
        startingStep: 1
        args:
          - name: service-name
            value: payments-api-canary
      steps:
        - setWeight: 20
        - pause: { duration: 5m }
        - setWeight: 50
        - pause: { duration: 10m }

payments-api-canary et payments-api-stable sont deux objets Service ordinaires avec le sélecteur app: payments-api. Le contrôleur ajoute un pod-template-hash au sélecteur de chacun, si bien que l'un ne sélectionne que les pods canary et l'autre que les pods stables, ce qui permet à Prometheus de mesurer le canary séparément. Sans traffic router (Istio, NGINX, Gateway API), setWeight est approximé par le nombre de réplicas, donc 20 % de 5 réplicas donnent un pod canary. Si vous avez besoin de pourcentages précis, ajoutez un traffic router.

Voici le template d'analyse :

yaml
# gitops-config : apps/payments-api/base/analysis-template.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: http-success-rate
spec:
  args:
    - name: service-name
  metrics:
    - name: success-rate
      interval: 1m
      failureLimit: 1
      successCondition: result[0] >= 0.99
      provider:
        prometheus:
          address: http://prometheus-operated.monitoring.svc:9090
          query: |
            sum(rate(http_requests_total{service="{{args.service-name}}", code!~"5.."}[2m]))
            /
            sum(rate(http_requests_total{service="{{args.service-name}}"}[2m]))

Adaptez les noms de métrique et de labels à votre instrumentation. Sans count, une analyse en arrière-plan tourne jusqu'à la fin du rollout, et deux mesures en échec (une de plus que failureLimit) l'interrompent. Si le canary ne reçoit aucun trafic, la requête ne renvoie aucune valeur exploitable et l'analyse passe en erreur au lieu de réussir, ce qui est l'issue la plus sûre.

Comment chaque brique réduit le lead time

  • Des lots plus petits : chaque release ne contient qu'un seul merge, ce qui la garde petite et peu coûteuse à annuler. Ce sont ces petites releases qui permettent de se passer du comité de changement.
  • Une promotion automatisée : la question à laquelle répondait un approbateur humain est désormais une requête Prometheus exécutée à chaque release. En additionnant les étapes, on obtient la CI (quelques minutes si vous avez accéléré le build), une synchronisation Argo CD (quelques secondes avec un webhook) et 15 minutes de pauses canary. Entre le merge et 100 % du trafic, on reste généralement bien en dessous d'une heure, là où il fallait auparavant attendre le prochain créneau d'approbation.
  • Un rollback rapide : quand l'analyse échoue, le rollout s'interrompt de lui-même et les pods stables tournent toujours. Faites ensuite un git revert du commit de mise à jour pour que Git corresponde de nouveau à ce qui tourne ; Argo CD affiche alors l'application comme saine sur le tag précédent.

Les équipes qui appliquent cette approche avec constance passent généralement de déploiements hebdomadaires validés par un comité de changement à plusieurs déploiements par jour. La traçabilité s'améliore aussi, puisque chaque changement en production garde un commit, un diff, une PR liée et une exécution d'analyse qui lui sont rattachés.

ALLER PLUS LOIN

Envie de faire tourner ça en production ?

Ce tutoriel couvre les concepts et l'architecture. Si vous voulez l'implémenter dans votre propre infrastructure, ou monter en compétence pour posséder ce sujet durablement, je propose du mentorat individuel construit autour de votre environnement réel, pas une formation générique.

Ce tutoriel

  • Concepts clés et architecture principale
  • Extraits de code illustratifs
  • Le raisonnement derrière chaque décision

Mentorat individuel

  • Des sessions de travail sur votre propre environnement
  • Des réponses directes aux cas particuliers que vous rencontrez
  • Un retour sur votre implémentation réelle
  • Un accompagnement continu pendant que vous la construisez
Me contacterRéponse sous 1 jour ouvré.

Tutoriels similaires