درس 1 — GitOps avec Argo CD

La plateforme GitOps sur kind

9 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

La plateforme GitOps sur kind

Le correctif « en urgence » fait par kubectl edit chez Ferrand Médical a vécu neuf jours, puis le déploiement suivant l'a effacé sans que personne le remarque. Le problème n'était pas le collègue : c'était l'absence d'une source unique de ce qui doit tourner. Avec GitOps, cette source est un dépôt Git, et un contrôleur dans le cluster compare en continu le cluster à ce dépôt. Cette leçon monte la plateforme complète sur un cluster kind de trois nœuds et déclare l'application.

Le mécanisme

Les quatre principes d'OpenGitOps se résument en une boucle : l'état voulu est déclaratif, versionné dans Git, tiré par un agent du cluster (pas poussé par la CI), et réconcilié en continu.

flowchart LR
  subgraph POSTE["Poste de l'équipe"]
    SRC["gitops-source/ (versionné avec le projet)"] -->|"make gitops-init"| REPO["gitops-repo/ (dépôt git local)"]
  end
  REPO -->|"make gitops-push (git://, port-forward)"| GIT["git-server (namespace git)"]
  subgraph KIND["kind lab-k8s-gitops"]
    GIT -->|"git fetch toutes les 3 min ou sur refresh"| REPOSRV["argocd-repo-server : helm template"]
    REPOSRV --> CTRL["argocd-application-controller : diff + apply"]
    CTRL -->|"Rollout, Service, Ingress, NetworkPolicy, ServiceMonitor..."| NS["namespace assistant"]
    CTRL -.->|"surveille les objets : toute dérive est vue"| NS
  end

Trois points comptent pour la suite :

  • Argo CD ne lance pas helm install. Le repo-server rend le chart (helm template) et le contrôleur applique les manifestes. Il n'y a donc pas de release Helm dans le cluster : helm list -n assistant est vide, et l'historique est celui de Git.
  • Le dépôt GitOps n'est pas le dépôt de l'application. Le code et l'image vivent ailleurs ; le dépôt GitOps ne contient que le chart et les valeurs par environnement. La CI propose une nouvelle étiquette d'image par pull request (github-actions.yml), Argo CD déploie.
  • Le serveur Git est dans le cluster. Dans le lab, un git daemon minimal (git-server.yaml) joue le rôle de GitHub ou GitLab. Argo CD ne sait pas lire un dossier de votre poste : il lui faut une URL.

La mise en œuvre

Le dépôt GitOps : versionné en clair, transformé en dépôt git par make

Le dépôt de la plateforme de formation ne peut pas contenir un autre dépôt git (le dossier .git imbriqué serait ignoré). Le contenu du dépôt GitOps existe donc en fichiers ordinaires dans gitops-source/, et une cible le transforme en dépôt :

files/Makefile
gitops-init: ## Crée le dépôt git gitops-repo/ à partir de gitops-source/ (sans effet s'il existe)
	@test -d gitops-repo/.git || { mkdir -p gitops-repo && cp -r gitops-source/. gitops-repo/ \
	  && git init -q -b main gitops-repo && $(GIT) add -A && $(GIT) commit -qm "Déploiement initial : assistant 0.2.0" \
	  && echo "Dépôt gitops-repo/ créé"; }
gitops-source/
  chart/inference/              le chart (Rollout, Service, Ingress, NetworkPolicy, PDB, ServiceMonitor, PrometheusRule...)
  environments/kind/values.yaml ce que l'on modifie pour déployer dans le lab
  environments/gke/values.yaml  Vertex AI, Workload Identity, pas d'Ingress (leçon 6)

Ensuite, on ne modifie plus gitops-source/ mais gitops-repo/ : c'est votre historique de déploiement. make clean ne l'efface jamais ; pour repartir de l'état initial, rm -rf gitops-repo && make gitops-init.

L'application déclarée

