Lab : Signalements en GitOps avec Argo CD
Ce lab rassemble le cours GitOps avec Argo CD. Vous montez, dans le laboratoire local de la leçon 1, la plateforme de déploiement de l'application Signalements telle qu'elle est à la fin du cours, puis un script vérifie dans le cluster qu'elle a les propriétés attendues. Il valide le niveau 200 de la compétence « GitOps », et contribue aux niveaux 200 de « Stratégies de déploiement » et « Gestion des secrets ».
Comptez une demi-journée. Toutes les pièces sont dans les leçons ; essayez d'écrire chacune avant d'ouvrir la leçon correspondante.
Ce qu'il faut
- Le laboratoire de la leçon 1 : un cluster kind, Argo CD 3.5 et la forge Forgejo, avec les dépôts
plateforme/signalements-deploiementetplateforme/gitops. - Les images de Signalements 1.1.0 et 1.2.0 chargées dans le nœud.
- Pour le secret : Sealed Secrets installé dans le cluster et l'outil
kubesealsur votre poste. Le lab utilise Sealed Secrets parce qu'il ne demande aucun service externe ; si vous disposez d'un gestionnaire de secrets, un ExternalSecret convient aussi. kubectl,argocdetjq.
Le lab n'a pas besoin du second cluster de la leçon 7.
Ce qu'il faut atteindre
plateforme/gitops
├── amorce/ appliqué une fois à la main
│ ├── plateforme.yaml projet de l'équipe plateforme (leçon 9)
│ └── racine.yaml application racine, projet plateforme (leçon 4)
└── geres/ tout ce que la racine déploie (directory.recurse)
├── projets/
│ ├── default.yaml projet default vidé (leçon 9)
│ └── signalements.yaml projet cloisonné, abonnements aux notifications (leçons 9, 10)
├── configuration/
│ └── argocd-rbac-cm.yaml politique sans droits par défaut (leçon 9)
└── applications/
└── signalements.yaml ApplicationSet protégé (leçon 6)
plateforme/signalements-deploiement
├── chart/templates/ base en vague -1, migration en hook Sync,
│ application en vague 1, test de fumée PostSync (leçon 5),
│ SealedSecret en vague -2 (leçon 8)
└── environnements/
├── recette/values.yaml dont le mot de passe chiffré par kubeseal --raw (leçon 8)
└── production/values.yamlLes exigences
| Exigence | Leçon |
|---|---|
Une application racine, dans un projet plateforme et synchronisée automatiquement, crée tout le reste depuis le dépôt gitops | 4, 9 |
Le projet default ne permet plus rien | 9 |
Le projet signalements n'autorise aucune ressource de portée cluster et seulement les namespaces de Signalements | 9 |
Un ApplicationSet en modèles Go avec missingkey=error génère recette et production | 6 |
L'ApplicationSet ne supprime jamais une application ni ses ressources (preserveResourcesOnDeletion, applicationsSync: create-update) | 6 |
| La recette se synchronise automatiquement avec auto-réparation ; la production se synchronise à la main | 3, 4 |
Recette et production sont Synced et Healthy, dans le projet signalements | 3 |
PostgreSQL est en vague -1, la migration est un hook Sync, un test de fumée tourne en PostSync | 5 |
| Le mot de passe de la base n'est pas dans Git : le Secret est produit dans le cluster par un SealedSecret (ou un ExternalSecret) | 8 |
policy.default est vide | 9 |
| Les échecs de synchronisation du projet sont notifiés | 10 |
Le namespace de chaque environnement est créé par vous, pas par Argo CD : le projet signalements interdit les ressources de portée cluster, donc retirez CreateNamespace=true du modèle de l'ApplicationSet de la leçon 6, sinon la synchronisation est refusée (leçon 9).
Le projet signalements de la leçon 9 est écrit pour la plateforme de la leçon 7. Dans le lab, à un seul cluster : gardez l'adresse du dépôt dans le cluster (http://forgejo.forge.svc.cluster.local:3000/...), et remplacez la destination production par in-cluster.
Étape 1 : la structure
Partez du dépôt gitops de la leçon 6 et réorganisez-le comme ci-dessus. La racine ne peut pas vivre dans le projet default que vous allez vider, ni déployer le projet qui l'autorise : le projet plateforme (sources : le dépôt gitops ; destination : in-cluster, namespace argocd ; projets, ApplicationSet et ConfigMap d'Argo CD sont des ressources de namespace, aucune ressource de portée cluster n'est nécessaire) et la racine sont appliqués à la main, une fois, depuis amorce/. La racine pointe sur geres/ avec directory: {recurse: true}. Créez ensuite les projets, déplacez-y les applications (le modèle de l'ApplicationSet indique project: signalements), vérifiez qu'aucune application ne reste dans default (argocd app list -p default), puis seulement videz default. Rendez la production manuelle par un templatePatch qui n'ajoute automated qu'à la recette, et vérifiez le résultat avec argocd appset generate avant de pousser.
Étape 2 : le déploiement ordonné
Reprenez le chart de la leçon 5 : base, migration, application, test de fumée. Faites une livraison de 1.1.0 vers 1.2.0 en recette et observez l'ordre des vagues avec argocd app get signalements-recette, puis promouvez en production par une demande de fusion et une synchronisation manuelle.
Étape 3 : le secret, le RBAC, les notifications
Retirez le mot de passe de values.yaml, chiffrez un nouveau mot de passe avec kubeseal --raw (portée strict, nom signalements-postgresql, namespace signalements-recette), rangez le texte chiffré dans environnements/recette/values.yaml, faites produire le SealedSecret par le chart en vague -2 (un fichier déposé à part dans environnements/ ne serait jamais appliqué, leçon 8), et faites lire le Secret par la base et l'application (secretKeyRef). Le mot de passe ayant été commité à la leçon 5, changez-le aussi dans la base (ALTER ROLE), comme le décrit la leçon 8. Gérez argocd-rbac-cm depuis le dépôt gitops, avec policy.default vide. Abonnez le projet à on-sync-failed : un service webhook vers un récepteur local suffit dans le laboratoire.
Vérifier
Le script suivant contrôle les exigences dans le cluster. Il ne modifie rien : il lit les objets d'Argo CD avec kubectl et les manifestes rendus avec argocd. Lancez-le avec le kubeconfig et la configuration argocd du laboratoire.
#!/usr/bin/env bash
# Vérifie les propriétés attendues du lab GitOps avec Argo CD.
# Usage : ./verifier-lab.sh (exige kubectl, argocd et jq, connectés au laboratoire)
# Chaque contrôle affiche OK ou ÉCHEC ; le code de sortie est le nombre d'échecs.
set -uo pipefail
echecs=0
controle() { # controle "libellé" commande...
local libelle=$1; shift
if "$@" > /dev/null 2>&1; then
echo "OK $libelle"
else
echo "ÉCHEC $libelle"
echecs=$((echecs + 1))
fi
}
controle "l'application racine se synchronise automatiquement, hors du projet default" \
bash -c 'kubectl -n argocd get application racine -o json | jq -e ".spec.syncPolicy.automated != null and .spec.project != \"default\""'
controle "le projet default ne permet plus rien" \
bash -c 'kubectl -n argocd get appproject default -o json | jq -e "((.spec.sourceRepos // []) == []) and ((.spec.destinations // []) == [])"'
controle "le projet signalements n'autorise aucune ressource de portée cluster" \
bash -c 'kubectl -n argocd get appproject signalements -o json | jq -e "(.spec.clusterResourceWhitelist // []) == []"'
controle "le projet signalements ne vise que les namespaces de Signalements" \
bash -c 'kubectl -n argocd get appproject signalements -o json | jq -e "[.spec.destinations[].namespace] | length > 0 and all(test(\"^(signalements-|apercu-)\"))"'
controle "l'ApplicationSet utilise les modèles Go avec missingkey=error" \
bash -c 'kubectl -n argocd get applicationset signalements -o json | jq -e ".spec.goTemplate == true and ((.spec.goTemplateOptions // []) | index(\"missingkey=error\") != null)"'
controle "l'ApplicationSet ne supprime ni applications ni ressources" \
bash -c 'kubectl -n argocd get applicationset signalements -o json | jq -e ".spec.syncPolicy.preserveResourcesOnDeletion == true and .spec.syncPolicy.applicationsSync == \"create-update\""'
for env in recette production; do
app=signalements-$env
controle "$app : générée par l'ApplicationSet, dans le projet signalements" \
bash -c "kubectl -n argocd get application $app -o json | jq -e '.spec.project == \"signalements\" and (.metadata.ownerReferences[0].kind == \"ApplicationSet\")'"
controle "$app : Synced et Healthy" \
bash -c "kubectl -n argocd get application $app -o json | jq -e '.status.sync.status == \"Synced\" and .status.health.status == \"Healthy\"'"
controle "$app : sans finalizer de suppression en cascade" \
bash -c "kubectl -n argocd get application $app -o json | jq -e '(.metadata.finalizers // []) | index(\"resources-finalizer.argocd.argoproj.io\") == null'"
done
controle "la recette se synchronise automatiquement, avec auto-réparation" \
bash -c 'kubectl -n argocd get application signalements-recette -o json | jq -e ".spec.syncPolicy.automated.selfHeal == true"'
controle "la production se synchronise à la main" \
bash -c 'kubectl -n argocd get application signalements-production -o json | jq -e ".spec.syncPolicy.automated == null"'
manifestes=$(argocd app manifests signalements-recette --source git 2>/dev/null)
controle "les manifestes de la recette sont rendus" \
test -n "$manifestes"
controle "PostgreSQL est en vague -1" \
grep -q 'argocd.argoproj.io/sync-wave: "-1"' <<< "$manifestes"
controle "la migration est un hook Sync" \
grep -q 'argocd.argoproj.io/hook: Sync$' <<< "$manifestes"
controle "un test de fumée tourne en PostSync" \
grep -q 'argocd.argoproj.io/hook: PostSync$' <<< "$manifestes"
controle "aucun Secret en clair dans les manifestes de la recette" \
bash -c '! grep -q "^kind: Secret$" <<< "$1"' _ "$manifestes"
controle "le Secret de la base est produit dans le cluster" \
bash -c 'kubectl -n signalements-recette get secret signalements-postgresql -o json | jq -e "[.metadata.ownerReferences[]?.kind] | index(\"SealedSecret\") != null or index(\"ExternalSecret\") != null"'
controle "policy.default est vide" \
bash -c 'kubectl -n argocd get configmap argocd-rbac-cm -o json | jq -e "(.data[\"policy.default\"] // \"\") == \"\""'
controle "les échecs de synchronisation du projet sont notifiés" \
bash -c 'kubectl -n argocd get appproject signalements -o json | jq -e ".metadata.annotations // {} | keys | any(startswith(\"notifications.argoproj.io/subscribe.on-sync-failed.\"))"'
echo
echo "$echecs échec(s)"
exit "$echecs"Le script vérifie des propriétés, pas des fichiers identiques à ceux du cours : une autre organisation des dépôts est acceptée tant qu'elle les respecte. Pour éprouver le script, abîmez volontairement une copie de votre configuration (remettez policy.default: role:readonly, retirez missingkey=error, ajoutez un Secret en clair au chart) et vérifiez que chaque défaut est signalé.
Pour le niveau 300 de la compétence « GitOps », ajoutez une revue : faites relire votre dépôt gitops par une autre personne, en lui demandant de trouver un chemin par lequel un développeur de Signalements pourrait déployer hors des namespaces de son projet.
Nettoyage
Le laboratoire tient dans un cluster kind et un kubeconfig isolé : kind delete cluster --name gitops --kubeconfig <kubeconfig du laboratoire> supprime tout, Argo CD et la forge compris. Supprimez ensuite les images de Signalements que vous avez construites, nommément (docker image rm registre.lyneko.example/signalements:1.1.0 ...), sans prune global si la machine sert à d'autres projets, et vérifiez que votre contexte kubectl courant n'a pas changé.