Aller au contenu

01 - Cluster kind piloté en GitOps par Flux

Je te propose de monter un homelab Kubernetes sur ton poste : jetable, reproductible, et entièrement décrit dans Git. Tu casses tout ? kind delete cluster, un bootstrap, et Flux remet le cluster dans l'état décrit par le dépôt.

C'est le premier épisode d'une série. On pose ici les fondations (le cluster, Flux, la structure du dépôt et une première app) avant d'ajouter la Gateway API dans l'épisode 02.

Les choix de stack

Flux ou Argo CD ?

  • Argo CD : son interface web montre l'arbre des ressources, le diff avec Git et l'état de synchronisation. Idéal pour voir la réconciliation, et c'est souvent l'outil qu'on présente en premier.
  • Flux : plus léger, piloté uniquement par CRD et CLI. Avec flux bootstrap, il écrit sa propre configuration dans Git et se gère lui-même dès le départ. L'image automation (Flux commite lui-même les nouveaux tags d'image) est intégrée.

Pour un homelab où tout doit vivre dans Git, Flux colle parfaitement. Si tu préfères Argo CD, la logique reste la même : jette un œil à mes slides GitOps avec Argo CD.

Quelle distribution locale ?

Option Points forts Limites
kind Kubernetes upstream dans des conteneurs Docker, cluster créé ou détruit en une minute, multi-nœuds via un fichier YAML Ingress et registre local à configurer soi-même
k3d k3s dans Docker, très léger, registre local intégré Distribution k3s (Traefik et stockage local par défaut), un peu différente d'un Kubernetes standard
minikube Addons (ingress, registre) activables en une commande Plus lourd, plus lent à recréer
k3s seul Idéal pour un homelab permanent (VM, Raspberry Pi) Ne tourne pas nativement sur macOS

On part sur kind : c'est le plus proche d'un vrai cluster, et le plus rapide à jeter puis recréer.

Brique Version testée
Docker Desktop macOS arm64
kind 0.33 (Kubernetes 1.37)
Flux 2.10
metrics-server (chart) 3.14
podinfo 6.17

Prérequis

brew install kind fluxcd/tap/flux kubernetes-cli helm k9s

Il te faut aussi Docker Desktop démarré, et un compte GitLab ou GitHub pour héberger le dépôt.

Attention à la version de kubectl

kubectl ne tolère qu'une version mineure d'écart avec le cluster. Un vieux kubectl installé par un autre outil (Rancher Desktop par exemple) peut passer devant celui de Homebrew dans ton PATH : vérifie avec kubectl version.

1. Le cluster kind

kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: homelab
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30080   # NodePort HTTP exposé sur localhost:8080
        hostPort: 8080
      - containerPort: 30443   # NodePort HTTPS exposé sur localhost:8443
        hostPort: 8443
  - role: worker
  - role: worker
kind create cluster --config kind-config.yaml
kubectl wait --for=condition=Ready nodes --all --timeout=120s
kubectl get nodes
NAME                    STATUS   ROLES           VERSION
homelab-control-plane   Ready    control-plane   v1.37.0
homelab-worker          Ready    <none>          v1.37.0
homelab-worker2         Ready    <none>          v1.37.0
  • Un control-plane et deux workers : assez pour voir des pods se répartir sur plusieurs nœuds, sans charger ta machine.
  • Les extraPortMappings : sur macOS, le réseau Docker n'est pas routable depuis ta machine, impossible de joindre directement l'IP d'un nœud. Le port mapping est la seule porte d'entrée : localhost:8080 arrive sur le port 30080 du control-plane.
  • Le contexte kubectl : kind ajoute le contexte kind-homelab dans ~/.kube/config et le rend actif. k9s le voit directement, :ctx pour changer de contexte.

2. Bootstrap Flux

flux check --pre
export GITLAB_TOKEN=<PAT avec le scope api>

