← Index
Cours · GitOps

argocd

Git est la vérité, le cluster est un miroir. ArgoCD surveille le repo et réconcilie — même approche que k8s, mais à l'échelle du déploiement. Ce cours décode aussi ce que tu vois dans l'UI.

NIVEAU · Débutant+
DURÉE · ~25 min
PRÉ-REQUIS · bases k8s

GitOps en 2 minutes 01

Git est la source de vérité

Plus personne ne fait kubectl apply à la main. On commit dans Git, ArgoCD voit le commit, compare avec le cluster et applique la différence. Rollback = revert git.

  • Déclaratif : le repo contient l'état désiré (YAML, Helm, Kustomize).
  • Versionné : qui a changé quoi, quand, pourquoi — git log tient lieu d'audit.
  • Pulled, not pushed : ArgoCD dans le cluster tire depuis Git — pas de credentials kube dans la CI.
  • Observable : une UI, une API, un CLI pour voir l'écart en temps réel.
dev git push Git repo manifests YAML ArgoCD pull toutes les 3 min diff desired ↔ live sync = kubectl apply Kubernetes cluster Deployments · Services · … état réel observé (live state) observe le live → calcule le diff
Fig. 01 · Le flux GitOps — push, pull, diff, apply, re-observe

Architecture ArgoCD 02

Quatre composants spécialisés

ArgoCD tourne dans le cluster — namespace argocd. Chaque composant a une responsabilité bien délimitée.

user UI / CLI Git github / gitlab namespace: argocd argocd-server API · UI · gRPC auth · RBAC · proxy repo-server clone repos render helm / kustomize application-controller compare & sync statefulset redis cache manifests side state dex / notifications OIDC SSO slack · webhooks clusters gérés ce cluster + remotes in-cluster (local) prod-eu staging-us
Fig. 02 · Composants ArgoCD & leurs responsabilités
ARGOCD-SERVER
UI + API
REPO-SERVER
Clone + render
APP-CONTROLLER
Compare + sync
REDIS
Cache
DEX
SSO OIDC
NOTIFICATIONS
Alertes

Le cycle de sync 03

Refresh → compare → sync

Toutes les 3 min (par défaut) — ou sur webhook Git, ou sur demande — ArgoCD refait une passe.

  • Refresh : le repo-server tire le dernier commit et rend les manifests (helm template, kustomize build…).
  • Compare : le controller compare le rendu au live state (ce qui tourne dans le cluster).
  • Sync : si automatisé, il applique. Sinon, on voit OutOfSync et on clique ⟳.
refresh git pull + render compare diff vs live sync apply au cluster observe healthy ?
Fig. 03 · La boucle ArgoCD

L'objet Application 04

Le contrat

Une Application est un CRD posé dans le namespace argocd. Elle dit quoi déployer, d'où, et où.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: laravel-pr730
  namespace: argocd
spec:
  project: default
  source:                     # d'où
    repoURL: https://git/org/app.git
    targetRevision: main
    path: charts/laravel
    helm:
      values: |
        replicaCount: 3
  destination:                # où
    server: https://kubernetes.default.svc
    namespace: laravel-pr730
  syncPolicy:                 # comment
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
  • source : repoURL + path + targetRevision (branche, tag ou SHA).
  • destination : cluster + namespace cible.
  • syncPolicy : manuel (par défaut) ou automated ± prune ± selfHeal.

Sync status 05

Git = cluster ?

Compare les manifests rendus depuis Git avec l'état live. Trois valeurs.

Synced OutOfSync Unknown
  • Synced — le rendu Git correspond au live, tout va bien.
  • OutOfSync — un diff existe. Soit Git a avancé, soit un humain a touché le cluster.
  • Unknown — ArgoCD n'a pas réussi à lire Git ou le cluster. Cherche les logs du controller.

Dans l'UI, l'icône est un cercle bleu-ciel avec ⟳ pour OutOfSync. Dans la capture, le laravel-pr730 affiche à la fois Sync et Health — les deux axes sont indépendants.

Health status 06

Ça tourne ?

Jugement sur ce qui est en vie dans le cluster — distinct de la sync.

Healthy Progressing Suspended Degraded Missing
  • Healthy ♥ vert — tout est UP (pods running, Deployment au bon nb de replicas…).
  • Progressing — changement en cours (rolling update, pods en Init). Cas normal < quelques minutes.
  • Suspended — ex. CronJob pausé, rollout paused.
  • Degraded — un pod CrashLoop, un rollout qui timeout → il faut regarder.
  • Missing — la ressource existe en Git mais pas dans le cluster.

L'arbre de ressources — décodé 07

Ce que tu vois dans l'UI

Au centre-gauche, l'Application. À droite, tous les objets déployés — arborescence par ownerReference : qui possède qui. C'est la vue Argo la plus utile pour diagnostiquer.

