Pipelines GitOps avec ArgoCD & Helm
Ce guide met en place un pipeline GitOps multi-environnements avec ArgoCD et Helm, avec une structure app-of-apps et des surcouches de valeurs par environnement, ainsi que des politiques de sync automatisées qui réconcilient en continu le cluster avec Git et corrigent le drift au lieu de se contenter de le détecter.
Le pattern app-of-apps
Au-delà de quelques services, vous ne voulez pas d'une Application par microservice ; vous voulez une seule Application racine qui gère elle-même toutes les autres Applications. Un seul commit Git sur le repo d'apps suffit pour intégrer, décommissionner ou repointer un environnement entier.
# root-app.yaml, le seul objet qu'un admin de cluster touche manuellement
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-apps
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/acme/gitops-apps.git
targetRevision: main
path: apps/production
directory:
recurse: true
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: trueTout ce qui se trouve sous apps/production/ est un manifeste Application ArgoCD classique, un par service ou un par groupe logique. L'app racine surveille ce dossier ; ajoutez un fichier, obtenez une nouvelle app gérée, supprimez un fichier, obtenez un démontage propre.
Structure des charts Helm et surcouches par environnement
Sous la couche app-of-apps, chaque service est un chart Helm avec un fichier de valeurs par environnement, plutôt que des branches par environnement ou des repos forkés. Brancher vos manifestes est le meilleur moyen de finir avec un staging et une production qui divergent silencieusement, sans que personne ne le remarque avant le post-mortem.
charts/payments-api/
├── Chart.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── hpa.yaml
├── values.yaml # valeurs par défaut partagées
├── values-staging.yaml # surcouche
└── values-production.yaml # surcouche# values-production.yaml
replicaCount: 6
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 6
maxReplicas: 24
env:
LOG_LEVEL: warn
FEATURE_FLAGS_SOURCE: launchdarkly-prodvalues.yaml ne définit jamais directement le nombre de replicas ni les requests et limites de ressources. Chaque champ qui dépend de l'environnement vit dans la surcouche, si bien que la différence entre staging et production se résume à un diff entre deux fichiers YAML, avec la même version de chart.
Relier la CRD Application à Helm avec une sync automatisée
Chaque Application enfant pointe vers le chart et la surcouche d'environnement, et active la sync automated avec prune et selfHeal.
# apps/production/payments-api.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payments-api
namespace: argocd
spec:
project: production
source:
repoURL: https://github.com/acme/gitops-apps.git
targetRevision: main
path: charts/payments-api
helm:
valueFiles:
- values.yaml
- values-production.yaml
destination:
server: https://kubernetes.default.svc
namespace: payments
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 10s
factor: 2
maxDuration: 3mprune: true signifie que les ressources retirées du chart sont retirées du cluster à la prochaine sync. Sans cela, les templates supprimés laissent des objets orphelins indéfiniment. selfHeal: true annule tout changement manuel sur un objet vivant et le ramène à ce qui est dans Git, automatiquement, à la prochaine passe de réconciliation (toutes les trois minutes par défaut, ou immédiatement si vous surveillez la ressource).
selfHeal a un inconvénient. Si quelqu'un corrige à chaud un Deployment avec kubectl pendant un incident, ArgoCD l'annule à la prochaine réconciliation et le correctif disparaît sans que personne ne s'en aperçoive. La parade relève de la procédure. Votre runbook d'incident doit prévoir une étape explicite, avant que quiconque touche une ressource en direct à la main, pour committer le patch immédiatement ou mettre d'abord la sync automatique en pause sur l'app enfant et sur l'app racine qui la possède. Le workflow est décrit plus bas.
L'enfant pointe vers project: production, et cet AppProject doit exister avant que l'Application puisse se synchroniser. Il limite les repos depuis lesquels l'app peut tirer et les destinations où elle peut déployer. Je le garde dans le même dossier, pour que l'app racine le gère aussi, avec une sync wave plus basse pour qu'il soit créé en premier :
# apps/production/project.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-1"
spec:
description: Production workloads
sourceRepos:
- https://github.com/acme/gitops-apps.git
destinations:
- server: https://kubernetes.default.svc
namespace: payments
# types cluster-scoped que les apps peuvent créer : Namespace pour
# CreateNamespace=true, CRD pour les charts qui en embarquent
clusterResourceWhitelist:
- group: ""
kind: Namespace
- group: apiextensions.k8s.io
kind: CustomResourceDefinitionChaque nouveau namespace de production doit être ajouté sous destinations dans un commit relu. Ainsi, une faute de frappe dans la destination d'une Application enfant fait échouer la sync au lieu de déployer à un endroit inattendu.
Sync waves et hooks pour l'ordonnancement
Le rendu de templates de Helm n'a aucune notion de « applique ceci avant cela » entre charts séparés. Beaucoup de déploiements réels en ont pourtant besoin : les CRD avant les contrôleurs qui les surveillent, ou un Job de migration qui doit terminer avant que le Deployment dépendant du nouveau schéma ne démarre. Les sync waves d'ArgoCD résolvent cela avec une simple annotation.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: postgresqls.acme.io
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: batch/v1
kind: Job
metadata:
name: payments-db-migrate
annotations:
argocd.argoproj.io/sync-wave: "0"
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
annotations:
argocd.argoproj.io/sync-wave: "1"Les waves les plus basses sont synchronisées en premier, et ArgoCD attend que les ressources de chaque wave soient Healthy avant de passer à la suivante. Combiné aux hooks PreSync pour les jobs ponctuels comme les migrations, cela remplace l'étape fragile du « exécute ce script avant cette release Helm ».
Détection de drift et workflow de dérogation manuelle
Même avec selfHeal activé, il vous faut une procédure documentée pour les interventions manuelles légitimes. Quand un incident exige un patch en direct plus vite qu'un cycle PR puis merge ne le permet, il faut mettre la sync automatique en pause, et suspendre uniquement l'Application enfant ne suffit pas. La syncPolicy de l'enfant est définie dans Git et appartient à root-apps, qui a lui aussi selfHeal: true. La racine voit votre modification comme du drift et remet automated en place en quelques minutes, souvent pendant que vous êtes encore en train de patcher. Mettez d'abord la racine en pause, puis l'enfant :
# 1. empêcher l'app racine d'annuler les modifications des Applications enfants
argocd app set root-apps --sync-policy none
# 2. mettre l'app concernée en pause, puis patcher la ressource vivante
argocd app set payments-api --sync-policy none
# 3. une fois le correctif mergé dans Git, vérifier que l'app y correspond
argocd app diff payments-api
argocd app sync payments-api
# 4. réactiver la racine : elle restaure la syncPolicy de payments-api depuis Git
argocd app set root-apps --sync-policy automated --auto-prune --self-healEnsuite, annulez le changement manuel ou committez-le dans Git pendant la même fenêtre d'incident, et ne réactivez la racine qu'une fois l'app confirmée en sync. Tant que la racine est en pause, aucune Application n'est ajoutée ni retirée, donc gardez cette fenêtre courte. Si vous préférez ne jamais toucher à la racine pendant un incident, vous pouvez ajouter sur l'app racine une entrée ignoreDifferences pour le champ /spec/syncPolicy des Application enfants. L'inconvénient, c'est que la racine ignore alors aussi les changements de syncPolicy que vous faites volontairement dans Git.
En dehors d'un incident actif, le drift apparaît dans l'UI et l'API ArgoCD comme OutOfSync. Il vaut la peine d'alerter directement dessus : plus de quelques minutes d'OutOfSync sur une app de production, sans incident en cours, signifie que quelqu'un a contourné le process.
Pour revenir sur une release défaillante, je commence par un git revert du commit fautif. La sync automatique le déploie comme n'importe quel autre changement, et Git reste fidèle à ce qui tourne. argocd app rollback refuse de s'exécuter sur une Application dont la sync automatique est activée. Il faudrait donc d'abord désactiver la sync automatique (sur l'app racine aussi, comme ci-dessus), et l'app resterait décalée par rapport à Git jusqu'à ce que vous y fassiez le revert de toute façon. Il ne remonte en outre que jusqu'à l'historique de sync conservé par l'Application, soit revisionHistoryLimit, 10 entrées par défaut.
Quelqu'un devra encore se connecter en urgence et patcher une ressource à la main pendant un incident. Ce qui change, c'est la suite : soit le patch atterrit dans Git et la réconciliation le confirme, soit selfHeal l'annule silencieusement et vous le découvrez en quelques minutes plutôt qu'au prochain déploiement sans rapport.
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
Tutoriels similaires
Réduire le lead time de déploiement avec le GitOps
Remplacez les approbations manuelles par du GitOps avec Argo CD et un canary, pour livrer en petits lots sous contrôle.
Kubernetes : Deployments, Services et Ingress
Les trois objets de presque toute application Kubernetes, et les réglages de rollout qui évitent qu'une release tourne mal.