files/argocd/application.yaml
spec:
  source:
    repoURL: git://git-server.git.svc.cluster.local/assistant.git
    targetRevision: main
    path: chart/inference
    helm:
      releaseName: inference
      valueFiles: [../../environments/kind/values.yaml]
  syncPolicy:
    automated: { prune: true, selfHeal: true }

prune supprime du cluster ce qui a disparu de Git ; selfHeal réapplique Git quand quelqu'un modifie le cluster à la main. Sans les deux, Argo CD ne fait qu'afficher des écarts.

releaseName: inference n'est pas cosmétique. Sans lui, Argo CD prend le nom de l'application (assistant) et toutes les ressources s'appellent assistant-inference. C'est arrivé au premier essai de ce lab : make e2e cherchait le Rollout inference et échouait. Sur GKE, le même décalage casserait Workload Identity, dont le principal IAM désigne le ServiceAccount assistant/inference par son nom (leçon 5).

Le mode simulé

Le cluster ne contient aucune clé. L'environnement kind déploie LLM_BACKEND: fake : l'API répond « [fake] Réponse simulée à : ... » sans appel réseau. Le chart référence quand même le Secret inference-gemini en optional: true, ce qui permet de passer en Gemini réel par un seul commit après make secret. L'appel réel au modèle se fait hors cluster, par Vertex AI (leçon 5). Ce découpage a un avantage qui dépasse le lab : les démonstrations canary des leçons 3 et 4 envoient plus de 160 000 requêtes chacune, et aucune ne coûte un appel modèle.

Monter la plateforme

terminal
make setup && make test
make up        # cluster, Ingress NGINX, metrics-server, kube-prometheus-stack, Argo Rollouts, Argo CD, git-server, image, application
make status
make e2e

L'ordre de make up suit les dépendances : kube-prometheus-stack avant l'application (le chart crée un ServiceMonitor, dont la CRD vient de kube-prometheus-stack), Argo Rollouts avant l'application (le chart crée un Rollout), le serveur Git avant le premier push.

Ce qui casse en production

SymptômeCauseParade
Application Unknown, condition ComparisonError ... remote repository is emptyle serveur Git a perdu le dépôt (ici : pod redémarré, volume emptyDir)dépôt Git hébergé et sauvegardé ; dans le lab, make gitops-push le repeuple
make app échoue : no matches for kind "Rollout" ou "ServiceMonitor"CRD absente au moment de la synchronisationinstaller les opérateurs avant l'application, ou vagues de synchronisation (sync-wave)
Ressources nommées assistant-inference au lieu de inferencenom de release dérivé du nom de l'applicationhelm.releaseName explicite
Argo CD ne voit pas un commit pendant 3 minutesintervalle de lecture par défautwebhook du forge Git vers Argo CD ; dans le lab, annotation refresh posée par make gitops-push

La première ligne a été reproduite : le pod git-server supprimé, Argo CD passe en Unknown mais ne touche à rien dans le namespace assistant, les trois pods continuent de servir. Une source illisible gèle les déploiements, elle ne détruit pas ce qui tourne.

La règle de décision

  • Argo CD dès que plus d'une personne déploie, ou dès qu'un auditeur demande « qui a changé quoi, quand ». Pour un service unique déployé par une seule CI, helm upgrade --atomic dans la CI suffit.
  • Un dépôt GitOps séparé du code : les droits d'écriture ne sont pas les mêmes, et un commit applicatif ne doit pas déclencher de déploiement.
  • automated avec prune et selfHeal en production ; sans selfHeal, la dérive s'accumule et le prochain déploiement réserve des surprises.
  • À surveiller : l'état Synced/Healthy de chaque application, et le délai entre commit et synchronisation.

Pratique

Le serveur Git de l'entreprise est en maintenance pendant une heure. Que fait Argo CD aux applications en automated + prune ?

Un collègue propose de mettre le dépôt GitOps dans le dépôt du code, sous un dossier deploy/, pour simplifier. Quel argument l'emporte contre ?

Exercice

