Aller au contenu
Plusieurs clusters

Plusieurs clusters

300 Concevoir ⏱ 1 h 10 argocdkubernetesgitops

À la fin, vous saurez

  • Enregistrer un cluster cible dans Argo CD avec des droits limités à ses namespaces
  • Expliquer quels identifiants argocd cluster add enregistre réellement, et pourquoi c'est un risque
  • Répartir des environnements sur plusieurs clusters avec le générateur de clusters
  • Déplacer une application d'un cluster à un autre sans laisser d'orphelins ni sauter de hooks
  • Bloquer les synchronisations de production hors des créneaux autorisés avec des fenêtres de synchronisation

Prérequis

Testé avec argocd 3.5.3 kubernetes 1.36.4 , vérifié le 2 octobre 2026

Pourquoi

Jusqu'ici, recette et production de Signalements vivaient dans le même cluster qu'Argo CD, séparées par un namespace. C'est le point de départ de beaucoup d'équipes, et il a des limites nettes : un incident de cluster (un nœud saturé, une mise à jour ratée, un contrôleur d'admission en panne) touche la recette et la production ; une erreur de droits dans la recette peut atteindre la production ; et certains clients exigent que leur production tourne sur un cluster qui leur est propre, parfois dans un autre projet ou une autre organisation cloud.

Argo CD sait piloter plusieurs clusters depuis une seule instance. Mais enregistrer un cluster, c'est confier à Argo CD des identifiants capables de le modifier : cette leçon s'attache autant à ce que l'on donne à Argo CD qu'à la manière de répartir les applications.

Les concepts

Hub et rayons

    flowchart LR
  G[(Dépôts Git)] --> A
  subgraph hub["Cluster d'Argo CD (hub)"]
    A[Argo CD] --> R[signalements-recette]
  end
  A -->|"API Kubernetes,<br/>jeton du cluster cible"| P
  subgraph prod["Cluster de production"]
    P[signalements-production]
  end
  

Dans le modèle le plus courant, une instance d'Argo CD (le hub) se connecte à l'API des clusters cibles (les rayons) et y applique les manifestes. Conséquences directes :

  • le hub doit joindre l'API de chaque cluster cible ;
  • le hub détient un identifiant par cluster cible, stocké dans un secret Kubernetes ;
  • les clusters cibles n'ont pas besoin d'accéder à Git : c'est le repo-server du hub qui lit les dépôts. Ils doivent seulement pouvoir tirer leurs images.

L'alternative est une instance d'Argo CD par cluster, chacune ne gérant que le sien : aucun identifiant ne quitte le cluster, mais il y a autant d'instances à exploiter. Entre les deux, le projet argocd-agent inverse le sens de la connexion : un agent installé dans chaque cluster cible se connecte au hub. Il est encore hébergé dans argoproj-labs et en version 0.x (0.10.0 en août 2026) : à suivre, pas encore à mettre en production sans précaution.

Un cluster, c'est un secret

Argo CD ne connaît un cluster que par un secret de son namespace portant l'étiquette argocd.argoproj.io/secret-type: cluster. Ses clés principales :

CléRôle
nameNom du cluster, utilisé par destination.name
serverURL de l'API, telle que le hub la joint
configJSON des identifiants : bearerToken, tlsClientConfig (caData, certData, keyData), ou execProviderConfig / awsAuthConfig
namespacesListe des namespaces gérés, séparés par des virgules ; vide = tout le cluster
clusterResources"true" pour gérer aussi les ressources de portée cluster quand namespaces est renseigné

Les étiquettes du secret servent ensuite au générateur de clusters des ApplicationSet.

En pratique

Pour cette leçon, un second cluster kind, gitops-prod, a été créé sur le même réseau Docker que le premier, avec la même image de nœud. Les deux contextes sont dans le kubeconfig isolé du laboratoire : kind-gitops (le hub) et kind-gitops-prod.

kind create cluster --name gitops-prod --image "$NOEUD" --kubeconfig "$KUBECONFIG" --wait 120s
kind load docker-image --name gitops-prod registre.lyneko.example/signalements:1.2.0

Warning

Sous Linux, un second cluster kind dépasse souvent la limite fs.inotify.max_user_instances de l'hôte (128 par défaut sous Ubuntu). Le cluster démarre, mais kube-proxy redémarre en boucle avec too many open files, et les Services ne fonctionnent plus : CoreDNS reste non prêt, et un test de fumée échoue sur Resolving timed out. C'est ce qui est arrivé pendant la préparation de cette leçon. La documentation de kind recommande sudo sysctl fs.inotify.max_user_instances=512 (et fs.inotify.max_user_watches=524288) avant de créer le cluster.

Ce que fait argocd cluster add

La méthode documentée en premier est la commande argocd cluster add, qui prend un contexte du kubeconfig :

$ argocd cluster add kind-gitops-prod --name production --yes
{"level":"info","msg":"ServiceAccount \"argocd-manager\" created in namespace \"kube-system\"","time":"2026-10-02T10:19:14+02:00"}
{"level":"info","msg":"ClusterRole \"argocd-manager-role\" created","time":"2026-10-02T10:19:14+02:00"}
{"level":"info","msg":"ClusterRoleBinding \"argocd-manager-role-binding\" created","time":"2026-10-02T10:19:14+02:00"}
{"level":"info","msg":"Created bearer token secret \"argocd-manager-long-lived-token\" for ServiceAccount \"argocd-manager\"","time":"2026-10-02T10:19:14+02:00"}
{"level":"fatal","msg":"rpc error: code = Unknown desc = error getting server version: failed to get server version: Get \"https://127.0.0.1:43675/version?timeout=32s\": dial tcp 127.0.0.1:43675: connect: connection refused","time":"2026-10-02T10:19:15+02:00"}

Deux enseignements dès ce premier essai :

  1. La commande a déjà modifié le cluster cible (un compte, un rôle de cluster, un jeton) avant d'échouer.
  2. Elle échoue parce qu'elle enregistre l'URL telle qu'elle figure dans votre kubeconfig (127.0.0.1:43675, le port publié par kind sur votre poste), et que le serveur Argo CD, qui tourne dans le hub, ne peut évidemment pas la joindre. C'est le même problème avec un cluster joint par un tunnel, un bastion ou une adresse privée différente de celle que voit le hub.

L'option --cluster-endpoint kube-public utilise à la place l'adresse publiée par le cluster lui-même dans le ConfigMap kube-public/cluster-info, ici https://gitops-prod-control-plane:6443, joignable sur le réseau Docker :

$ argocd cluster add kind-gitops-prod --name production --cluster-endpoint kube-public --label environnement=production --yes
...
Cluster 'https://gitops-prod-control-plane:6443' added
$ argocd cluster list
SERVER                                  NAME        VERSION  STATUS      MESSAGE  PROJECT
https://gitops-prod-control-plane:6443  production  v1.36.4  Successful
https://kubernetes.default.svc          in-cluster  v1.36.4  Successful

Regardons ce qui a été donné, d'abord au compte créé dans le cluster cible :

$ kubectl --context kind-gitops-prod get clusterrole argocd-manager-role -o jsonpath='{.rules}'
[{"apiGroups":["*"],"resources":["*"],"verbs":["*"]},{"nonResourceURLs":["*"],"verbs":["*"]}]

Tous les droits, sur tout le cluster. Puis au secret enregistré côté hub (valeurs remplacées par <...> ici) :

$ kubectl -n argocd get secrets -l argocd.argoproj.io/secret-type=cluster --show-labels
NAME                                           TYPE     DATA   AGE   LABELS
cluster-gitops-prod-control-plane-2294550256   Opaque   3      6s    argocd.argoproj.io/secret-type=cluster,environnement=production

Le champ config de ce secret, décodé, a la forme suivante (valeurs masquées) :

{
  "tlsClientConfig": {
    "insecure": false,
    "certData": "<...>",
    "keyData": "<...>",
    "caData": "<...>"
  }
}

Il n'y a pas de jeton dans ce secret, mais un certificat client et sa clé privée : ceux du contexte kind-gitops-prod de votre kubeconfig, c'est-à-dire l'identité d'administration du cluster. Le code de la version 3.5.3 l'explique (cmd/util/cluster.go, fonction NewCluster) :

// Bearer token will preferentially be used for auth if present,
// Even in presence of key/cert credentials
// So set bearer token only if the key/cert data is absent
if len(tlsClientConfig.CertData) == 0 || len(tlsClientConfig.KeyData) == 0 {
    clst.Config.BearerToken = managerBearerToken
}

Quand votre contexte s'authentifie par certificat client (kind, kubeadm, beaucoup de clusters installés à la main), argocd cluster add recopie votre certificat d'administration dans Argo CD, et le compte argocd-manager qu'il vient de créer ne sert à rien. Un tel certificat ne se révoque pas individuellement dans Kubernetes : il reste valide jusqu'à son expiration. Quand le contexte s'authentifie par jeton ou par un programme externe (exec), c'est bien le jeton de argocd-manager qui est enregistré, avec ses droits illimités.

Et pour défaire :

$ argocd cluster rm production --yes
Cluster 'production' removed
{"level":"fatal","msg":"context production does not exist in kubeconfig","time":"2026-10-02T10:20:09+02:00"}
$ kubectl --context kind-gitops-prod -n kube-system get sa,secret -o name | grep argocd
serviceaccount/argocd-manager
secret/argocd-manager-long-lived-token
$ kubectl --context kind-gitops-prod get clusterrole,clusterrolebinding -o name | grep argocd
clusterrole.rbac.authorization.k8s.io/argocd-manager-role
clusterrolebinding.rbac.authorization.k8s.io/argocd-manager-role-binding

argocd cluster rm cherche, pour nettoyer le cluster cible, un contexte portant le nom donné au cluster (production), qui n'existe pas dans le kubeconfig : il retire l'enregistrement, puis échoue, et laisse dans le cluster cible un compte administrateur avec un jeton de longue durée. Il faut les supprimer à la main.

Warning

argocd cluster add est pratique pour une démonstration. Pour un cluster qui compte, préférez l'enregistrement déclaratif qui suit : vous choisissez les droits, et vous savez exactement quel identifiant est confié à Argo CD.

Un enregistrement déclaratif, limité à un namespace

L'équipe plateforme prépare, dans le cluster cible, un compte dédié et ses droits, limités au namespace de Signalements :

# acces-argocd.yaml, à appliquer sur le cluster de production par l'équipe plateforme.
# Argo CD n'y reçoit que les droits sur le namespace de Signalements.
apiVersion: v1
kind: Namespace
metadata:
  name: signalements-production
---
apiVersion: v1
kind: Namespace
metadata:
  name: argocd-acces
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: argocd
  namespace: argocd-acces
---
# Jeton de longue durée, lié au compte : il est révoqué si le compte ou ce secret est supprimé.
apiVersion: v1
kind: Secret
metadata:
  name: argocd-jeton
  namespace: argocd-acces
  annotations:
    kubernetes.io/service-account.name: argocd
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: argocd
  namespace: signalements-production
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: admin
subjects:
  - kind: ServiceAccount
    name: argocd
    namespace: argocd-acces

Le namespace signalements-production est créé par l'équipe plateforme, pas par Argo CD : un compte limité à un namespace ne peut pas créer de namespace. Le rôle admin intégré à Kubernetes donne, à travers un RoleBinding, tous les droits usuels dans ce seul namespace.

Côté hub, le secret de cluster référence ce jeton :

apiVersion: v1
kind: Secret
metadata:
  name: cluster-production
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: cluster
    environnement: production
type: Opaque
stringData:
  name: production
  server: https://gitops-prod-control-plane:6443
  namespaces: signalements-production
  clusterResources: "false"
  config: |
    {
      "bearerToken": "<jeton du secret argocd-acces/argocd-jeton>",
      "tlsClientConfig": {"caData": "<ca.crt du même secret, en base64>"}
    }

Ce secret contient un identifiant : il ne se commite pas en clair dans le dépôt gitops. La leçon 8 montre comment le faire venir d'un gestionnaire de secrets. Une fois appliqué :

$ argocd cluster list
SERVER                                                 NAME        VERSION  STATUS      MESSAGE                                                  PROJECT
https://gitops-prod-control-plane:6443 (1 namespaces)  production           Unknown     Cluster has no applications and is not being monitored.
https://kubernetes.default.svc                         in-cluster  v1.36.4  Successful

Unknown n'est pas une erreur : Argo CD ne surveille un cluster qu'à partir du moment où une application le cible.

Étiqueter aussi le cluster local

Le générateur de clusters sélectionne les clusters par leurs étiquettes. Le cluster local (in-cluster) n'a pas de secret par défaut, donc pas d'étiquette : on le déclare explicitement.

# Le cluster local d'Argo CD, déclaré pour pouvoir porter une étiquette.
apiVersion: v1
kind: Secret
metadata:
  name: cluster-in-cluster
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: cluster
    environnement: recette
type: Opaque
stringData:
  name: in-cluster
  server: https://kubernetes.default.svc
  config: '{"tlsClientConfig": {"insecure": false}}'

Chaque environnement sur son cluster

L'ApplicationSet de la leçon 6 devient une matrice : pour chaque cluster portant une étiquette environnement, le générateur Git ne retient que le répertoire du même nom.

# Une application par environnement, sur le cluster qui porte son étiquette.
spec:
  generators:
    - matrix:
        generators:
          - clusters:
              selector:
                matchExpressions:
                  - key: environnement
                    operator: Exists
          - git:
              repoURL: http://forgejo.forge.svc.cluster.local:3000/plateforme/signalements-deploiement.git
              revision: main
              directories:
                - path: 'environnements/{{ index .metadata.labels "environnement" }}'
  template:
    spec:
      destination:
        name: '{{ .name }}'
        namespace: 'signalements-{{ .path.basename }}'
      # ... le reste du modèle de la leçon 6, inchangé

Le second générateur utilise un paramètre du premier (.metadata.labels) : c'est permis dans une matrice. Avant de pousser, la vérification habituelle :

$ argocd appset generate applications/signalements.yaml -o json | jq -r '.[]|"\(.metadata.name)  \(.spec.destination.name)  \(.spec.destination.namespace)"'
signalements-recette  in-cluster  signalements-recette
signalements-production  production  signalements-production

Ce qui se passe quand une application change de cluster

Le commit poussé, l'Application signalements-production change de destination en quelques secondes. Puis :

$ argocd app get signalements-production --refresh
...
Sync Status:        Unknown
Health Status:      Healthy

CONDITION        MESSAGE
ComparisonError  Failed to load live state: failed to get cluster info for "https://gitops-prod-control-plane:6443": error synchronizing cache state : failed to sync cluster https://gitops-prod-control-plane:6443: failed to load initial state of resource CSIStorageCapacity.storage.k8s.io: failed to list resources: csistoragecapacities.storage.k8s.io is forbidden: User "system:serviceaccount:argocd-acces:argocd" cannot list resource "csistoragecapacities" in API group "storage.k8s.io" in the namespace "signalements-production"

Le cache d'Argo CD ne se contente pas des types de ressources que l'application utilise : il liste tous les types de ressources namespacées que l'API annonce, dans chaque namespace géré. Le rôle admin de Kubernetes n'en couvre pas certains (csistoragecapacities, podtemplates...), et une seule liste refusée bloque tout le cache du cluster. Il faut donc ajouter, dans le namespace, un droit de lecture sur tout :

# Le cache d'Argo CD liste tous les types de ressources du namespace :
# le rôle admin intégré n'en couvre pas certains (csistoragecapacities...).
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: argocd-lecture
  namespace: signalements-production
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: argocd-lecture
  namespace: signalements-production
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: argocd-lecture
subjects:
  - kind: ServiceAccount
    name: argocd
    namespace: argocd-acces

Ce rôle donne la lecture des secrets du namespace, ce que admin donnait déjà. L'alternative est d'exclure de la surveillance les types inutiles (resource.exclusions dans argocd-cm), au risque d'en oublier. Le droit ajouté, l'application se synchronise sur le nouveau cluster :

$ kubectl --context kind-gitops-prod -n signalements-production get deploy
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
signalements   3/3     3            3           2s

Deux surprises restent à voir. La première, sur l'ancien cluster :

$ kubectl --context kind-gitops -n signalements-production get deploy
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
signalements   3/3     3            3           52m

L'ancienne production tourne toujours. L'élagage ne concerne que le cluster de destination : en changeant de destination, l'application a simplement oublié ses ressources de l'ancien cluster. Les voilà orphelines, et plus rien ne les met à jour. Pour un vrai déménagement, c'est d'ailleurs souhaitable pendant la bascule (le trafic peut continuer d'y aller), mais il faut les supprimer explicitement ensuite.

La seconde surprise est dans l'opération qui a déployé la production sur le nouveau cluster :

$ kubectl -n argocd get application signalements-production -o json | jq '{op: .status.operationState | {startedAt, phase, initiatedBy: .operation.initiatedBy, results: [.syncResult.resources[] | "\(.kind) \(.hookType // "-") \(.message)"]}, history: [.status.history[] | {id, deployedAt, revision}]}'
{
  "op": {
    "startedAt": "2026-10-02T08:23:05Z",
    "phase": "Succeeded",
    "initiatedBy": {
      "automated": true
    },
    "results": [
      "Service - service/signalements created",
      "Deployment - deployment.apps/signalements created"
    ]
  },
  "history": [
    {
      "id": 0,
      "deployedAt": "2026-10-02T08:15:47Z",
      "revision": "07a1de5bb01a5bc9cde97c675db1ef4692d83b63"
    }
  ]
}

Le hook PostSync de test de fumée n'a pas été exécuté, et l'historique n'a pas de nouvelle entrée. La révision 07a1de5 n'a pas changé : pour le contrôleur, la synchronisation automatique de cette révision a déjà été tentée (leçon 3). Il ne relance donc qu'une synchronisation d'auto-réparation, et le code de controller/appcontroller.go la restreint aux ressources désynchronisées :

op.Sync.SelfHealAttemptsCount++
for _, resource := range resources {
    if resource.Status != appv1.SyncStatusCodeSynced {
        op.Sync.Resources = append(op.Sync.Resources, appv1.SyncOperationResource{ ... })
    }
}

Une synchronisation partielle n'exécute pas les hooks (le filtre de controller/sync.go ne retient que les ressources listées) et n'est pas inscrite dans l'historique. Sans selfHeal, il n'y aurait même rien eu : la synchronisation automatique aurait été ignorée (Skipping auto-sync: already attempted). Pour une application avec une migration de base en hook, déplacer l'application sans changer de révision déploie l'application sans sa migration. Après un changement de destination, lancez une synchronisation complète (argocd app sync signalements-production), qui exécute tous les hooks.

Fenêtres de synchronisation

Plus il y a de clusters, plus il y a de raisons de ne pas déployer à certains moments : la nuit, pendant une période de forte charge, pendant un gel des mises en production. Les fenêtres de synchronisation se déclarent dans un AppProject (la leçon 9 traite les projets en détail) :

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: production
  namespace: argocd
spec:
  description: Applications de production
  sourceRepos:
    - http://forgejo.forge.svc.cluster.local:3000/plateforme/*
  destinations:
    - name: production
      namespace: signalements-production
  syncWindows:
    # Pas de mise en production le soir, la nuit et le week-end, sauf geste manuel.
    - kind: allow
      schedule: '0 9 * * 1-4'
      duration: 8h
      timeZone: Europe/Paris
      applications: ['*']
      manualSync: true
      description: Du lundi au jeudi, de 9 h à 17 h
    # Gel de fin d'année, sans exception.
    - kind: deny
      schedule: '0 0 20 12 *'
      duration: 336h
      timeZone: Europe/Paris
      clusters: [production]
      description: Gel du 20 décembre au 2 janvier inclus

Les règles, d'après la documentation :

  • sans fenêtre correspondante, tout est permis ;
  • dès qu'une fenêtre allow correspond à une application, les synchronisations ne sont permises que pendant une fenêtre allow active ;
  • une fenêtre deny active interdit les synchronisations, et l'emporte sur une allow active ;
  • manualSync: true laisse passer les synchronisations manuelles ; les fenêtres bloquent sinon les automatiques comme les manuelles ;
  • les sélecteurs applications, namespaces et clusters acceptent des jokers et sont combinés par un OU, sauf andOperator: true ;
  • sans timeZone, l'horaire est en UTC.

L'état est visible dans argocd app get (ligne SyncWindow, qui affiche Sync Allowed, Sync Denied ou Manual Allowed, ce dernier quand seule une synchronisation manuelle passerait) et dans argocd proj windows list production. Une fenêtre bloque aussi l'auto-réparation : une dérive pendant un gel reste en place jusqu'à la réouverture.

Sous le capot

Pour chaque cluster ciblé par au moins une application, le contrôleur d'applications maintient un cache : il liste puis surveille (watch) chaque type de ressource, sur tout le cluster ou dans les seuls namespaces déclarés. C'est ce cache qui rend les comparaisons rapides, et c'est lui qui consomme la mémoire du contrôleur : elle croît avec le nombre de clusters et d'objets. C'est aussi pourquoi un seul droit de liste manquant met tout le cluster en erreur : le cache ne peut pas se construire partiellement.

Au-delà de quelques dizaines de clusters, on répartit les clusters entre plusieurs répliques du contrôleur d'applications (le sharding) : chaque réplique ne gère que ses clusters, selon un algorithme de répartition (legacy par défaut ; round-robin et consistent-hashing, tous deux expérimentaux), ou un numéro de réplique imposé par la clé shard du secret de cluster.

Pièges courants

dial tcp 127.0.0.1:... connection refused. L'URL du kubeconfig n'est pas celle que voit le hub. Utilisez --cluster-endpoint kube-public, ou un secret déclaratif avec la bonne URL.

Un certificat d'administration dans Argo CD. argocd cluster add sur un contexte à certificat client recopie ce certificat. Vérifiez le contenu de config du secret de cluster.

Un compte administrateur oublié dans le cluster cible. Après argocd cluster rm, vérifiez kube-system/argocd-manager et le ClusterRole argocd-manager-role.

cannot list resource ... in the namespace. Le compte d'Argo CD doit pouvoir lister tous les types de ressources des namespaces gérés, pas seulement ceux de l'application.

Cluster has no applications and is not being monitored. Normal tant qu'aucune application ne cible le cluster.

Une application déplacée sans ses hooks, et des orphelins sur l'ancien cluster. Synchronisation complète manuelle après le déplacement, puis suppression explicite des anciennes ressources.

Une synchronisation urgente bloquée par une fenêtre. C'est son rôle. Prévoyez manualSync: true sur les fenêtres où une intervention humaine doit rester possible, ou retirez la fenêtre par un commit tracé.

Sécurité

Le hub est la cible la plus précieuse de votre infrastructure : il détient un identifiant d'écriture sur chaque cluster qu'il pilote. Quiconque compromet Argo CD (son serveur, son repo-server, un administrateur) atteint tous les clusters. D'où :

  • le moindre privilège par cluster : un compte limité aux namespaces gérés, clusterResources: "false" quand c'est possible, jamais le certificat d'administration ;
  • des identifiants révocables : un jeton de compte de service se révoque en supprimant son secret ; un certificat client ne se révoque pas ;
  • des identifiants à durée courte quand le fournisseur le permet : execProviderConfig ou awsAuthConfig obtiennent un jeton à la demande au lieu d'en stocker un ;
  • l'API des clusters cibles filtrée : n'autoriser que l'adresse sortante du hub ;
  • désactiver in-cluster (cluster.inClusterEnabled: "false" dans argocd-cm) quand le hub ne doit héberger aucune application : on ne déploie alors plus par erreur dans le cluster d'Argo CD lui-même.

En production

Chez Lyneko, un seul cluster Kapsule héberge Argo CD et les applications : toutes les Applications ciblent in-cluster. Ce choix est raisonnable à cette taille, et la leçon montre ce qu'il faudrait le jour où un client exige son propre cluster : un secret de cluster déclaratif, un compte limité à ses namespaces, préparé dans son cluster, et un identifiant distribué par le gestionnaire de secrets plutôt que par Git.

Un hub ou un Argo CD par cluster ? Le hub donne une vue unique et une seule instance à mettre à jour, au prix d'un point central qui détient tous les accès et doit joindre toutes les API. Un Argo CD par cluster isole les incidents et les identifiants, au prix de N instances. Beaucoup d'organisations combinent : un hub par grande zone de confiance (un par client, ou un pour la production et un pour le reste).

Le contrôleur grossit avec les clusters. Surveillez sa mémoire ; le sharding devient nécessaire bien avant des centaines de clusters si ceux-ci sont chargés.

Exercices

1. Pourquoi le secret de cluster créé par argocd cluster add sur un cluster kind ne contenait-il aucun jeton ?

Solution

Le contexte kind s'authentifie par certificat client. NewCluster recopie le certificat et la clé du contexte et n'ajoute le jeton de argocd-manager que si l'un d'eux est absent. Argo CD utilise donc l'identité d'administration du kubeconfig, et le compte argocd-manager créé n'est jamais utilisé.

2. Écrivez le secret de cluster et les droits nécessaires pour qu'Argo CD gère deux namespaces, signalements-production et annuaire-production, et puisse aussi créer des ClusterRole.

Solution

Dans le secret de cluster : namespaces: signalements-production,annuaire-production et clusterResources: "true". Dans le cluster cible : les RoleBinding admin et le Role de lecture dans chacun des deux namespaces, plus un ClusterRole permettant get, list, watch, create, update, patch, delete sur clusterroles (et clusterrolebindings si besoin), lié par un ClusterRoleBinding. Attention : Kubernetes empêche l'escalade de privilèges, et refuse la création d'un ClusterRole qui contiendrait des droits que le compte n'a pas lui-même (« attempting to grant RBAC permissions not currently held »). Pour gérer des rôles quelconques, il faudrait les verbes escalate (sur les rôles) et bind (sur les liaisons), qui équivalent à se donner tous les droits du cluster. Ne les accordez qu'en connaissance de cause.

3. Une fenêtre allow du lundi au jeudi de 9 h à 17 h existe sur le projet. Un développeur fusionne une correction le vendredi à 10 h. Que se passe-t-il, avec et sans manualSync: true ?

Solution

Aucune fenêtre allow n'est active le vendredi : la synchronisation automatique n'a pas lieu, l'application reste OutOfSync jusqu'au lundi 9 h. Avec manualSync: true, une personne autorisée peut lancer argocd app sync ; sans, même la synchronisation manuelle est refusée, et il faut modifier la fenêtre (par un commit sur l'AppProject) pour déployer.

4. Vous déplacez signalements-recette, qui a une base PostgreSQL et un hook de migration, vers un nouveau cluster, dans le même commit qu'une nouvelle version de l'image. Les hooks seront-ils exécutés ?

Solution

Oui : la révision du dépôt de déploiement change (nouvelle version de l'image), donc la synchronisation automatique est une synchronisation complète, hooks compris. Le piège ne concerne que le déplacement à révision inchangée. Mais attention à l'ordre : la base du nouveau cluster est vide, et les données de l'ancien cluster ne suivent pas. Déplacer une application avec état est un projet de migration de données, pas un changement de destination.

5. Après le déménagement, comment supprimez-vous proprement l'ancienne production du hub ?

Solution

Plus aucune Application ne gère ces ressources : kubectl --context kind-gitops delete namespace signalements-production, après avoir vérifié que le trafic va bien au nouveau cluster. Si l'on veut que ce soit tracé dans Git, on peut aussi le faire par une Application temporaire, mais un namespace orphelin se supprime en général à la main, par une procédure revue.

Récapitulatif

  • Argo CD connaît un cluster par un secret étiqueté argocd.argoproj.io/secret-type: cluster : URL, identifiants, namespaces gérés, étiquettes.
  • argocd cluster add crée un compte administrateur dans le cluster cible, et peut recopier votre certificat d'administration ; argocd cluster rm peut laisser le compte derrière lui. Préférez l'enregistrement déclaratif avec un compte limité.
  • Un compte limité à des namespaces a besoin de lister tous les types de ressources de ces namespaces.
  • Le générateur de clusters, en matrice avec le générateur Git, place chaque environnement sur son cluster.
  • Changer la destination d'une application laisse des orphelins sur l'ancien cluster, et, à révision inchangée, ne déclenche qu'une synchronisation partielle sans hooks.
  • Les fenêtres de synchronisation d'un AppProject bloquent les synchronisations hors créneau ; deny l'emporte sur allow.

Pour aller plus loin

Voir ma constellation →

Sources