APP laravel-pr730 cm · redis-configuration cm · redis-health cm · redis-scripts secret · redis svc · laravel-pr730 svc · redis-headless svc · redis-master sa · laravel-pr730 sa · redis deploy · laravel-pr730 sts · redis-master ES · db-secrets ep · laravel-pr730 ES · eps-laravel rs · rev:27 · old rs · rev:28 · old rs · rev:29 · NEW rs · rev:2 · old CR · controllerrev pod · redis-master-0 secret · db-secrets pod · rev28 · terminating pod · rev29 · init 0/1 pod · rev29 · terminating pod · rev? · 2/2 running ↑ Top-level (ce qui est dans Git) ↑ Owned (par le top-level) ↑ Pods (par les ReplicaSets)
Fig. 07 · Ton arbre, reproduit & décodé
  • La 1ʳᵉ colonne = ressources directement dans Git (ce qu'ArgoCD a appliqué).
  • La 2ᵉ colonne = ressources créées par celles de la 1ʳᵉ via leur contrôleur k8s (un Deployment crée un RS, un Service expose des Endpoints, une ExternalSecret matérialise un Secret).
  • La 3ᵉ colonne = pods (créés par les ReplicaSets ou directement par un StatefulSet).
  • Les flèches suivent les ownerReferences Kubernetes — ArgoCD ne fait que lire ce graphe, il ne l'invente pas.
  • Le « 7 hours » sous chaque objet = âge depuis la dernière modification observée.

Icônes · abréviations 08

Lire les petits badges

cmConfigMap config non sensible
secSecret creds, certs
svcService endpoint stable
saServiceAccount identité du pod
deployDeployment pods stateless
stsStatefulSet pods avec identité
rsReplicaSet génération d'un Deployment
podPod unité d'exécution
epEndpoints IPs derrière un svc
ESEndpointSlice (bordure pointillée)
CRControllerRevision snapshot de sts/ds
ESExternalSecret externalsecrets.io

ES apparaît deux fois avec deux sens : cercle pointillé = EndpointSlice (natif k8s, enfant d'un Service) ; cercle plein = ExternalSecret (CRD externe qui crée un Secret depuis Vault/AWS SM/etc.).

Deployment → RS → Pod 09

Pourquoi plusieurs RS ?

Un Deployment ne crée pas les Pods directement. Il fabrique un ReplicaSet par révision. À chaque image/env qui change, un nouveau RS naît — l'ancien reste pour pouvoir rollback.

  • Par défaut, 10 RS précédents sont gardés (revisionHistoryLimit).
  • Un seul RS est actif à un instant — c'est celui qui matche le template courant.
  • Les pods héritent du hash du template dans leur nom (web-6f9b846949-xyz).
  • Rollback = kubectl rollout undo — il suffit de re-scaler un ancien RS.
# Les révisions visibles dans ton screenshot
laravel-pr730-64cd89f578   rev:1   # 0 replicas (ancienne)
laravel-pr730-6c8c8f4f7    rev:3   # 2/2 running (stable)
laravel-pr730-7ddb846949   rev:29  # 0/1 (nouveau, en train de monter)
laravel-pr730-866ccc7b74   rev:28  # 0/2 terminating (ancien)
laravel-pr730-d46787577    rev:2   # 0 replicas (ancienne)

Lire un rolling update 10

Ce qui se passe sous tes yeux

Dans ta capture, la colonne des ReplicaSets et la colonne des Pods racontent la même histoire : un rolling update en cours. Voilà la séquence.

T₀ · stable RS rev:28 pod ✓ pod ✓ 2/2 running T₁ · git push RS rev:28 (−) RS rev:29 (+) rev28 ✓ rev28 ✓ init ArgoCD a appliqué le nouveau template. T₂ · surge RS rev:28 RS rev:29 rev28 ✓ rev28 ✗ rev29 ✓ init Les pods v29 passent ready → un pod v28 est tué (maxSurge/maxUnavailable) T₃ · ce que tu vois RS rev:28 (vieille) RS rev:29 (active) rev28 ✗ terminating rev29 ✗ terminating rev29 init 0/1 rev? 2/2 Le rollout est en cours. Health = Progressing Sync = OutOfSync possible T₄ · stable RS rev:29 rev29 ✓ rev29 ✓ Health = Healthy
Fig. 10 · Séquence d'un rolling update vue depuis ArgoCD
  • Le nouveau RS monte ; l'ancien descend. À chaque pod ready côté v29, un pod v28 est terminating.
  • Les rectangles rose « terminating » sont normaux — c'est la phase de grace period.
  • Le RS rev:29 peut aussi montrer un pod terminating si un pod v29 a mis trop de temps à devenir Ready et que le scheduler a remplacé.
  • Health Progressing pendant tout ce temps, puis Healthy.
  • Si ça bloque en Progressing > 10 min → regarder les logs du pod v29 et les probes.

Sync waves & hooks 11

Ordre d'application

Par défaut, ArgoCD applique tout en parallèle. Mais parfois il faut un ordre (CRD avant CR qui l'utilise, migration avant app…).

# Pousser une ressource dans une wave plus tardive
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "2"

# Hook : exécuté à un moment du sync
annotations:
  argocd.argoproj.io/hook: PreSync       # avant
  argocd.argoproj.io/hook: Sync          # pendant
  argocd.argoproj.io/hook: PostSync      # après
  argocd.argoproj.io/hook: SyncFail      # si échec
  argocd.argoproj.io/hook-delete-policy: HookSucceeded
  • Waves négatives (−1, −2) : appliquées en premier (ex : Namespace, CRD, Secret).
  • Usage typique du PreSync : migration DB en Job avant de restarter les pods.

Auto-sync · prune · self-heal 12

Les trois cases à cocher

  • automated — applique dès que Git change, sans clic.
  • prune — supprime du cluster ce qui a été retiré de Git. Sans ça, ArgoCD ne supprime jamais.
  • selfHeal — si un humain a modifié en direct (kubectl edit), Argo réécrase avec Git au refresh suivant.
syncPolicy:
  automated:
    prune: true
    selfHeal: true
  syncOptions:
    - CreateNamespace=true
    - PrunePropagationPolicy=foreground
    - ApplyOutOfSyncOnly=true
  retry:
    limit: 5
    backoff: { duration: 5s, factor: 2, maxDuration: 3m }

⚠ Sans prune, un fichier supprimé de Git laisse la ressource orpheline en cluster — dette garantie.

AppProject & ApplicationSet 13

Isoler · multiplier

  • AppProject — boîte autour de N Applications. Restreint les sources (repos autorisés) et les destinations (clusters/ns autorisés). Utile en multi-tenant.
  • ApplicationSetcontroller qui génère des Applications depuis un template + un générateur (Git, List, Cluster, Matrix, PR). Le cas typique : une App par PR GitHub.
# ApplicationSet : une App par PR
kind: ApplicationSet
spec:
  generators:
    - pullRequest:
        github: { owner: acme, repo: laravel }
  template:
    metadata:
      name: 'laravel-pr{{number}}'
    spec:
      source:
        repoURL: https://git/acme/laravel.git
        targetRevision: '{{head_sha}}'
        path: charts/laravel
      destination:
        server: https://kubernetes.default.svc
        namespace: 'laravel-pr{{number}}'

💡 Le préfixe laravel-pr730 dans ta capture vient très probablement d'un ApplicationSet PR generator — chaque PR GitHub a son Application et son namespace.

CLI argocd 14

L'essentiel au terminal

# Login
argocd login argocd.example.com --sso
argocd login --core                 # talk to k8s directly

# Apps
argocd app list
argocd app get laravel-pr730
argocd app diff laravel-pr730       # git vs live
argocd app sync laravel-pr730
argocd app sync laravel-pr730 --prune
argocd app rollback laravel-pr730 29
argocd app logs laravel-pr730 -f --tail 100

# History · refresh
argocd app history laravel-pr730
argocd app refresh laravel-pr730 --hard
argocd app wait laravel-pr730 --health
argocd app terminate-op laravel-pr730

# Clusters · repos
argocd cluster list
argocd repo list

Troubleshooting — patterns 15

Ce que l'UI te dit vraiment

  • OutOfSync qui colle — clique « App Diff ». Souvent une annotation last-applied-configuration ou un champ managed by un autre controller (HPA modifie replicas…). Fix : ignoreDifferences dans la spec.
  • Degraded sur un Deployment — ouvre le RS courant → Pods → Events. Probe qui échoue, image tag introuvable, OOMKill, ImagePullBackOff.
  • Missing — le manifest existe en Git, pas en cluster. Souvent un hook en échec ou une erreur RBAC côté controller.
  • Prune réticent — Argo exige prune: true et que la ressource ait été un jour gérée par lui (annotation argocd.argoproj.io/tracking-id).
  • Hook failed — visible en bleu-ciel avec statut d'échec dans l'arbre. argocd app logs <app> + kubectl logs sur le Job hook.
  • Sync bloquée sur une ressource « pending » — un finalizer retient la suppression. Regarde kubectl get <res> <n> -o yaml | grep finalizers.
  • App entière orpheline — l'ApplicationSet parent a été retiré mais l'App reste — active preservedFields ou la policy create-only avec précaution.
# Ignorer un champ qui varie en runtime (HPA, operator…)
spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas