Skip to content

Comprendre ArgoCD

Ce guide explique ArgoCD en profondeur : ce qu'il fait, comment il raisonne, et les options qu'on utilise dans le projet.

ADR lié : GitOps avec ArgoCD. Voir aussi le guide Observabilité (distinction plan installation / plan runtime).


En une phrase

ArgoCD est un moteur GitOps : il lit un dépÎt Git (l'état voulu) et fait en sorte que le cluster Kubernetes (l'état réel) lui corresponde, en permanence.

C'est déclaratif (on décrit la cible, pas les étapes) et continu (il réconcilie en boucle, pas juste une fois).


L'objet central : Application

Tout part d'un objet kind: Application. C'est une fiche qui répond à 3 questions :

spec:
  source:                       # QUOI déployer
    repoURL: ...                #   depuis quel dépÎt
    targetRevision: develop     #   quelle branche/version
    path: k8s/base              #   quel dossier (ou chart Helm)
  destination:                  # OÙ dĂ©ployer
    server: https://kubernetes.default.svc
    namespace: fastapi
  syncPolicy:                   # COMMENT rester synchronisé
    automated:
      prune: true
      selfHeal: true

ArgoCD compare en boucle le contenu de la source (Git) avec ce qui tourne dans la destination (cluster).


Les deux états que tu liras tout le temps

État Signification
Synced Le cluster correspond Ă  Git
OutOfSync Le cluster diffÚre de Git (drift, ou nouveau commit pas encore appliqué)
Healthy Les ressources tournent correctement (pods Ready, etc.)
Progressing En cours de déploiement (rollout pas fini)
Degraded Une ressource est en échec

Le combo idéal : Synced + Healthy. Si une Application reste Progressing sans se stabiliser, c'est souvent un conflit (cf. INC-054, HPA vs selfHeal).


La syncPolicy : prune et selfHeal

syncPolicy:
  automated:
    prune: true       # supprime du cluster ce qui disparaĂźt de Git
    selfHeal: true    # réécrase toute modif manuelle faite dans le cluster
  • Sans prune : si tu retires un fichier de Git, la ressource reste dans le cluster (orpheline). prune: true la supprime. Ça corrige le dĂ©faut du kubectl apply impĂ©ratif (qui ne supprime jamais).
  • selfHeal: true : si quelqu'un fait un kubectl edit Ă  la main, ArgoCD remet l'Ă©tat de Git. Git est la seule source de vĂ©ritĂ©.

Attention : selfHeal se bat avec tout autre contrĂŽleur qui modifie le mĂȘme champ. Exemple vĂ©cu (INC-054) : un HPA change spec.replicas, ArgoCD le réécrase → boucle. Solution : ignoreDifferences (voir plus bas).


Le pattern app-of-apps

On installe une seule Application à la main (le root, nommée apps). Son path pointe vers un dossier qui contient... d'autres Applications.

root-app (apps)
   └── surveille k8s/platform/argocd-apps/
         ├── fastapi.yaml
         ├── kube-prometheus-stack.yaml
         ├── loki.yaml
         └── alloy.yaml

Résultat : pour ajouter une brique, on dépose un fichier dans ce dossier, ArgoCD le découvre et le déploie tout seul. C'est ce qu'on a fait pour l'observabilité (#74/#75) et l'exposition (#76).


Les options qu'on utilise dans le projet

sync-wave : l'ordre d'installation

metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "0"   # vague 0 = installé avant la vague 1

Sert Ă  ordonner : kube-prometheus-stack (wave 0) pose les CRD, puis l'Application monitoring (wave 1) qui les utilise. Loki (wave 1) avant Alloy (wave 2) qui pousse vers lui.

multi-source : recette d'internet + nos réglages

sources:
  - repoURL: https://prometheus-community.github.io/helm-charts   # le chart
    chart: kube-prometheus-stack
    targetRevision: 86.2.2
    helm:
      valueFiles:
        - $values/k8s/platform/monitoring/values.yaml             # nos values
  - repoURL: https://gitlab.com/yk-devops/fastapi-eks-project.git
    targetRevision: develop
    ref: values                                                   # fournit $values

Le chart vient d'internet (figé par targetRevision), nos réglages vivent dans notre Git (diffables en review). On ne modifie jamais la recette publique.

syncOptions

syncOptions:
  - CreateNamespace=true   # crée le namespace cible s'il n'existe pas
  - ServerSideApply=true   # applique cÎté serveur API

ServerSideApply est indispensable pour kube-prometheus-stack : ses CRD dépassent la limite d'annotation du kubectl apply classique (262144 octets).

ignoreDifferences : cohabiter avec un autre contrĂŽleur

ignoreDifferences:
  - group: apps
    kind: Deployment
    name: fastapi
    jsonPointers:
      - /spec/replicas        # ce champ est géré par le HPA, pas par Git

On dit à ArgoCD d'ignorer un champ. Indispensable quand un HPA (ou un autre opérateur) pilote dynamiquement un champ : sinon selfHeal lui fait la guerre (INC-054).


Refresh vs Sync (et le polling)

  • Refresh : ArgoCD relit Git pour dĂ©tecter un changement. Par dĂ©faut il le fait par polling toutes les ~3 minutes (ou instantanĂ©ment via un webhook GitLab).
  • Sync : ArgoCD applique le changement dĂ©tectĂ© au cluster.

En auto-sync, les deux s'enchaßnent. Quand on merge sur develop, il y a donc un délai (jusqu'à ~3 min) avant qu'ArgoCD agisse. On peut forcer (bouton Refresh puis Sync dans l'UI, ou argocd app sync <nom>).


