Socle GitOps & bootstrap
Comment le cluster se construit et se réconcilie : un dépôt Git décrit l'état désiré, Argo CD l'applique dans l'ordre. Deux exceptions, bootstrappées à la main une fois — expliquées ici.
Trois cycles de vie, un seul dépôt
Toute l'infra tient dans fga-infrastructure, en dossiers séparés. Argo CD ne surveille que gitops/.
fga-infrastructure/ ├─ provisioning/ # Day 0 : bootstrap k3s, Argo, operators, secrets — HORS Argo (runbook) ├─ dev/ # docker-compose local └─ gitops/ # état désiré du cluster — SEUL chemin surveillé par Argo ├─ charts/ quarkus-service · spa-front ├─ apps/ values par app (svc-reference-data, fga-portail, …) ├─ envs/staging/ Applications plateforme + CR + ApplicationSets (par sync-wave) └─ bootstrap/ app-of-apps racine (root-staging.yaml)
App-of-apps & sync-waves
Une Application racine (app-of-apps) surveille envs/staging/. Argo applique son contenu dans l'ordre des sync-waves : il attend qu'une vague soit Healthy avant la suivante. Les dépendances sont ainsi garanties (le ClusterIssuer attend cert-manager, l'instance Keycloak attend sa base, etc.).
Flux applicatif : CI publie l'image → write-back du tag dans apps/<app>/values-staging.yaml → Argo synchronise. Ajouter un service = un fichier de values + une ligne dans l'ApplicationSet.
Les operators bootstrappés hors GitOps
Quatre operators publient des CRD énormes (ex. clusters, keycloaks, kafkas, prometheuses) dont les annotations dépassent la limite du client-side apply (262144 octets). Argo ne bascule pas ces CRD en server-side apply de façon fiable → la synchro échoue en boucle. On installe donc l'operator en direct (kubectl apply --server-side), une fois, exactement comme Argo CD lui-même. Leurs CR (bases, instance Keycloak, cluster Kafka…) restent, eux, en GitOps.
| Operator | Rôle | Ses CR (en GitOps) |
|---|---|---|
| CloudNativePG | PostgreSQL opéré | Cluster sigfga-pg, keycloak-pg (secrets d'identifiants auto-générés) |
| Keycloak | authentification | Keycloak + KeycloakRealmImport (realm agents) |
| Strimzi | Kafka KRaft (sans ZooKeeper) | Kafka + KafkaNodePool |
| Prometheus operator | observabilité | stack kube-prometheus-stack via Helm (crds.enabled=false) |
Bases de données — une base par service
Chaque service a sa propre base PostgreSQL, déclarée par un Database CR de CloudNativePG (fichier gitops/envs/staging/databases.yaml). Le service s'y connecte et Liquibase migre dans le schéma public (qui existe toujours dans une base neuve) — rien à pré-créer, aucun Job, aucune commande manuelle.
apiVersion: postgresql.cnpg.io/v1
kind: Database # déclaratif, en GitOps — un bloc par service
metadata: { name: claims-db }
spec:
cluster: { name: sigfga-pg }
name: claims
owner: sigfga
Recette d'un service (tout déclaratif) : un bloc Database + un apps/<svc>/values-staging.yaml (base, client Keycloak, Kafka si besoin, ingress.path) + une ligne dans l'ApplicationSet + le build de l'image. Ingress sur un certificat TLS partagé api-tls (émis une fois pour api.fga.antah.dev) — pas un certif par service.
Secrets
| Identifiants BD | générés par CloudNativePG (secret <cluster>-app) → consommés via secretEnv. Zéro saisie. |
| Sealed Secrets | pour les secrets à versionner : kubeseal chiffre avec le certif public du contrôleur ; seul le cluster déchiffre. Sûr dans Git. |
| Bootstrap | quelques secrets posés une fois à la main (accès GHCR, MinIO, Grafana) — non versionnés, comme les credentials Argo↔dépôt. |
Pièges rencontrés (documentés dans le repo)
- CRD trop grosse pour Argo (annotations: Too long) → bootstrapper l'operator hors GitOps (server-side). Voir tableau ci-dessus.
- no matches for kind … alors que la CRD existe → soit la version d'API diffère (réflexe : kubectl api-resources | grep <type> ; ex. Strimzi sert v1, plus v1beta2), soit le cache d'Argo est périmé après un bootstrap de CRD (rollout restart statefulset argocd-application-controller).
- CR d'operator en OutOfSync perpétuel → un champ posé au mauvais endroit est pruné par le schéma de la CRD (ex. les ressources du broker Kafka vont sur le KafkaNodePool, pas sur Kafka.spec.kafka).
- Fronts (SPA) — binaire natif rollup manquant (bug npm des deps optionnelles, npm/cli#4828) → l'image se build sans le lock (résolution fraîche pour la plateforme du runner). Le ci.yml des fronts doit aussi checkout le submodule design-system (token).
- Services (jib) — push GHCR en échec sous concurrence (BLOB_UPLOAD_UNKNOWN) → GHCR perd des sessions d'upload quand trop de couches/images partent en parallèle. Correctif : -Djib.serialize=true (pushs en série) + max-parallel: 2 sur le matrix CI.
- Schéma par service + Liquibase → la table de suivi ne peut pas s'auto-créer son schéma (cf. section Bases de données). Choix retenu : une base par service (CNPG Database CR), déclaratif, sans Job.