flux bootstrap gitlab \
  --owner=<ton-groupe> \
  --repository=<ton-depot> \
  --branch=main \
  --path=clusters/kind \
  --read-write-key \
  --components-extra=image-reflector-controller,image-automation-controller
export GITHUB_TOKEN=<PAT avec accès au dépôt>

flux bootstrap github \
  --owner=<ton-user> \
  --repository=<ton-depot> \
  --branch=main \
  --path=clusters/kind \
  --personal \
  --read-write-key \
  --components-extra=image-reflector-controller,image-automation-controller

Ce que fait le bootstrap :

  • Il crée le dépôt s'il n'existe pas (privé par défaut), ou réutilise celui qui existe.
  • Il pousse les manifestes de Flux dans clusters/kind/flux-system/ et installe les contrôleurs dans le namespace flux-system.
  • Il génère une clé SSH, stocke la partie privée dans le Secret flux-system et enregistre la partie publique comme deploy key du dépôt. C'est cette clé que Flux utilise ensuite : ton token ne sert qu'au bootstrap.

Deux options préparent la suite et t'évitent de refaire le bootstrap plus tard :

  • --read-write-key : la deploy key peut pousser des commits, indispensable pour l'image automation.
  • --components-extra : installe les deux contrôleurs d'image automation, absents par défaut.

Garde ton token hors de l'historique

Lance le bootstrap dans ton propre terminal et ne commite jamais le token. La commande est idempotente : tu peux la relancer pour mettre à jour Flux ou changer ses options.

Vérifie que tout tourne :

flux check
flux get sources git
flux get kustomizations
kubectl -n flux-system get pods

Tu dois voir six contrôleurs : source, kustomize, helm, notification, et les deux contrôleurs d'image.

3. Structurer le dépôt

Flux applique tout ce qui se trouve dans clusters/kind/. On y déclare une Kustomization Flux par couche, pour que l'infrastructure soit prête avant les applications :

flowchart LR
    G[("Dépôt Git")] --> FS["Kustomization<br>flux-system"]
    FS --> IC["infra-controllers<br>metrics-server"]
    IC -->|dependsOn| AP["apps<br>podinfo"]
.
├── clusters/kind/
│   ├── flux-system/          # généré par flux bootstrap, ne pas éditer à la main
│   ├── infrastructure.yaml   # Kustomization infra-controllers
│   └── apps.yaml             # Kustomization apps
├── infrastructure/
│   └── controllers/          # metrics-server
├── apps/
│   ├── base/podinfo/         # définition générique de l'app
│   └── kind/                 # overlay propre au cluster kind
└── kind-config.yaml
clusters/kind/infrastructure.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: infra-controllers
  namespace: flux-system
spec:
  interval: 1h
  retryInterval: 1m
  timeout: 5m
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./infrastructure/controllers
  prune: true
  wait: true
clusters/kind/apps.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m
  retryInterval: 1m
  timeout: 5m
  dependsOn:
    - name: infra-controllers
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./apps/kind
  prune: true
  wait: true
  • dependsOn + wait: true : apps n'est appliquée qu'une fois toute l'infrastructure Ready.
  • prune: true : supprimer un manifeste du dépôt supprime la ressource du cluster.
  • base / overlay : apps/base/ porte la définition générique d'une app, apps/kind/ ce qui est propre à ce cluster. Le jour où tu ajoutes un deuxième cluster, il aura son propre overlay.

4. Premier composant d'infra : metrics-server

infrastructure/controllers/metrics-server.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: metrics-server
  namespace: kube-system
spec:
  interval: 24h
  url: https://kubernetes-sigs.github.io/metrics-server/
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: metrics-server
  namespace: kube-system
spec:
  interval: 30m
  chart:
    spec:
      chart: metrics-server
      version: "3.x"
      sourceRef:
        kind: HelmRepository
        name: metrics-server
  values:
    args:
      # Les kubelets kind servent des certificats auto-signés
      - --kubelet-insecure-tls