Le point qui perd tout le monde : ArgoCD ≠ pipeline

ArgoCD ne regarde ni le pipeline GitLab, ni les MR, ni les branches de feature. Il regarde uniquement : « le contenu de la branche que je surveille (develop) a-t-il changé ? ».

Conséquences concrÚtes :

  • Un git push sur une branche de feature ne dĂ©clenche rien dans ArgoCD (ce n'est pas develop).
  • Une MR ouverte ne change rien tant qu'elle n'est pas mergĂ©e dans develop.
  • Un pipeline ratĂ© ou absent n'empĂȘche pas ArgoCD d'agir (et inversement).

C'est pour ça qu'une modif ne prend effet qu'au merge dans develop, et que « l'infra Ă©teinte » n'empĂȘche pas de livrer : on Ă©crit dans Git, ArgoCD applique au prochain dĂ©marrage du cluster.


Comment ArgoCD est installé (lien avec le bootstrap)

ArgoCD lui-mĂȘme est posĂ© par le bootstrap Ansible (une fois, Ă  l'aws-start) : il installe le chart ArgoCD via Helm, puis applique le root-app.yaml. Ensuite ArgoCD prend le relais et dĂ©ploie tout le reste.

Ansible = impératif, one-shot (amorce). ArgoCD = déclaratif, continu (pilote automatique). Voir Observabilité.


Le mot de passe admin (géré via ESO)

Par défaut, le chart ArgoCD génÚre un mot de passe admin aléatoire et le pose dans le secret argocd-initial-admin-secret. Pratique tant que l'accÚs est local (port-forward), mais on veut exposer l'UI publiquement (#105/MR2), donc on prend la main sur ce mot de passe : on le gÚre via ESO, comme celui de Grafana (#76).

Sauf que ArgoCD a deux particularités par rapport à Grafana :

1. ArgoCD stocke un hash bcrypt, pas un mot de passe en clair. Le mot de passe admin vit dans argocd-secret, sous deux clĂ©s : admin.password (le hash bcrypt) et admin.passwordMtime (la date depuis laquelle il est valide). On stocke donc le hash dans AWS Secrets Manager, pas un mot de passe brut. Et comme la fonction bcrypt() de Terraform regĂ©nĂšre un sel Ă  chaque apply (le hash changerait sans arrĂȘt), on gĂ©nĂšre le hash une fois en local et on le fournit comme valeur externe (TF_VAR_argocd_admin_bcrypt), exactement comme l'URL du webhook Slack (#77) :

# Générer le hash bcrypt à passer en TF_VAR_argocd_admin_bcrypt
argocd account bcrypt --password 'MON_MOT_DE_PASSE'
# ou, sans la CLI argocd :
htpasswd -nbBC 10 "" 'MON_MOT_DE_PASSE' | tr -d ':\n' | sed 's/$2y/$2a/'

2. argocd-secret appartient déjà au chart ArgoCD. Il contient aussi d'autres clés (server.secretkey...). Si notre ExternalSecret essayait de le créer (creationPolicy: Owner, comme Grafana qui crée son secret), ESO et ArgoCD se battraient pour la propriété. On utilise donc creationPolicy: Merge + template.mergePolicy: Merge : ESO patche le secret existant en ajoutant juste admin.password + admin.passwordMtime, sans toucher au reste.

Le tout est dĂ©ployĂ© par une Application ArgoCD dĂ©diĂ©e argocd-config (path: k8s/platform/argocd) : ArgoCD gĂšre sa propre config en GitOps. Il dĂ©ploie l'ExternalSecret (pas argocd-secret directement), c'est ESO qui fait le patch → aucun conflit. Le pourquoi complet : ADR 022.

Retenir : Owner quand tu crées le secret (Grafana) ; Merge quand tu enrichis un secret possédé par quelqu'un d'autre (ArgoCD). Confondre les deux = bataille de contrÎleurs.


L'exposition publique (argocd.devopsyouss.com)

Une fois le mot de passe gĂ©rĂ© (ci-dessus), on expose l'UI via le mĂȘme Gateway partagĂ© que Grafana (voir Exposition Grafana) : un HTTPRoute argocd.devopsyouss.com (ns argocd) qui pointe argocd-server:80, attachĂ© au fastapi-gateway cross-namespace. Le cert wildcard couvre le sous-domaine, ExternalDNS crĂ©e le CNAME tout seul.

Le piĂšge Ă  connaĂźtre : le TLS-sur-TLS. Par dĂ©faut, argocd-server fait lui-mĂȘme du TLS. Si on le met derriĂšre un Gateway qui termine dĂ©jĂ  le TLS, on a deux couches de TLS qui s'empilent → boucle de redirection HTTP↔HTTPS, ou 502 sur le gRPC. La solution standard d'ArgoCD derriĂšre un ingress : passer le serveur en server.insecure: true. Il sert alors l'UI / l'API / le gRPC-Web en clair sur le port 80, et c'est le Gateway qui fait le HTTPS public.

navigateur ──HTTPS (cert wildcard)──â–ș Gateway (Envoy) ──HTTP──â–ș argocd-server:80
                  TLS terminĂ© ici ─────────┘

OĂč vit quoi (Ă  retenir) : server.insecure est un paramĂštre d'installation du serveur → il est dans les values du helm install ArgoCD, donc dans le bootstrap Ansible. Le HTTPRoute, lui, est une ressource d'exposition → il est en GitOps (k8s/platform/argocd/, Application argocd-config). Le pourquoi complet : ADR 023.

Point ouvert (Ă  valider live) : le CLI argocd parle gRPC. Avec server.insecure et un seul port, ArgoCD sert le gRPC-Web sur le 80 ; reste Ă  confirmer que argocd login argocd.devopsyouss.com passe bien Ă  travers le Gateway.


Le SSO (connexion via GitLab)

Maintenant qu'ArgoCD est exposĂ©, on ne veut plus se connecter avec le mot de passe admin au quotidien : on dĂ©lĂšgue l'authentification Ă  GitLab (l'IdP qu'on utilise dĂ©jĂ ). ArgoCD embarque pour ça Dex, un petit serveur qui sait parler Ă  des fournisseurs d'identitĂ© (GitLab, GitHub, Google
).

Le flux quand tu cliques « Log in via GitLab » :

toi ──â–ș ArgoCD ──â–ș Dex ──â–ș GitLab (tu t'authentifies) ──â–ș Dex ──â–ș ArgoCD (connectĂ©)
                                    │
                                    └─ GitLab renvoie : qui tu es + tes groupes

La config vit dans argocd-cm (configs.cm.dex.config, donc dans les values du helm install → bootstrap Ansible) : un connecteur type: gitlab, le clientID (public, en clair) et clientSecret: $dex.gitlab.clientSecret. Le $ dit Ă  ArgoCD d'aller lire la valeur dans argocd-secret (la clĂ© y est injectĂ©e par ESO, mĂȘme mĂ©canisme que le mot de passe admin). Le champ groups: [yk-devops] filtre : seuls les membres du groupe GitLab yk-devops peuvent se connecter.

Le RBAC (qui a le droit de faire quoi) est dans argocd-rbac-cm (configs.rbac) :

policy.default: role:readonly        # par défaut, lecture seule
policy.csv: |
  g, yk-devops, role:admin           # les membres du groupe yk-devops -> admin

GitLab renvoie le groupe dans le token, ArgoCD applique la rĂšgle. Le pourquoi complet (GitLab vs GitHub, une seule app multi-redirect, secret via ESO) : ADR 024.

Le mot de passe admin devient un « break-glass » : on se connecte via GitLab au quotidien, l'admin ne sert plus qu'en secours (si le SSO tombe). À terme on peut mĂȘme le dĂ©sactiver (admin.enabled: false). MĂȘme logique pour Grafana (MR2 de #106).


Teardown

Les Applications ont un finalizer :

metadata:
  finalizers:
    - resources-finalizer.argocd.argoproj.io

Supprimer une Application supprime aussi ce qu'elle a créé (cascade). Au teardown, on supprime les Applications en premier : ça stoppe le selfHeal avant le reste, sinon il recrĂ©erait des ressources (ex: HTTPRoute → ELB orphelin, classe INC-016).


Récap des piÚges

SymptÎme Cause probable Réflexe
Application reste Progressing Conflit sur un champ (HPA vs selfHeal) ignoreDifferences (INC-054)
CRD Too long ... 262144 bytes apply client-side sur gros CRD ServerSideApply=true
Ma modif n'est pas appliquée Pas (encore) sur develop Merger ; ArgoCD suit develop, pas les branches
Rien ne bouge aprĂšs le merge Polling ~3 min Attendre, ou Refresh+Sync
ELB orphelin au teardown selfHeal recrée l'HTTPRoute Supprimer les Applications en premier