Gardener : piloter une flotte de clusters Kubernetes sans jamais étirer un cluster entre deux sites
Un cluster autonome par centre de données, fédérés par un plan de contrôle unique. Comment Gardener industrialise le cycle de vie de dizaines de clusters, et pourquoi le cluster étiré reste une mauvaise idée.
Passé une dizaine de clusters, le problème change de nature. Ce n'est plus « comment déployer un cluster » mais « comment garantir que les quarante clusters partagent la même version, les mêmes politiques et le même niveau de correctif ».
Gardener, développé par SAP et versé en source ouverte, répond à cette question avec une idée forte : un cluster Kubernetes gère d'autres clusters Kubernetes. Le plan de contrôle des clusters gérés s'exécute comme une charge applicative ordinaire.
Le modèle à trois étages
| Étage | Rôle | Nombre |
|---|---|---|
| Garden | Plan de contrôle central, catalogue, API Shoot | 1 |
| Seed | Héberge les plans de contrôle des clusters gérés | 1 par site |
| Shoot | Cluster de travail livré aux équipes | des dizaines |
Le point qui surprend au premier abord : l'apiserver et l'etcd d'un cluster Shoot tournent dans un cluster Seed, sous forme de pods. Le Shoot ne contient que ses nœuds de travail.
Trois conséquences pratiques. La création d'un cluster revient à déployer un ensemble de pods — quelques minutes plutôt qu'une heure. La supervision du plan de contrôle bénéficie de l'outillage Kubernetes existant. Et la mise à jour d'etcd sur quarante clusters devient une mise à jour de charte Helm.
Pourquoi jamais de cluster étiré
L'idée d'un cluster unique réparti sur deux centres de données revient régulièrement. Elle semble offrir la haute disponibilité à moindre coût. Elle ne tient pas.
Le quorum etcd ne supporte pas la partition. Avec trois membres répartis deux-un, la perte du site majoritaire fait basculer le cluster entier en lecture seule. Avec cinq membres en trois-deux, le site minoritaire perd tout.
La latence dégrade le plan de contrôle. etcd exige des écritures synchrones sur le quorum. Au-delà de dix millisecondes entre membres, les temps de réponse de l'apiserver s'effondrent — et avec eux tout ce qui en dépend : ordonnanceur, contrôleurs, sondes.
Le rayon d'explosion double. Une erreur de configuration, une montée de version ratée, un certificat expiré : sur un cluster étiré, l'incident touche les deux sites simultanément. C'est exactement ce que l'architecture multi-site cherchait à éviter.
La règle que j'applique. Un cluster autonome par centre de données, avec son propre
etcdet son propre plan de contrôle. La haute disponibilité se construit au-dessus des clusters — par le routage, la réplication des données et le basculement applicatif — jamais à l'intérieur d'un seul.
Déclarer un Seed par site
apiVersion: core.gardener.cloud/v1beta1
kind: Seed
metadata:
name: seed-dc1-paris
spec:
provider:
type: openstack
region: paris-dc1
zones: ["az-a", "az-b", "az-c"] # trois zones DANS le site, pas entre sites
networks:
nodes: 10.20.0.0/16
pods: 10.21.0.0/16
services: 10.22.0.0/17
shootDefaults:
pods: 100.96.0.0/11
services: 100.64.0.0/13
dns:
provider:
type: powerdns
secretRef: { name: dns-powerdns, namespace: garden }
ingress:
domain: ingress.dc1.interne.fr
controller: { kind: nginx }
settings:
scheduling: { visible: true }
verticalPodAutoscaler: { enabled: true }
topologyAwareRouting: { enabled: true } # le trafic reste dans la zone
taints: []Les trois zones déclarées appartiennent au même site. C'est la nuance qui change tout : on répartit à l'intérieur d'un centre de données, pas entre centres.
Déclarer un cluster de travail
apiVersion: core.gardener.cloud/v1beta1
kind: Shoot
metadata:
name: production-paiement
namespace: garden-finance
spec:
cloudProfileName: openstack-interne
region: paris-dc1
seedSelector:
matchLabels: { site: dc1 }
secretBindingName: identifiants-finance
kubernetes:
version: "1.31.4"
kubeAPIServer:
auditConfig:
auditPolicy: { configMapRef: { name: politique-audit-stricte } }
oidcConfig:
issuerURL: https://sso.interne.fr/realms/entreprise
clientID: kubernetes
usernameClaim: preferred_username
groupsClaim: groups
requests:
maxNonMutatingInflight: 600
maxMutatingInflight: 300
kubeControllerManager:
nodeMonitorGracePeriod: 40s
enableStaticTokenKubeconfig: false # authentification uniquement par OIDC
provider:
type: openstack
infrastructureConfig:
apiVersion: openstack.provider.extensions.gardener.cloud/v1alpha1
kind: InfrastructureConfig
networks: { workers: 10.180.0.0/16 }
workers:
- name: general
machine:
type: m1.xlarge
image: { name: gardenlinux, version: "1592.3.0" }
minimum: 3
maximum: 12
maxSurge: 2
zones: ["az-a", "az-b", "az-c"]
systemComponents: { allow: true }
- name: calcul
machine: { type: c2.4xlarge, image: { name: gardenlinux, version: "1592.3.0" } }
minimum: 0
maximum: 20
taints:
- { key: dedie, value: calcul, effect: NoSchedule }
zones: ["az-b"]
networking:
type: calico
pods: 100.96.0.0/11
services: 100.64.0.0/13
nodes: 10.180.0.0/16
maintenance:
timeWindow: { begin: "020000+0100", end: "040000+0100" }
autoUpdate:
kubernetesVersion: false # jamais automatique en production
machineImageVersion: true
hibernation:
enabled: false
schedules:
- start: "0 20 * * 1-5" # sur les environnements hors production
end: "0 7 * * 1-5"
location: "Europe/Paris"Trois éléments méritent un commentaire.
enableStaticTokenKubeconfig: false supprime le fichier d'accès à jeton statique. L'authentification passe exclusivement par OIDC, donc par votre annuaire — révocation immédiate incluse.
autoUpdate.kubernetesVersion: false sur la production. Gardener sait monter de version tout seul ; laissez-le faire sur la recette, jamais sur la production sans validation préalable.
L'hibernation planifiée éteint les clusters hors production la nuit et le week-end. Sur vingt clusters de développement, l'économie atteint 60 % de la facture de calcul.
La haute disponibilité, bâtie au-dessus
Chaque site porte son cluster autonome. La continuité se construit à l'étage supérieur.
┌──────────────────────┐
Utilisateurs ─▶│ Répartiteur global │ contrôle de santé toutes les 5 s
└───────┬──────────────┘
┌───────────┴───────────┐
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Shoot dc1-paris │ │ Shoot dc2-lyon │
│ etcd propre │ │ etcd propre │
│ 3 zones internes │ │ 3 zones internes │
└──────────┬──────────┘ └──────────┬──────────┘
└────────── réplication ─┘
asynchrone des donnéesLe répartiteur global bascule le trafic quand un site ne répond plus. Les données se répliquent en asynchrone entre les deux sites — PostgreSQL en réplication logique, ou un stockage objet répliqué. On accepte une perte potentielle de quelques secondes de transactions plutôt que de risquer l'indisponibilité complète.
Les politiques, appliquées partout
L'intérêt d'une flotte gérée tient à l'uniformité. Gardener applique les extensions à tous les Shoot correspondant à un sélecteur.
apiVersion: core.gardener.cloud/v1beta1
kind: ControllerRegistration
metadata:
name: extension-securite-interne
spec:
resources:
- { kind: Extension, type: securite-interne, globallyEnabled: true }
deployment:
policy: Always
deploymentRefs: [{ name: extension-securite-interne }]L'extension installe Kyverno, les politiques d'entreprise, l'agent de collecte de journaux et le déclencheur de sauvegarde etcd. Un nouveau cluster naît conforme, sans que l'équipe qui le demande ait à s'en préoccuper.
Ce que Gardener ne résout pas
La conception réseau reste à votre charge. Gardener provisionne, il ne décide pas de votre plan d'adressage ni de vos règles d'interconnexion.
Le coût du Seed. Chaque Seed est un cluster Kubernetes à part entière, à dimensionner et à exploiter. En dessous de dix Shoot par site, l'équation économique reste discutable.
La courbe d'apprentissage. Le modèle à trois étages déroute. Prévoyez deux à trois semaines avant que l'équipe raisonne naturellement en Garden, Seed et Shoot.
Au-delà de quinze clusters, l'investissement se rentabilise sans discussion. En dessous, un dépôt Terraform bien tenu fait souvent l'affaire.