Avec un kustomization.yaml qui liste ce fichier dans infrastructure/controllers/, metrics-server alimente kubectl top et les colonnes CPU / mémoire de k9s :

NAME                    CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
homelab-control-plane   190m         1%       833Mi           10%
homelab-worker          113m         1%       321Mi           4%
homelab-worker2         130m         1%       486Mi           6%

5. Première app : podinfo

podinfo est l'app de démo de l'écosystème Flux. Son chart est publié dans un registre OCI : on utilise cette fois un OCIRepository référencé par chartRef, l'autre façon de sourcer un chart Helm avec Flux.

apps/base/podinfo/repository.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  name: podinfo
  namespace: podinfo
spec:
  interval: 1h
  url: oci://ghcr.io/stefanprodan/charts/podinfo
  layerSelector:
    mediaType: "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
    operation: copy
  ref:
    semver: "6.x"
apps/base/podinfo/release.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: podinfo
  namespace: podinfo
spec:
  interval: 5m
  chartRef:
    kind: OCIRepository
    name: podinfo
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      retries: 3
  driftDetection:
    mode: enabled

La base contient aussi le Namespace podinfo et un kustomization.yaml qui liste les ressources.

apps/kind/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../base/podinfo
patches:
  - path: podinfo-values.yaml
apps/kind/podinfo-values.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: podinfo
  namespace: podinfo
spec:
  values:
    replicaCount: 2
    service:
      type: NodePort
      nodePort: 30080

Le Service podinfo prend le NodePort 30080, celui du port mapping kind. Pour déployer, tu pousses et tu demandes à Flux de relire le dépôt tout de suite (sinon, il le relit toutes les minutes) :

git add . && git commit -m "feat: add podinfo" && git push
flux reconcile kustomization flux-system --with-source
flux get helmreleases -A
curl localhost:8080
{
  "hostname": "podinfo-c6497595d-zwqjm",
  "version": "6.17.0",
  "message": "greetings from podinfo v6.17.0"
}

6. Tester le drift

C'est le moment de vérifier que Git est bien la source de vérité : on modifie le cluster à la main et on regarde Flux remettre l'état décrit dans le dépôt.

La détection de drift d'une HelmRelease est désactivée par défaut

Sans driftDetection.mode: enabled, helm-controller ne regarde que l'état de la release Helm et ignore les modifications faites à la main dans le cluster : le scale et la suppression ci-dessous ne seraient jamais corrigés. Les ressources appliquées directement par une Kustomization Flux, elles, sont réappliquées à chaque intervalle.

# Scale manuel : Flux ramène à 2 replicas
kubectl -n podinfo scale deploy podinfo --replicas=5
flux reconcile helmrelease podinfo -n podinfo
kubectl -n podinfo get deploy podinfo

# Suppression : Flux recrée le Deployment
kubectl -n podinfo delete deploy podinfo
flux reconcile helmrelease podinfo -n podinfo
kubectl -n podinfo get deploy podinfo

# Trace des corrections
kubectl -n podinfo events --for helmrelease/podinfo
Warning   DriftDetected    HelmRelease/podinfo   Cluster state of release podinfo/podinfo.v1 has drifted from the desired state:
                                                 Deployment/podinfo/podinfo removed
Normal    DriftCorrected   HelmRelease/podinfo   Cluster state of release podinfo/podinfo.v1 has been corrected:
                                                 Deployment/podinfo/podinfo created

flux reconcile déclenche la correction tout de suite. Sans lui, elle arrive au prochain intervalle de la HelmRelease (5 minutes ici).

7. Repartir de zéro

Le cluster est jetable, tout son état vit dans le dépôt :

kind delete cluster --name homelab
kind create cluster --config kind-config.yaml
flux bootstrap gitlab ...   # même commande qu'à l'étape 2

Flux réapplique alors toutes les couches dans l'ordre, sans aucune commande kubectl apply de ta part.

La suite