Ce projet c'est mon homelab k3s. L'objectif : héberger quelques apps perso et d'asso dans une infra qui tient la route — GitOps-first, résiliente géographiquement, et avec une observabilité poussée sur toutes les couches.
Le cluster tourne sur 3 NUCs répartis sur 2 maisons différentes (une forme d'HA géographique maison). Ils sont interconnectés via un réseau Tailscale (VPN mesh), ce qui permet à Cilium de faire tourner son réseau VXLAN inter-pods à travers ce tunnel sécurisé.
Maison 1 Maison 2
┌──────────────┐ ┌──────────────┐
│ NUC 1 │◄────── Tailscale ────────► NUC 2 │
│ control- │ VPN │ control- │
│ plane │ │ plane │
└──────┬───────┘ └──────┬───────┘
│ │
└──────────────────┬──────────────────────┘
│ Tailscale VPN
┌──────┴───────┐
│ NUC 3 │
│ control- │
│ plane │
└──────────────┘
▲
│ (via Tailscale)
┌──────┴───────┐
│ Mon PC │
│ HAProxy │
│ 127.0.0.1 │
│ :6443 │
└──────────────┘
Mon PC est également dans le réseau Tailscale. J'ai un HAProxy local qui fait du round-robin sur les 3 control-planes pour avoir un accès HA à l'API server depuis ma machine — si un nœud est down, kubectl continue de fonctionner.
Côté réseau pods, Cilium tourne en mode VXLAN tunnel (MTU 1150 : 1200 imposé par Tailscale, minus 50 d'overhead VXLAN). etcd est en cluster natif k3s avec des snapshots automatiques toutes les 6h.
| Outil | Rôle |
|---|---|
| Ingress controller en DaemonSet, hostPort 80/443 | |
| TLS automatique via Let's Encrypt (DNS-01 Cloudflare) | |
| Tunnel sortant, zéro port ouvert sur la box |
Le flux d'une requête externe :
Internet → Cloudflare (DNS + TLS) → cloudflared DaemonSet → Traefik → Service → Pod
L'objectif à terme : une observabilité complète sur toutes les couches — infra, middleware, et backends applicatifs. Logs, métriques et traces centralisés dans Grafana.
Les backends applicatifs (Ski'UT en tête) sont instrumentés avec OpenTelemetry pour envoyer leurs traces directement vers Tempo.
| App | Stack |
|---|---|
| Ski'UT | Laravel + MySQL + ProxySQL + PhpMyAdmin + HPA/PDB + OTEL |
| Mon site | Next.js |
| Affine | Workspace collaboratif (Postgres + Redis) |
Le repo est déployé directement par ArgoCD via un pattern app-of-apps en plusieurs niveaux :
bootstrap-app ← appliqué une seule fois à la main
└── meta ← app-of-apps racine
├── infra ← sync-wave: -1 (déployée en premier)
├── monitoring
├── skiut
├── website
└── productivity
k3s-project/
├── bootstrap/ # Bootstrap ArgoCD + définitions des AppProjects
├── meta/ # App-of-apps (une ArgoCD App par dossier ci-dessous)
├── infra/ # Traefik, cert-manager, Sealed Secrets, Cloudflare Tunnel
├── monitoring/ # kube-prometheus-stack, Loki, Alloy, Tempo
├── apps/ # Ski'UT, Website
├── productivity/ # Affine
├── valueFiles/ # Fichiers de valeurs Helm (séparés des apps)
└── setup/ # Scripts d'installation du cluster
└── ha-cluster/ # Init premier CP, join des autres, HAProxy local
La séquence de bootstrap au premier déploiement :
# 1. Sur le premier NUC
./setup/ha-cluster/setup-init-ha-node.sh
# 2. Sur chaque NUC suivant
./setup/ha-cluster/setup-ha-node.sh --token=<TOKEN> --init-node-ip=<IP_TAILSCALE>
# 3. Sur mon PC
./setup/ha-cluster/setup-local-computer.sh --master-ips=IP1,IP2,IP3 --master-names=N1,N2,N3
# 4. Bootstrap ArgoCD (fait automatiquement par setup-init-ha-node.sh)
kubectl apply -f bootstrap-app.yaml
# → ArgoCD sync tout le reste automatiquementRenovate scanne automatiquement les charts Helm et les images Docker pour ouvrir des PRs de mise à jour. Les mises à jour mineures sont auto-mergées, les majeures passent en revue manuelle.
Chaque PR déclenche un workflow GitHub Actions qui génère le diff complet des manifests ArgoCD (Helm rendu + Kustomize) et le poste directement en commentaire de la PR. Pratique pour savoir exactement ce qui va changer dans le cluster avant de merger, sans avoir à faire tourner ArgoCD localement.