Aller au contenu

ArgoCD

ArgoCD est l'outil GitOps qui synchronise en continu les manifestes Kubernetes stockés dans Git avec l'état réel des clusters. Il est installé sur le cluster service (namespace argocd) et pilote l'ensemble des clusters ADEME depuis ce point unique. L'authentification se fait directement via Keycloak (OIDC).


Déploiement Terraform

Élément Emplacement
Run Terraform main-infra/scw-service/terraform/030.argocd
Module main-infra/scw-tf-modules/argocd
Values du chart Helm main-infra/scw-tf-modules/argocd/helm_values/argocd_values.yaml
Version du chart argo-cd scw-init-config/001.config_infrastructure/terraform.tfvars → k8s_helm_charts_versions["service"].argocd

Les values sont un template Terraform (templatefile) : les variables ${CLUSTER_FQDN}, ${KEYCLOAK_URL}, etc. sont injectées par le module à partir de Secret Manager et de la configuration commune.


Composants

graph LR
    U["Utilisateurs / GitLab CI"] --> S["argocd-server (x8)"]
    S --> R["argocd-repo-server (x8)"]
    S --> H["redis-ha-haproxy (x3)"]
    C["application-controller (x3 shards)"] --> R
    C --> H
    A["applicationset-controller (x8)"] --> S
    N["notifications-controller (x1)"] --> H
    H --> RS["redis-ha-server (x3, Sentinel)"]
    C --> K["Clusters gérés"]
    R --> G["Dépôts Git"]
Composant Type Réplicas Rôle
application-controller StatefulSet 3 Cœur d'ArgoCD : compare l'état désiré (Git) et l'état réel des clusters, calcule le statut Synced/Healthy et applique les synchronisations. Les clusters gérés sont répartis entre les réplicas (sharding) : chaque pod ne surveille que les clusters de son shard.
repo-server Deployment 8 Clone les dépôts Git et génère les manifestes (Helm, Kustomize, YAML). Consommation mémoire ponctuelle lors des générations.
server Deployment 8 API et interface web (argocd.<fqdn service>), exposés via Traefik avec liste blanche IP. Gère l'authentification OIDC Keycloak et les comptes locaux (gitlab pour la CI).
applicationset-controller Deployment 8 Génère des Application à partir des ApplicationSet. Un seul réplica est actif à la fois (élection de leader), les autres sont en attente.
notifications-controller Deployment 1 Envoie les notifications sur les événements des applications (sync, échec, dégradation).
redis-ha-server StatefulSet 3 Cache Redis répliqué (1 maître, 2 réplicas) surveillé par Sentinel, qui élit un nouveau maître en cas de panne. Chaque pod contient les conteneurs redis, sentinel et split-brain-fix.
redis-ha-haproxy Deployment 3 Point d'entrée unique de Redis (argocd-redis-ha-haproxy:6379) : redirige toujours vers le maître courant.
redis-secret-init Job (hook Helm) – Génère le secret argocd-redis (mot de passe Redis) à l'installation.
dex-server – désactivé Plus utilisé : le SSO passe directement par Keycloak (configs.cm.oidc.config).

Modifications de novembre 2026

Sujet Avant Après
application-controller 1 réplica 3 réplicas (1 shard par cluster géré, sharding automatique via ARGOCD_CONTROLLER_REPLICAS)
Dex déployé désactivé (dex.enabled: false)
Redis 1 pod argocd-redis Redis HA : 3 serveurs + Sentinel, 3 HAProxy
Tolérances multiaz uniquement multiaz + unreachable / not-ready à 30 s, pour tous les composants
Répartition par zone aucune topologySpreadConstraints sur tous les composants
PodDisruptionBudgets aucun PDB sur tous les composants multi-réplicas
Ressources aucune request ni limit requests / limits mémoire dimensionnées sur l'historique Thanos

Tolérances

