> ./exec Devops_cloud.sh — ARTICLE
Kubernetes sur Hetzner : guide de déploiement complet avec Terraform
MarcusSoyons directs : Les entreprises de 50 à 250 collaborateurs qui migrent leurs workloads vers Kubernetes paient régulièrement trois à cinq fois plus cher sur AWS EKS ou Azure AKS que sur Hetzner Cloud, sans valeur ajoutée significative pour la charge applicative typique de ces organisations. Choisir Hetzner n'est pas un compromis. C'est une décision architecturalement correcte.
Martin Fowler décrit dans Patterns of Enterprise Application Architecture (2002) un principe plus actuel que jamais : la complexité ne doit jamais être introduite sans bénéfice proportionnel. AWS et Azure livrent cette complexité à l'échelle industrielle. Hetzner fournit une API européenne, propre, conforme au RGPD, avec des coûts prévisibles. C'est suffisant pour neuf projets ETI sur dix.
Ce guide montre comment déployer un cluster Kubernetes production-ready sur Hetzner Cloud avec Terraform. Pas de Hello World. Pas de « ça dépend ». Un setup qui part en production le lundi matin.
L'architecture que nous construisons
Trois nœuds Control Plane (HA), trois Worker Nodes, un Hetzner Load Balancer, réseau privé via Hetzner Private Network, Cilium comme CNI, Hetzner CSI Driver pour le Block Storage, k3s comme distribution Kubernetes.
Pourquoi k3s et non kubeadm ? Sam Newman écrit dans Building Microservices (2e édition, 2021) sur l'« overhead opérationnel » comme décision d'architecture cachée. Kubeadm est puissant, mais la charge de maintenance pour une équipe DevOps de deux à quatre personnes est considérable : sauvegardes etcd, rotation de certificats, gestion du cycle de vie des nœuds. k3s abstrait la majeure partie de ces tâches, est certifié CNCF et tourne en production depuis 2019 dans des milliers d'environnements. Le choix est sans ambiguïté.
Pourquoi Cilium et non Flannel ? Cilium repose sur eBPF et offre l'application des NetworkPolicies, une observabilité intégrée via Hubble et des performances réseau mesurées 20 à 30 % supérieures à Flannel sur matériel identique (Cilium Benchmark Report, 2023). Flannel convient à l'apprentissage. Pas à la production.
Terraform : provider et Remote State
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.47"
}
}
backend "s3" {
# Hetzner Object Storage, compatible S3
endpoint = "https://fsn1.your-objectstorage.com"
bucket = "tf-state-k8s"
key = "kubernetes/terraform.tfstate"
region = "eu-central-1"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
force_path_style = true
}
}
provider "hcloud" {
token = var.hcloud_token
}
Le Remote State n'est pas optionnel. Versionner le State Terraform en local ou dans Git constitue un problème de sécurité sérieux : le State contient des secrets en clair. Hetzner Object Storage comme backend S3 coûte quelques centimes par mois et élimine le problème complètement.
Réseau et infrastructure SSH
resource "hcloud_ssh_key" "k8s_deploy" {
name = "k8s-deploy"
public_key = file("~/.ssh/k8s_hetzner.pub")
}
resource "hcloud_network" "k8s_network" {
name = "k8s-private"
ip_range = "10.0.0.0/16"
}
resource "hcloud_network_subnet" "k8s_subnet" {
network_id = hcloud_network.k8s_network.id
type = "cloud"
network_zone = "eu-central"
ip_range = "10.0.1.0/24"
}
Le réseau privé est le chemin de sécurité central. Les communications internes à Kubernetes transitent exclusivement par ce sous-réseau. Le Load Balancer est le seul point d'entrée depuis l'extérieur. L'accès SSH direct aux Worker Nodes doit être bloqué en production, seul un Bastion Host ou un service Jump équivalent est autorisé.
Nœuds Control Plane : HA sur trois sites
resource "hcloud_server" "control_plane" {
count = 3
name = "k8s-cp-${count.index + 1}"
server_type = "cax21" # Ampere ARM64, 4 vCPU, 8 Go RAM
image = "ubuntu-24.04"
location = element(["nbg1", "fsn1", "hel1"], count.index)
ssh_keys = [hcloud_ssh_key.k8s_deploy.id]
network {
network_id = hcloud_network.k8s_network.id
ip = "10.0.1.${count.index + 10}"
}
labels = {
role = "control-plane"
environment = var.environment
}
}
Trois nœuds Control Plane, répartis sur Nuremberg, Falkenstein et Helsinki. Ce n'est pas de la paranoïa, c'est le minimum pour la HA. Un Control Plane mono-nœud n'est pas acceptable, même pour les petits clusters. L'etcd embarqué dans k3s requiert un nombre impair pour le quorum. Trois est l'optimum pragmatique pour les projets ETI.
Le choix du cax21 (ARM64/Ampere) est délibéré : les instances ARM de Hetzner offrent le même rapport qualité-prix que les AWS Graviton, aux tarifs Hetzner. Go, Rust, Python et Node.js fonctionnent nativement sur ARM64. La seule contrainte réelle : les binaires propriétaires sans build ARM64. C'est alors un problème d'architecture, pas un problème Hetzner.
Worker Nodes et Load Balancer
resource "hcloud_server" "worker" {
count = 3
name = "k8s-worker-${count.index + 1}"
server_type = "cax31" # Ampere ARM64, 8 vCPU, 16 Go RAM
image = "ubuntu-24.04"
location = element(["nbg1", "fsn1", "hel1"], count.index)
ssh_keys = [hcloud_ssh_key.k8s_deploy.id]
network {
network_id = hcloud_network.k8s_network.id
ip = "10.0.1.${count.index + 20}"
}
labels = {
role = "worker"
environment = var.environment
}
}
resource "hcloud_load_balancer" "k8s_ingress" {
name = "k8s-ingress"
load_balancer_type = "lb11"
location = "nbg1"
}
resource "hcloud_load_balancer_network" "k8s_ingress_network" {
load_balancer_id = hcloud_load_balancer.k8s_ingress.id
network_id = hcloud_network.k8s_network.id
}
Bootstrap k3s : initialisation du cluster
Après le terraform apply, les nœuds sont initialisés via cloud-init. Le premier nœud Control Plane démarre en tant qu'initiateur de cluster ; les suivants rejoignent en tant que serveurs et non en tant qu'agents :
# Premier nœud Control Plane
curl -sfL https://get.k3s.io | sh -s - server \
--cluster-init \
--tls-san "${LOAD_BALANCER_IP}" \
--disable traefik \
--disable servicelb \
--flannel-backend=none \
--disable-network-policy \
--secrets-encryption \
--node-ip="${PRIVATE_IP}"
# Nœuds Control Plane suivants
curl -sfL https://get.k3s.io | sh -s - server \
--server "https://${FIRST_CP_PRIVATE_IP}:6443" \
--token "${K3S_TOKEN}" \
--tls-san "${LOAD_BALANCER_IP}" \
--disable traefik \
--disable servicelb \
--flannel-backend=none \
--disable-network-policy \
--secrets-encryption
--disable traefik et --disable servicelb ne sont pas négociables : nous remplaçons les deux par NGINX Ingress Controller et le Hetzner Cloud Controller Manager. --secrets-encryption active le chiffrement etcd, désactivé par défaut dans k3s, ce qui laisse les Secrets Kubernetes en clair sur le disque. Un état inacceptable.
Cilium et Hetzner Cloud Controller Manager
# Cilium via Helm
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium \
--namespace kube-system \
--set operator.replicas=2 \
--set kubeProxyReplacement=true \
--set hubble.enabled=true \
--set hubble.relay.enabled=true
# Hetzner Cloud Controller Manager
kubectl apply -f \
https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/latest/download/ccm-networks.yaml
# Hetzner CSI Driver
kubectl apply -f \
https://raw.githubusercontent.com/hetznercloud/csi-driver/main/deploy/kubernetes/hcloud-csi.yml
Le Hetzner CCM est la clé de l'intégration native : il provisionne automatiquement des Load Balancers pour les ressources Service de type LoadBalancer. Le driver CSI permet la création dynamique de PersistentVolume via Hetzner Block Storage, stockage NVMe, jusqu'à 10.000 IOPS par volume.
Anti-patterns que je rencontre chaque jour
1. Le Cluster Autoscaler dès le premier jour. L'auto-scaling ne résout pas les problèmes typiques d'un cluster ETI. Il en crée de nouveaux : coûts imprévisibles, race conditions à l'initialisation des nœuds, états difficiles à déboguer. Commencez avec des groupes de nœuds fixes. Scalez manuellement via Terraform. Introduisez le Cluster Autoscaler lorsque des données réelles d'utilisation sont disponibles, pas avant.
2. Tout dans le Namespace default.
L'isolation par Namespace n'est pas un luxe. C'est le fondement du RBAC, des NetworkPolicies et des Resource Quotas. Déployer des workloads de production dans default revient à n'avoir aucune séparation de périmètre, même de facto. Un Namespace par équipe ou par service est le minimum.
3. kubectl apply directement en production.
John Vlissides et al. expliquent dans Design Patterns (GoF, 1994) pourquoi les mutations directes non contrôlées corrompent la conception d'un système. Cela vaut autant pour les objets logiciels que pour les états d'un cluster. GitOps via ArgoCD ou Flux n'est pas un complément optionnel. C'est la seule réponse à la question : « qu'est-ce qui tourne actuellement sur mon cluster, et pourquoi ? »
4. Aucun PodDisruptionBudget. Les Node Drains lors des mises à jour ou des fenêtres de maintenance peuvent interrompre totalement les déploiements sans PDB. Deux lignes de YAML suffisent à prévenir des indisponibilités non planifiées. Il n'existe aucune justification pour omettre ces deux lignes.
Checklist de production avant la mise en ligne
- Remote State (Hetzner Object Storage) configuré, State Locking actif
--secrets-encryptionactivé sur tous les nœuds Control Plane- Snapshots etcd quotidiens vers Object Storage (
k3s etcd-snapshot save) - Cilium NetworkPolicies : default-deny entre les Namespaces
- cert-manager avec Let's Encrypt installé et vérifié
- ArgoCD ou Flux déployé, tous les manifestes issus de Git
- kube-prometheus-stack (Prometheus + Grafana + Alertmanager) actif
- PodDisruptionBudgets définis pour tous les déploiements critiques
- Resource Requests et Limits définis pour chaque conteneur
- Règles de pare-feu : Worker Nodes non accessibles directement depuis Internet
- Automatisation des snapshots Hetzner activée pour la récupération des nœuds
Coûts : ce que la comparaison révèle vraiment
Un cluster production-ready conforme à l'architecture décrite (3× Control Plane cax21, 3× Worker cax31, 1× Load Balancer lb11) coûte chez Hetzner environ 180–220 EUR/mois trafic inclus.
Un cluster EKS comparable à Francfort (3× m6g.large Control Plane, 3× m6g.xlarge Worker, 1× ALB) coûte chez AWS environ 650–800 EUR/mois, hors coûts de transfert de données, hors remises Reserved Instances, hors plan de support.
Le facteur 3 à 4 est réel et documenté. Sur trois ans, l'écart représente un montant à six chiffres, qui peut être réinvesti dans le produit, la capacité de développement ou les réserves. C'est le Hetzner Cloud Consulting en chiffres concrets.
Conclusion
Kubernetes sur Hetzner avec Terraform n'est pas un compromis pour les équipes contraintes budgétairement. C'est la réponse architecturalement correcte pour les ETI européennes : conforme au RGPD, datacenter européen, coûts prévisibles, aucune dépendance propriétaire au-delà d'une API cloud propre.
L'architecture décrite ici, k3s en HA, Cilium, Hetzner CCM et CSI, GitOps, Remote State chiffré, est éprouvée en production. Elle donne à une équipe DevOps de deux à quatre personnes le contrôle nécessaire, sans générer l'overhead opérationnel que personne n'a budgété.
Recommandation : Démarrez avec ce setup. Mesurez pendant trois mois. Scalez ensuite sur la base de données d'utilisation réelles, pas sur la base de présentations commerciales d'hyperscalers.
Marcus est Solution Architect chez NextGen IT. Il accompagne les ETI dans leur transition vers des infrastructures cloud-natives, le Hetzner Cloud Consulting et les architectures Kubernetes pour les équipes de moins de 250 collaborateurs.
Marcus
Solution Architect
Architecture globale, ADR, coherence technique, knowledge graphs.
Besoin d'aide sur Devops & Cloud?
Premier échange gratuit, forfait après audit.
INIT_CONSULTATION() →