Article principal · Ce tutoriel fait partie de la série Kubernetes domestique sur Proxmox VE. Tutoriel précédent : Rook-Ceph stockage. Tutoriel suivant : kube-prometheus-stack observabilité.
Déployer manuellement avec kubectl apply fonctionne pour deux ou trois charges. Au-delà, l état du cluster diverge insidieusement de ce qu on pense avoir déclaré, les rollbacks deviennent risqués, et on perd l historique des changements. Le pattern GitOps résout ces trois problèmes en plaçant le dépôt Git comme source de vérité unique : le cluster ne fait que synchroniser ce qui est commité. Argo CD est l implémentation de référence de ce pattern. La version 3.4, livrée mai 2026, ajoute un Application Controller multi-thread plus rapide et une meilleure gestion des références ApplicationSet. Ce tutoriel installe Argo CD 3.4, structure un dépôt Git, crée la première Application qui déploie une charge depuis Git, puis met en place une ApplicationSet qui décline la même charge sur plusieurs namespaces.
Étape 1 — Préparer le dépôt Git source de vérité
Le dépôt Git est la pièce centrale. Il peut être hébergé sur GitHub, GitLab, Gitea, Forgejo ou tout serveur SSH. Pour un homelab souverain, l idéal est de l héberger soi-même, mais on peut commencer sur un GitHub privé en attendant que Gitea soit déployé.
mkdir homelab-gitops && cd homelab-gitops
git init
mkdir -p apps clusters/homelab/bootstrap clusters/homelab/infra clusters/homelab/apps
# Structure recommandée :
# apps/ — manifestes Helm/Kustomize par application
# clusters/homelab/ — définition Argo CD du cluster
# bootstrap/ — Application racine (app-of-apps)
# infra/ — Cilium, Rook, cert-manager, MetalLB
# apps/ — Vaultwarden, Nextcloud, Gitea...
echo "# Homelab GitOps" > README.md
git add . && git commit -m "Initial structure"
# Pousser vers le dépôt distant (créer le dépôt manuellement avant)
git remote add origin git@github.com:moncompte/homelab-gitops.git
git branch -M main
git push -u origin main
La structure crée trois niveaux : apps/ pour les manifestes réutilisables, clusters/homelab/ pour la configuration spécifique du cluster, et trois sous-dossiers bootstrap/, infra/, apps/ qui correspondent aux trois grandes catégories de charges. Cette organisation se révèle robuste à l échelle, parce qu elle permet d ajouter d autres clusters (clusters/edge1/, clusters/labo/) sans réorganiser le dépôt.
Étape 2 — Installer Argo CD 3.4 via Helm
L installation d Argo CD se fait par Helm pour permettre un upgrade simple. Le chart officiel est dans le dépôt argoproj.
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
kubectl create namespace argocd
helm install argocd argo/argo-cd \
--version 9.5.12 \
--namespace argocd \
--set server.service.type=ClusterIP \
--set server.extraArgs[0]=--insecure \
--set configs.params.server.insecure=true \
--set redis-ha.enabled=false \
--set controller.replicas=1 \
--set server.replicas=2 \
--set repoServer.replicas=2 \
--set applicationSet.enabled=true \
--set notifications.enabled=true
Le chart 9.5.12 du dépôt argo-helm livre Argo CD 3.4.x. L option --insecure sur le serveur évite la double couche TLS quand on l expose ensuite derrière un Ingress qui termine le TLS. Compter trois minutes pour que tous les pods soient en Running.
Étape 3 — Récupérer le mot de passe admin et accéder à l UI
Argo CD génère automatiquement un compte admin avec un mot de passe initial stocké dans un Secret. Le récupérer puis se connecter via port-forward.
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d ; echo
kubectl -n argocd port-forward svc/argocd-server 8080:80 &
# Ouvrir http://localhost:8080 dans le navigateur, login admin, mot de passe ci-dessus
L interface affiche un écran « Aucune Application ». C est normal : le cluster n a pas encore d Application synchronisée. Avant tout, changer le mot de passe admin via User Info → Update Password, ou mieux, désactiver l auth interne et utiliser le SSO (Keycloak ou Authentik) — pratique standard en production.
Étape 4 — Connecter Argo CD au dépôt Git
Argo CD doit pouvoir cloner le dépôt source de vérité. Pour un dépôt public en lecture seule, rien à faire. Pour un dépôt privé via SSH, il faut fournir une clé déployée. Pour HTTPS avec token, créer le Secret approprié.
apiVersion: v1
kind: Secret
metadata:
name: homelab-gitops-repo
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: git@github.com:moncompte/homelab-gitops.git
sshPrivateKey: |
-----BEGIN OPENSSH PRIVATE KEY-----
[contenu de la clé privée déployée en read-only sur GitHub]
-----END OPENSSH PRIVATE KEY-----
kubectl apply -f homelab-gitops-repo.yaml
# Vérifier dans l UI Argo CD : Settings → Repositories
# Le dépôt doit apparaître avec l état CONNECTION SUCCESSFUL
L état Connection Successful confirme qu Argo CD peut cloner le dépôt. À ce stade, on peut créer la première Application qui pointera vers un sous-dossier du dépôt.
Étape 5 — Créer la première Application
Une Application Argo CD est un objet déclaratif qui dit : « surveille tel chemin de tel dépôt et applique le résultat dans tel namespace ». Le manifest ci-dessous déploie un démonstrateur podinfo depuis un sous-dossier du dépôt.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: podinfo-demo
namespace: argocd
spec:
project: default
source:
repoURL: git@github.com:moncompte/homelab-gitops.git
targetRevision: main
path: apps/podinfo-demo
destination:
server: https://kubernetes.default.svc
namespace: demo
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
kubectl apply -f podinfo-app.yaml
# Dans l UI Argo CD, l Application apparaît
# Statut Sync : Synced (vert), Health : Healthy
kubectl get pods -n demo
# Le pod podinfo doit être en Running
Le pod podinfo qui apparaît dans le namespace demo a été créé sans kubectl apply manuel : Argo CD l a synchronisé depuis Git. Si l on commit un changement sur la branche main (par exemple porter le nombre de réplicas de 1 à 3), Argo CD détecte la dérive en moins de trois minutes et applique le changement. Le rollback est tout aussi simple : git revert et c est synchronisé en sens inverse.
Étape 6 — Mettre en place l app-of-apps
Créer chaque Application à la main devient pénible quand on en a vingt. Le pattern app-of-apps consiste à créer une Application racine qui déploie elle-même toutes les autres Applications du cluster. C est récursif et auto-suffisant : un seul kubectl apply initial suffit, ensuite tout vit dans Git.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: bootstrap-homelab
namespace: argocd
spec:
project: default
source:
repoURL: git@github.com:moncompte/homelab-gitops.git
targetRevision: main
path: clusters/homelab/bootstrap
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
Le dossier clusters/homelab/bootstrap/ contient ensuite les manifestes Application qui pointent vers infra/ et apps/. Argo CD les découvre récursivement et synchronise tout. C est cette structure qui permet de reconstruire un cluster complet en deux commandes : helm install argocd puis kubectl apply -f bootstrap-homelab.yaml.
Étape 7 — Décliner sur plusieurs environnements avec ApplicationSet
Quand on a deux clusters (homelab et un labo), copier-coller chaque Application devient absurde. ApplicationSet génère dynamiquement les Applications à partir d un générateur (liste, matrice, cluster, Git directory).
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: monitoring-everywhere
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: homelab
url: https://kubernetes.default.svc
- cluster: labo
url: https://10.0.1.10:6443
template:
metadata:
name: '{{cluster}}-kube-prometheus-stack'
spec:
project: default
source:
repoURL: git@github.com:moncompte/homelab-gitops.git
targetRevision: main
path: clusters/{{cluster}}/infra/kube-prometheus-stack
destination:
server: '{{url}}'
namespace: monitoring
syncPolicy:
automated:
prune: true
Avec un seul ApplicationSet, deux Applications sont créées : homelab-kube-prometheus-stack et labo-kube-prometheus-stack, chacune pointant vers son propre dossier. Ajouter un troisième cluster prend trois lignes au lieu de dupliquer trente lignes de YAML.
Étape 8 — Vérifier la chaîne complète après un commit
Le test final consiste à modifier un manifest dans Git et vérifier que le cluster évolue. C est le critère de fonctionnement réel d une chaîne GitOps.
# Sur le poste, modifier le nombre de réplicas
cd homelab-gitops
yq -i '.spec.replicas = 3' apps/podinfo-demo/deployment.yaml
git add apps/podinfo-demo/deployment.yaml
git commit -m "Scale podinfo to 3 replicas"
git push origin main
# Argo CD détecte le changement en moins de 3 minutes (intervalle de polling par défaut)
kubectl get pods -n demo -l app=podinfo -w
# Doit passer de 1 pod Running à 3 pods Running
Voir les trois pods apparaître sans avoir lancé une seule commande kubectl scale confirme que la boucle GitOps est complète : Git → Argo CD → cluster. À partir de maintenant, toute modification d infrastructure ou d application passe par un commit, jamais par une commande directe sur le cluster.
Erreurs fréquentes et résolutions
| Symptôme | Cause | Résolution |
|---|---|---|
| L Application reste en OutOfSync indéfiniment | Drift entre Git et le cluster, mais sync auto désactivée | Cliquer Sync manuellement ou activer syncPolicy.automated |
| Connection Failed sur le repository | Mauvaise clé SSH, ou clé non déployée sur GitHub | Régénérer la clé, l ajouter dans Settings → Deploy keys du dépôt |
| Le sync échoue avec « namespace not found » | Namespace inexistant et CreateNamespace=true non activé | Ajouter syncOptions: [CreateNamespace=true] |
| Argo CD bouge les ressources mais ne les supprime pas | prune: false par défaut |
Activer prune: true dans automated |
| L UI Argo CD est lente sur un gros cluster | Application Controller saturé | Augmenter controller.replicas et activer le sharding |
Pour la suite du parcours
Le déploiement est désormais piloté par Git. Toute charge ajoutée au dépôt est automatiquement appliquée au cluster, et toute modification est tracée dans l historique Git. La couche manquante pour opérer sereinement est l observabilité : sans Prometheus ni Grafana, on déploie à l aveugle. Le tutoriel Observabilité Kubernetes avec kube-prometheus-stack détaille l installation du chart 84.5.0 et la configuration des dashboards essentiels. Pour la vue d ensemble, retourner au guide principal Kubernetes domestique sur Proxmox VE.
Ressources et documentation officielle
- Documentation Argo CD : argo-cd.readthedocs.io
- Notes de version Argo CD 3.4 : github.com/argoproj/argo-cd/releases
- Pattern app-of-apps : argo-cd.readthedocs.io/cluster-bootstrapping
- ApplicationSet specification : argo-cd.readthedocs.io/applicationset
- Charts Helm Argo : artifacthub.io/argo-cd
- Référence Application CRD : argo-cd.readthedocs.io/declarative-setup
Étape 9 — Sécuriser les secrets dans Git avec Sealed Secrets
Le pattern GitOps a un défaut évident : on ne peut pas committer les Secrets Kubernetes en clair. Les mots de passe, les tokens d API, les certificats privés ne doivent jamais arriver dans Git. Plusieurs solutions existent ; la plus simple à opérer en homelab est sealed-secrets de Bitnami. Le principe : un contrôleur dans le cluster détient une clé privée qui peut déchiffrer des SealedSecret committés en Git. On chiffre côté poste de travail, on commit, le contrôleur déchiffre dans le cluster.
# Installer sealed-secrets via Helm
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets \
--namespace kube-system
# Installer la CLI kubeseal sur le poste
KUBESEAL_VERSION=0.36.6
curl -OL https://github.com/bitnami-labs/sealed-secrets/releases/download/v\$\{KUBESEAL_VERSION\}/kubeseal-\$\{KUBESEAL_VERSION\}-linux-amd64.tar.gz
tar xfz kubeseal-\$\{KUBESEAL_VERSION\}-linux-amd64.tar.gz
sudo install kubeseal /usr/local/bin/
# Chiffrer un secret existant
kubectl create secret generic db-password \
--from-literal=password=monsecret \
--dry-run=client -o yaml | \
kubeseal --format yaml > apps/myapp/db-password.sealed.yaml
# Le fichier sealed peut être commité en clair dans Git, il est inutilisable hors du cluster
Voir le fichier db-password.sealed.yaml contenant un blob chiffré confirme que la chaîne fonctionne. Quand Argo CD synchronisera, il créera un SealedSecret, que le contrôleur transformera en Secret Kubernetes utilisable. La clé privée du contrôleur doit absolument être sauvegardée hors du cluster (Velero le fera, mais une copie manuelle initiale est prudente).
Étape 10 — Activer les notifications de synchronisation
Sans notification, on découvre les échecs de synchronisation au pire moment, en cliquant dans l UI. Le composant argocd-notifications intégré au chart envoie des messages vers Slack, Discord, email, ou n importe quel webhook quand un événement se produit.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.webhook.discord: |
url: https://discord.com/api/webhooks/...
headers:
- name: Content-Type
value: application/json
template.app-sync-failed: |
webhook:
discord:
method: POST
body: |
{"content": "🔴 {{.app.metadata.name}} sync failed: {{.app.status.operationState.message}}"}
trigger.on-sync-failed: |
- description: Application sync failed
send: [app-sync-failed]
when: app.status.operationState.phase in ['Error', 'Failed']
Avec cette configuration, chaque échec de synchronisation déclenche un message Discord. C est suffisant pour réagir en quelques minutes plutôt qu en quelques jours. Pour les utilisateurs sans Discord, le webhook fonctionne avec n importe quel service compatible (Mattermost auto-hébergé, Matrix, Pushover).
Étape 11 — Comprendre les pièges du selfHeal
L option selfHeal: true est puissante mais a un effet de bord à connaître. Si un opérateur applique manuellement un patch d urgence sur une ressource gérée par Argo CD (par exemple kubectl scale pour résoudre un incident), Argo CD va annuler ce patch lors de la prochaine synchronisation, parce qu il ne correspond plus à Git. C est par dessein, mais cela peut surprendre.
Deux contournements existent. Le premier consiste à annoter la ressource avec argocd.argoproj.io/sync-options: IgnoreExtraneous=true ou désactiver temporairement selfHeal sur l Application concernée. Le second, plus propre, consiste à committer le patch d urgence dans Git, ce qui réconcilie l état désiré avec l état réel et restaure la cohérence.
En pratique, on apprend à ne plus jamais utiliser kubectl apply sur les ressources gérées par Argo CD. Toute modification passe par un commit, même les corrections rapides. Cette discipline transforme l opération du cluster : on retrouve l historique de chaque changement, on peut rejouer une configuration trois mois plus tard, on peut diffuser une correction sur dix clusters via un seul ApplicationSet.
Étape 12 — Vérifier que Argo CD se gère lui-même
L étape ultime du GitOps est de faire gérer Argo CD par Argo CD lui-même. Le chart Helm est ajouté au dépôt, une Application pointe dessus, et toute mise à jour d Argo CD passe par un commit sur Git. C est un peu vertigineux la première fois mais c est cohérent.
# Ajouter le chart Argo CD au dépôt
mkdir -p clusters/homelab/infra/argocd
cat > clusters/homelab/infra/argocd/application.yaml <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd-self
namespace: argocd
spec:
project: default
source:
repoURL: https://argoproj.github.io/argo-helm
targetRevision: 9.5.12
chart: argo-cd
helm:
valueFiles:
- $values/clusters/homelab/infra/argocd/values.yaml
sources:
- repoURL: git@github.com:moncompte/homelab-gitops.git
ref: values
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
selfHeal: true
EOF
git add clusters/homelab/infra/argocd
git commit -m "Self-manage Argo CD via GitOps"
git push
Une fois cette Application synchronisée, mettre à jour Argo CD revient à modifier targetRevision: 9.5.12 en 8.5.0 et committer. Le contrôleur upgrade lui-même son propre déploiement de manière transactionnelle. C est l aboutissement de la chaîne GitOps : tout le cluster, y compris l outil qui le synchronise, vit dans Git.