Définies une seule fois dans global.tolerations (ancre YAML &argocd_tolerations) et réutilisées pour redis-ha et redis-ha.haproxy, car le sous-chart Redis HA ne lit pas global.tolerations.

- { key: k8s.scaleway.com/multiaz, operator: Equal, value: "true", effect: NoSchedule }
- { key: node.kubernetes.io/unreachable, operator: Exists, effect: NoExecute, tolerationSeconds: 30 }
- { key: node.kubernetes.io/not-ready, operator: Exists, effect: NoExecute, tolerationSeconds: 30 }

Les deux dernières réduisent de 300 s (valeur par défaut Kubernetes) à 30 s le délai avant éviction des pods d'un nœud injoignable : en cas de perte d'un nœud ou d'une zone, les pods sont replanifiés beaucoup plus vite.

Répartition par zone (topologySpreadConstraints)

Chaque composant ArgoCD porte la contrainte suivante (label adapté au composant) :

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    nodeTaintsPolicy: Honor
    matchLabelKeys:            # Deployments uniquement
      - pod-template-hash
    labelSelector:
      matchLabels:
        app.kubernetes.io/name: argocd-server
  • maxSkew: 1 : l'écart entre la zone la plus chargée et la moins chargée ne peut pas dépasser 1 pod (ex. 8 réplicas sur 3 zones → 3/3/2). Avec DoNotSchedule, un pod qui violerait la règle reste Pending.
  • nodeTaintsPolicy: Honor : les nœuds portant un taint non toléré sont exclus du calcul. Conséquences :
    • lors d'une panne de zone, les nœuds unreachable ne comptent plus et les pods évincés peuvent être replanifiés dans les zones restantes ;
    • lors d'un drain, le nœud cordonné est ignoré et le drain ne bloque pas ;
    • la zone fr-par-3, dont l'unique nœud porte le taint k8s.scaleway.com/par-3, est ignorée : les composants se répartissent aujourd'hui sur fr-par-1 et fr-par-2.
  • matchLabelKeys: [pod-template-hash] (Deployments uniquement) : seuls les pods d'une même révision (ReplicaSet) sont comptés, ce qui garantit une répartition équilibrée à chaque rollout. Non applicable à l'application-controller (StatefulSet, pas de label pod-template-hash).

Redis HA : contrainte en ScheduleAnyway

Le sous-chart redis-ha génère lui-même sa contrainte et n'accepte que maxSkew, topologyKey et whenUnsatisfiable : impossible d'y ajouter nodeTaintsPolicy ou matchLabelKeys. Sans Honor, la zone fr-par-3 compte comme une zone vide ; en DoNotSchedule, le 3ᵉ pod Redis et le 3ᵉ HAProxy resteraient Pending indéfiniment (et le helm_release Terraform échouerait en timeout).

Redis HA utilise donc whenUnsatisfiable: ScheduleAnyway : la répartition par zone est une préférence, et l'anti-affinité obligatoire du chart impose toujours un pod par nœud. Si le taint par-3 est un jour remplacé par le taint multiaz, la zone fr-par-3 deviendra utilisable et l'on pourra repasser en DoNotSchedule.

Retour d'une zone après une panne

Les contraintes de répartition ne sont évaluées qu'à la planification : quand une zone revient, les pods déjà en cours d'exécution ne sont pas déplacés (ex. 4/4/0 au lieu de 3/3/2). Le service fonctionne normalement, mais n'est plus réparti. L'équilibre revient au prochain déploiement, ou immédiatement avec :

kubectl -n argocd rollout restart \
  deploy/argocd-server deploy/argocd-repo-server deploy/argocd-applicationset-controller \
  sts/argocd-application-controller

Les nouveaux pods ne peuvent être placés que dans la zone sous-peuplée tant qu'elle n'a pas rattrapé les autres.

PodDisruptionBudgets

