Mon infrastructure K3S : architecture et choix techniques
Vue d’ensemble
Mon infrastructure tourne sur un cluster K3S composé de deux nœuds reliés par Tailscale :
| Nœud | Rôle | Fournisseur | vCPU | RAM | OS |
|---|---|---|---|---|---|
debian-16gb-hel1-2 | control-plane | Hetzner | 8 | ~15 Go | Debian 13 |
vmi2735515 | worker | Contabo | 3 | ~12 Go | Debian 12 |
L’ensemble est géré en GitOps pur : toute la configuration vit dans un repo GitHub, et un push sur main déclenche automatiquement le déploiement.
Pas besoin de Terraform, pas besoin d’Ansible, pas de scripts de provisioning. Juste des fichiers YAML Kubernetes gérés par ArgoCD.
Repo : github.com/BaptTF/vps-infra
Note sur l’évolution : historiquement, le control-plane tournait sur Contabo et un second nœud (
bapt-debian, une VM chez un pote avec 8 Go) faisait office de worker. Quand cette VM est tombée, j’ai cherché un remplaçant et j’ai trouvé Hetzner. J’ai inversé les rôles : Hetzner est devenu control-plane (il offre plus de capacité et il est plus adapté aux workloads critiques) et Contabo est devenu un worker.
GitOps avec ArgoCD
J’utilise le pattern App of Apps : un root-app.yaml pointe vers un dossier apps/ contenant les Application CRs de chaque service. Chaque app est configurée avec autoSync, prune: true et selfHeal: true. Si je modifie quelque chose à la main sur le cluster par erreur, ArgoCD le remet automatiquement dans l’état défini par Git, et si je retire quelque chose du repo, il est supprimé (il faut faire attention aux PVC avec cette configuration).
Mises à jour automatiques
Renovate surveille les versions des charts Helm et crée des PRs automatiquement. ArgoCD Image Updater est utilisé uniquement pour la mise à jour automatique de mes images personnelles ; il fait directement un commit dans le repo pour rester à 100 % GitOps.
Pour activer ou désactiver un service, je déplace juste son fichier entre apps/ et disable-apps/. C’est tout.
Réseau
Ingress public : Traefik
Traefik sert d’ingress controller pour tout le trafic public. Il écoute sur les ports 80, 443 et 22 (historiquement pour le SSH Forgejo, mais je ne l’utilise plus). Le routage se fait via des IngressRoute CRs vers *.bapttf.com.
Les certificats TLS sont gérés par cert-manager avec un challenge DNS-01 via l’API Cloudflare. Ça permet d’avoir des certificats facilement sans devoir faire des challenges HTTP.
Mesh VPN : Tailscale
Tailscale est au cœur de l’architecture. Les deux nœuds K3S communiquent via le tunnel WireGuard de Tailscale, et Flannel encapsule son overlay VXLAN dans ce tunnel. Certains services comme Grafana, Bifrost ou LLDAP ne sont exposés que sur le Tailnet via l’ingress class tailscale. Et pour l’admin, kubectl et l’UI ArgoCD sont accessibles uniquement via Tailscale.
Cloudflare
En frontal, Cloudflare fournit le WAF par défaut, la protection DDoS et la gestion DNS. L’API Cloudflare est utilisée par cert-manager pour les challenges DNS-01. Le seul défaut, c’est que je ne peux pas avoir de sous-sous-domaines (*.*.bapttf.com) avec la version gratuite de Cloudflare, mais c’est déjà très bien ; c’est juste que ce n’est pas super clair.
Authentification
La stack d’authentification, c’est LLDAP pour l’annuaire LDAP (avec une UI web pour gérer les utilisateurs et les groupes) et Authelia pour le portail SSO. Authelia fournit du ForwardAuth via un middleware Traefik pour protéger les routes, et un provider OIDC pour les apps qui le supportent, comme Immich ou les dashboards Hermes.
Les politiques d’accès sont basées sur les groupes LDAP : certains services sont accessibles à la famille, d’autres uniquement aux admins.
Gestion des secrets
J’utilise un modèle à deux niveaux :
Sealed Secrets (Bitnami). Utilisé uniquement pour initialiser le premier secret : les identifiants d’accès à Infisical. Le certificat de scellement est versionné dans le repo.
Infisical. SaaS externe qui gère tous les autres secrets. L’opérateur Kubernetes synchronise automatiquement les secrets depuis Infisical vers des
SecretKubernetes, organisés par path (/argocd,/cloudflare,/tailscale,/agents/bifrost, etc.).
Pourquoi Infisical ?
Quand je me suis renseigné, ça me paraissait plus simple que Vault (HashiCorp), qui est massif pour un petit cluster, et plus intégré que SOPS (qui nécessite de gérer des clés, de chiffrer/déchiffrer à chaque modification et rend difficile la rotation des secrets). Avec Infisical, j’ai une UI web, de la rotation, des environnements et un opérateur Kubernetes natif. Est-ce que c’était le meilleur choix ? Je sais pas. Mais c’est OK.
Bases de données
J’utilise CloudNative-PG (CNPG) comme opérateur PostgreSQL. Il gère un cluster principal avec 9 bases (ArgoCD, Authelia, Vaultwarden, Immich server, agents, TripKit, etc.) et un cluster dédié Immich avec l’extension vectorchord pour la recherche par similarité d’images.
Stockage
Local
Le provisionneur par défaut de K3S (local-path) est utilisé pour la plupart des PVCs : bases de données, config, petits volumes.
Hetzner Storage Box (SMB)
Pour Immich, les photos et vidéos sont stockées sur une Storage Box Hetzner montée via le driver CSI SMB. C’est un choix économique : 1 To de stockage réseau pour 3 €/mois, c’est beaucoup moins cher que d’augmenter le disque du VPS.
L’architecture de stockage Immich :
- Local PVC : thumbnails, profils, données générées
- Storage Box : uploads, librairie principale, vidéos encodées, photos de famille
Un FileBrowser est aussi monté sur cette Storage Box pour permettre à la famille de gérer ses fichiers.
Sauvegardes
Approche :
| Quoi | Vers où | Fréquence | Rétention |
|---|---|---|---|
| Volumes K3S (Restic) | Hetzner Storage Box (SFTP) | Manuelle | - |
| Config/Manifestes | GitHub (Git) | Chaque push | Illimité |
Services déployés
Actifs
| Service | Description | Accès |
|---|---|---|
| Immich | Google Photos self-hosted, ML, reconnaissance faciale | Public (OIDC) |
| Vaultwarden | Gestionnaire de mots de passe (compatible Bitwarden) | Public |
| Bifrost | Gateway LLM (routes vers Bedrock, Gemini, Groq) | Tailscale |
| Agents IA | OpenCLAW, Nullclaw, Hermes-Leo/Lya, Steel browser | Mixte |
| TripKit | App de planification de voyages (frontend + backend) | Public |
| BaptTF Front | Mon site perso/portfolio/blog | Public |
| HA-EYG | Home Assistant (proxy via Tailscale ExternalName) | Tailscale |
| LLDAP | Annuaire LDAP + UI d’admin | Tailscale |
| Authelia | Portail SSO + OIDC provider | Public |
Désactivés (en standby)
Monitoring (kube-prometheus-stack), Forgejo, OpenWebUI, MinIO, Obsidian LiveSync, Velero et quelques autres.
Rétrospective
Bons choix
- ArgoCD + GitOps, ne plus jamais se connecter en SSH à un serveur pour déployer. C’est trop bien.
- Tailscale simplifie tout. Réseau inter-nœuds, accès admin, ingress privé, c’est le même outil pour tout.
- CloudNative-PG, c’est du PostgreSQL managé sur Kubernetes, avec des sauvegardes automatiques. Ça simplifie énormément la gestion des bases de données sur Kubernetes.
- Traefik avec les IngressRoute CRs et cert-manager, c’est super simple d’exposer des services sur une URL.
- Renovate + Image Updater font que les mises à jour se font en un clic via une PR, et sinon automatiquement.
Évolution de l’infrastructure
Historiquement, le control-plane tournait sur Contabo (8 Go) et un second nœud (bapt-debian, une VM chez un pote avec 8 Go) faisait office de worker. Quand cette VM s’est éteinte, j’ai dû manuellement scaler à zéro plusieurs workloads sur Contabo pour éviter l’OOM. J’ai ensuite cherché un nouveau VPS pour remplacer le worker. C’est Hetzner (15 Go). Au passage, j’ai inversé les rôles : Hetzner est devenu control-plane (il a la RAM nécessaire pour supporter etcd et les workloads critiques) et Contabo est devenu un worker. En août, je vais faire passer Contabo de 8 Go à 12 Go de RAM. Pas par choix : Contabo a augmenté ses prix et offre l’upgrade gratuitement en contrepartie.
Choix discutables
- Infisical crée une dépendance à un SaaS externe pour les secrets. J’avais hésité avec HashiCorp Vault, mais c’était overkill pour ce dont j’avais besoin. Quand j’avais posé la question à Gemini, il m’avait fait l’analogie : « Vault sur un VPS de 8 Go, c’est comme installer un coffre-fort suisse pour garder les clés d’une Twingo ». Infisical est plus léger, plus simple à mettre en place, et il fait exactement ce dont j’ai besoin sans la complexité de Vault. Peut-être que je changerai dans le futur, mais pour l’instant c’est très bien.
- Pas de monitoring permanent. kube-prometheus-stack est désactivé, car j’avais besoin d’économiser de la RAM. Il faut que je remette ma stack de monitoring.
Ce que je changerais
- Garder le monitoring actif en permanence sur Contabo (worker, 12 Go), qui a suffisamment de marge en stockage et en mémoire pour l’accueillir.
- Automatiser les backups Restic, actuellement manuels. Je regarde pour les envoyer soit dans un bucket S3, soit sur une autre Storage Box, soit sur un NAS ; je ne suis pas encore décidé.