Ajoutez une deuxième application Argo CD, assistant-preprod, qui déploie le même chart dans le namespace assistant-preprod avec replicaCount: 1. Quels fichiers modifiez-vous, et lequel ne doit surtout pas changer ?

Voir la solution

Dans gitops-repo/ : un fichier environments/preprod/values.yaml (copie de kind avec replicaCount: 1), commit, make gitops-push. Hors du dépôt : une deuxième Application (copie de argocd/application.yaml) avec metadata.name: assistant-preprod, destination.namespace: assistant-preprod, valueFiles: [../../environments/preprod/values.yaml], appliquée par kubectl apply.

Le chart ne change pas : un environnement est un fichier de valeurs, jamais une copie du chart. Gardez aussi releaseName: inference : la NetworkPolicy, le ServiceMonitor et l'analyse canary sélectionnent les pods par les étiquettes app.kubernetes.io/instance. Point d'attention : l'Ingress des deux environnements déclare le même hôte ; changez ingress.host dans les valeurs preprod, sinon NGINX répartit entre les deux.

Résultat obtenu

Exécution du 2026-10-16 (Docker 33 Go, kind 0.33, Kubernetes 1.37) :

$ time make up
...
kube-prometheus-stack 92.9.0 prêt
Argo Rollouts prêt
Argo CD prêt
Image lab-k8s-gitops/inference:0.2.0 chargée
Pas de clé Gemini : le lab tourne en mode simulé (LLM_BACKEND=fake).
Poussé : 68185e5 Déploiement initial : assistant 0.2.0
Application assistant déclarée
make up  4:23,20 total

$ make status
APP         SYNC     SANTE     REVISION
assistant   Synced   Healthy   68185e50985c377d67ca76acfc13be6979b18b1e
NAME                            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
rollout.argoproj.io/inference   3         3         3            3           44s

$ make e2e
[OK] Argo CD : application synchronisée et saine : Synced/Healthy, commit 68185e5
[OK] Argo Rollouts : Rollout sain : Healthy, 3 pods prêts
[OK] aucun Secret dans le dépôt Git
[OK] réponse via l'Ingress : HTTP 200
    backend=fake modèle=fake version=0.2.0 pod=inference-5f6757f64d-wl2cp latence=0 ms
    [fake] Réponse simulée à : Comment signaler un e-mail d'hameçonnage ?
[OK] Prometheus collecte les métriques du service : 1 requêtes comptées
e2e : tout est vert

Le premier make e2e avait échoué sur la dernière étape (« aucune série ») : il interrogeait Prometheus moins de 15 s après la requête, avant la première collecte. Le script attend désormais jusqu'à 60 s.

Empreinte mesurée (docker stats, plateforme au repos) : 1,92 Gio pour le nœud control-plane (Argo CD, Prometheus, Ingress), 1,18 Gio et 1,0 Gio pour les deux workers, soit 4,1 Gio. Pods : 7 dans argocd, 4 dans monitoring, 1 dans argo-rollouts, 1 dans git, 3 pour l'application.

Perte du serveur Git, puis repeuplement :

$ kubectl -n git delete pod -l app=git-server
Unknown Healthy
ComparisonError Failed to load target state: failed to generate manifest for source 1 of 1: rpc error: code = Unknown desc = failed to list refs: remote repository is empty
3                       <- pods de l'application toujours en service
$ make gitops-push
Poussé : 9e8a014 Alertes d'astreinte : 5xx > 5 %, cible absente
assistant   Synced   Healthy   9e8a014a01aac01107fe68b629dc0dd92a367ca1

Je sais faire

  • Expliquer la boucle GitOps : déclaratif, versionné, tiré, réconcilié
  • Créer le dépôt GitOps à partir de gitops-source/ avec make gitops-init
  • Déclarer une Application avec prune, selfHeal et un releaseName fixe
  • Monter la plateforme avec make up et la vérifier avec make status et make e2e
  • Diagnostiquer une ComparisonError sans paniquer : ce qui tourne continue de tourner
التالي