PDB maxUnavailable Réplicas Justification
argocd-server 4 8 Drains rapides, la moitié des pods reste disponible
argocd-repo-server 4 8 idem
argocd-applicationset-controller 4 8 idem
argocd-application-controller 1 3 Un seul shard (donc un seul cluster géré) indisponible à la fois
argocd-redis-ha-pdb 1 3 2 serveurs sur 3 toujours disponibles : Sentinel garde son quorum
argocd-redis-ha-haproxy-pdb 1 3 Point d'entrée Redis toujours disponible

Le notifications-controller n'a volontairement pas de PDB : avec un seul réplica, tout PDB protecteur bloquerait les drains de nœuds (mises à jour Kapsule, autoscaler). Un redémarrage pendant un drain est sans impact.

maxUnavailable est préféré à minAvailable car il reste valable si le nombre de réplicas change.

Ressources

Dimensionnées à partir de l'historique Thanos du cluster service (p95/p99 sur 7 jours, max sur 30 jours, pire pod de chaque composant). Pas de limite CPU (le throttling ralentirait la réconciliation) ; la mémoire est réservée à hauteur de la limite.

Conteneur Mesuré CPU (p95 / max) Mesuré mémoire (p95 / max 30 j) Request CPU Request = limit mémoire
application-controller (x3) 2,44 / 4,58 cores* 4,8 Gi / 7,2 Gi* 1 8Gi
repo-server (x8) 0,19 / 0,22 504 Mi / 1,06 Gi 100m 1536Mi
server (x8) 0,02 / 0,14 213 Mi / 363 Mi 50m 512Mi
applicationset-controller (x8) 0,012 / 0,022 118 Mi / 169 Mi 25m 256Mi
notifications-controller (x1) 0,013 / 0,023 161 Mi / 255 Mi 25m 384Mi
redis-ha redis (x3) 0,015 / 0,022** 784 Mi / 1,06 Gi** 100m 1536Mi
redis-ha haproxy (x3) non mesuré non mesuré 25m 128Mi

* Mesuré avec un seul controller gérant tous les clusters. Les shards ne sont pas équilibrés (le cluster horsprod porte la majorité des objets), d'où une marge large. ** Mesuré sur l'ancien Redis mono-nœud. Chaque serveur Redis HA contient l'intégralité des données, la mémoire par pod est donc identique.

Total réservé sur le namespace : environ 4,8 cores et 47 Gi de mémoire.

Redis HA sans volume persistant

Redis n'est qu'un cache pour ArgoCD (arbre des ressources, manifestes générés, cache des clusters) : tout est reconstructible depuis Git et les clusters. Le chart le déploie donc sans PVC (persistentVolume.enabled: false, volume emptyDir, snapshots désactivés save "").

  • Redémarrage d'un pod : il repart vide et se resynchronise depuis le maître ; Sentinel élit un nouveau maître si besoin. Transparent pour ArgoCD.
  • Redémarrage des 3 pods : cache vide, UI et réconciliation plus lentes quelques minutes. Aucune perte de configuration (stockée dans les CRDs et secrets ArgoCD).

Un PVC serait contre-productif : les volumes Block Storage Scaleway sont zonaux et figeraient chaque pod Redis dans sa zone.


Points de vigilance

  • Application du changement : le passage à Redis HA modifie l'adresse Redis (argocd-redis-ha-haproxy:6379) ; tous les composants redémarrent et le cache se reconstruit. Prévoir quelques minutes de lenteur.
  • Re-mesurer l'application-controller environ deux semaines après la mise en place du sharding, puis réduire la mémoire si possible.
  • Dimensionner HAProxy et Sentinel après quelques jours de fonctionnement (valeurs de départ / non définies).
  • Nombre de réplicas : server et applicationset-controller sont largement surdimensionnés à 8 (quelques millicores par pod ; un seul applicationset-controller actif). 3 réplicas suffiraient.
  • Zone fr-par-3 : tant que son nœud porte le taint par-3, ArgoCD tourne sur deux zones seulement. Le remplacer par le taint multiaz permettrait une répartition sur les trois zones.

Liens utiles