Kubernetes su Hetzner Cloud: setup economico per startup e PMI italiane
Qualche mese fa mi ha contattato una startup italiana che sviluppa un applicativo SaaS di gestione documentale per studi professionali: qualche centinaio di utenti attivi, un'architettura a microservizi con una mezza dozzina di container e un vincolo di budget infrastrutturale rigido, nell'ordine delle decine di euro al mese. Il preventivo che avevano in mano per un cluster gestito su uno dei grandi cloud americani, con tre nodi worker, load balancer applicativo e storage a blocchi, superava i trecento euro al mese, traffico in uscita escluso. Per un'azienda in fase di traction, in cui ogni euro speso in infrastruttura è un euro sottratto al prodotto, quella cifra non era sostenibile. Su Hetzner Cloud, con k3s e i driver ufficiali Hetzner per il cloud controller e lo storage, ho costruito per loro un cluster di produzione a tre nodi con alta disponibilità reale, a un costo mensile di un ordine di grandezza inferiore. Stesse prestazioni, stessa elasticità, una frazione del conto.
Kubernetes non è per tutti, e lo dico subito per evitare fraintendimenti. Se hai un singolo applicativo Laravel su un VPS che serve qualche centinaio di utenti, Kubernetes è un over-engineering colossale: aggiunge complessità operativa senza restituire valore. Ma se hai tre o più servizi containerizzati che devono essere deployati in modo indipendente, scalati in base al carico e riavviati da soli quando crashano, allora l'orchestrazione diventa lo strumento giusto. E su Hetzner Cloud diventa anche economicamente accessibile, perché elimini la voce di costo che rende proibitivi i managed Kubernetes: il control plane fatturato a parte.
Quanto costa davvero un cluster Kubernetes per una PMI?
La risposta dipende interamente da dove lo esegui, e la differenza non è marginale: è di un ordine di grandezza. La voce che fa esplodere il conto sui cloud gestiti non sono i nodi, ma il control plane fatturato come servizio più il load balancer applicativo e il traffico in uscita. Su un cluster gestito di un grande provider americano il solo control plane viaggia intorno ai 70 euro al mese, a cui si sommano i nodi worker, lo storage a blocchi e un load balancer di livello 7 che da solo può costare quanto un intero nodo Hetzner. È qui che k3s ribalta l'economia: il control plane gira sui tuoi nodi, non lo paghi a parte, e il load balancer è una risorsa cloud nativa a basso costo.
Per ancorare il ragionamento a numeri verificabili, questi sono i prezzi di listino Hetzner Cloud per le linee shared e dedicated vCPU nelle location europee (Falkenstein, Norimberga, Helsinki), listino EUR, IVA esclusa, rilevati a giugno 2026 dopo il repricing entrato in vigore il primo aprile 2026:
| Linea | Riferimento | Prezzo (EUR/mese, eu-central, IVA escl.) |
|---|---|---|
| Entry shared Intel | CX23 (2 vCPU, 4 GB) | 3,99 |
| Mid shared Intel | CX33 (4 vCPU, 8 GB) | 6,49 |
| Mid shared Arm64 | CAX21 (4 vCPU, 8 GB) | 7,99 |
| Mid shared AMD | CPX22 | 7,99 |
| Entry dedicated vCPU | CCX13 | 15,99 |
Una nota metodologica che applico sempre, e che vale per chiunque scriva di prezzi cloud: Hetzner gestisce listini separati per valuta e per zona di rete. Le location europee hanno il listino EUR; le region statunitensi (Ashburn, Hillsboro) e Singapore hanno prezzi diversi e più alti, esposti anche in USD. Un prezzo non si converte mai "per comodità": si cita nella valuta e nella zona del listino di origine. Per gli IP, il Primary IP IPv4 costa 0,50 EUR/mese a parte.
Per un cluster a tre nodi su CX33, la spesa di calcolo è poco meno di venti euro al mese. A questa si aggiungono il load balancer cloud (classe sotto i dieci euro/mese per il taglio entry LB11, fascia di prezzo che invecchia e che vale la pena verificare sulla pagina ufficiale dei load balancer Hetzner al momento dell'acquisto) e i volumi a blocchi per i dati persistenti, fatturati per gigabyte. Il totale resta saldamente sotto la soglia che rende un cluster gestito improponibile per una piccola realtà. Il trade-off è operativo, non prestazionale: il control plane lo gestisci tu. Ma k3s è progettato esattamente per questo, e con la sua modalità etcd embedded ottieni un'alta disponibilità reale senza dover orchestrare un cluster di database separato.
Se gestisci infrastrutture per PMI e ti stai chiedendo se questo modello regge nel tempo, nel mio profilo professionale trovi l'esperienza concreta su cluster k3s in produzione, hardening Linux e gestione di VPS Hetzner: la differenza tra un cluster che "funziona in demo" e uno che regge per anni sta tutta nelle operazioni del giorno dopo, non nel comando di installazione.
k3s e Kubernetes vanilla: cosa cambia e perché conta in produzione
k3s è una distribuzione Kubernetes leggera, oggi mantenuta sotto l'egida della CNCF e originariamente sviluppata da Rancher (parte di SUSE), certificata come distribuzione Kubernetes conforme. Non è un Kubernetes "ridotto" nel senso di castrato: è un Kubernetes conforme, con lo stesso set di API, impacchettato in modo radicalmente più semplice da gestire. La versione corrente alla stesura è la v1.36.1+k3s1, che incorpora Kubernetes 1.36, l'edizione corrente della piattaforma di orchestrazione secondo il calendario ufficiale delle release Kubernetes, insieme a etcd v3.6.7 embedded, containerd, CoreDNS e Flannel come CNI di default.
La differenza con un Kubernetes vanilla installato a mano (kubeadm) sta in cosa k3s impacchetta e cosa rende opzionale: l'intero control plane vive in un singolo binario, il datastore può essere SQLite per il nodo singolo oppure etcd embedded per la configurazione ad alta disponibilità, e componenti come Traefik (ingress) e ServiceLB sono inclusi ma disinstallabili. Questo è il punto che la maggior parte dei tutorial sbaglia.
In produzione il datastore SQLite a nodo singolo non è un'opzione: è un single point of failure. La configurazione corretta su Hetzner usa etcd embedded con tre nodi server, perché etcd richiede un numero dispari di membri per mantenere il quorum. Con tre server il cluster tollera la perdita di un nodo senza interruzione del control plane; con uno solo, quando quel nodo cade, cade tutto.
L'installazione del primo nodo server su un VPS Hetzner con Debian 13 (la stable corrente) inizializza il cluster in modalità etcd embedded:
# Primo nodo server: inizializza il cluster in modalità etcd embedded (HA)
# --cluster-init abilita etcd al posto di SQLite
# --disable=traefik perché in produzione uso Nginx Ingress Controller
# --flannel-iface lega il traffico del cluster alla rete privata (vedi sotto)
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
--cluster-init \
--disable=traefik \
--disable=servicelb \
--flannel-iface=enp7s0 \
--tls-san=10.0.0.2 \
--write-kubeconfig-mode=600" sh -
# Recupera il token per aggiungere gli altri nodi
cat /var/lib/rancher/k3s/server/node-tokenDisabilito Traefik perché in produzione preferisco l'Nginx Ingress Controller: offre più controllo sulle annotazioni di rate limiting, buffering del proxy e header personalizzati. Disabilito anche ServiceLB (il load balancer on-metal di k3s, Klipper) perché su Hetzner uso il load balancer cloud nativo, gestito dal Cloud Controller Manager. Il secondo e il terzo nodo server si aggiungono al cluster etcd così:
# Nodo server 2 e 3: si uniscono al control plane HA (etcd a 3 membri)
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
--server https://10.0.0.2:6443 \
--disable=traefik \
--disable=servicelb \
--flannel-iface=enp7s0" \
K3S_TOKEN="<token-dal-primo-server>" sh -In pochi minuti hai un cluster Kubernetes a tre nodi con control plane in alta disponibilità, DNS interno (CoreDNS) e kubectl configurato. Per chi vuole eliminare del tutto la gestione manuale, esiste il modulo Terraform della community kube-hetzner: provisioning di un cluster k3s maintenance-free su openSUSE MicroOS con un solo comando, auto-upgrade del sistema operativo e di k3s, scelta della CNI (Flannel, Calico o Cilium), supporto nativo alle istanze Arm64 CAX più economiche e autoscaling integrato. È la via che consiglio quando il cliente vuole un cluster riproducibile e versionato in Infrastructure as Code anziché installato a mano.
Come si configurano storage persistente e load balancer su Hetzner?
Servono due componenti ufficiali Hetzner: il Cloud Controller Manager (CCM), che insegna a Kubernetes a parlare con l'API Hetzner per load balancer e lifecycle dei nodi, e il CSI driver, che permette a Kubernetes di creare e montare automaticamente volumi a blocchi. Senza CSI driver un cluster gestisce solo carichi stateless; il primo database o file upload richiede storage persistente che sopravviva al riavvio di un pod.
Hetzner mantiene entrambi i progetti come software ufficiale e open source: l'hcloud-cloud-controller-manager (versione corrente v1.33.0) e il CSI driver (versione corrente v2.21.2), che si integra con gli Hetzner Cloud Volumes, storage a blocchi SSD replicato e ad alta disponibilità. L'installazione richiede un token API Hetzner Cloud e l'applicazione dei manifest:
# Secret con il token API Hetzner e l'id della rete privata
kubectl -n kube-system create secret generic hcloud \
--from-literal=token="<hetzner-api-token>" \
--from-literal=network="<network-id>"
# Cloud Controller Manager (variante con networks, per la rete privata)
kubectl apply -f https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/latest/download/ccm-networks.yaml
# CSI driver: gestisce i Cloud Volumes come PersistentVolume
kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/main/deploy/kubernetes/hcloud-csi.yml
# Verifica che la storage class sia attiva
kubectl get storageclass
# Atteso: hcloud-volumes (default)Da questo momento qualsiasi PersistentVolumeClaim con storage class hcloud-volumes crea automaticamente un volume Hetzner e lo monta nel pod. Il volume minimo è 10 GB per vincolo Hetzner e la fatturazione è al gigabyte: per un PostgreSQL con qualche decina di gigabyte di dati la spesa di storage resta nell'ordine di pochi euro al mese, e i prezzi puntuali per gigabyte vanno verificati sulla pagina pricing ufficiale al momento dell'acquisto, perché la fascia cambia. Il CSI driver di Hetzner monta volumi in modalità ReadWriteOnce: un volume si attacca a un nodo alla volta, quindi per workload che richiedono accesso concorrente in scrittura da più pod serve un'altra strategia (object storage o un file system di rete dedicato), non il volume a blocchi.
Il load balancer si dichiara con un'annotazione sul Service di tipo LoadBalancer, e il CCM lo provisiona da solo:
apiVersion: v1
kind: Service
metadata:
name: nginx-ingress
annotations:
load-balancer.hetzner.cloud/type: "lb11"
load-balancer.hetzner.cloud/location: "fsn1"
load-balancer.hetzner.cloud/name: "k3s-lb"
# instradamento via rete privata: il LB raggiunge i nodi sull'IP interno
load-balancer.hetzner.cloud/use-private-ip: "true"
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
selector:
app: nginx-ingressIl CCM crea il load balancer Hetzner, lo collega ai nodi e aggiorna i target quando l'insieme dei nodi cambia. Il traffico HTTPS arriva al load balancer, viene instradato all'Nginx Ingress Controller e smistato ai pod corretti in base alle regole di ingress. L'intero percorso, dal DNS alla risposta del container, è gestito da Kubernetes senza intervento manuale. Le release recenti del CCM hanno aggiunto la riconciliazione delle route basata su eventi anziché a intervallo fisso, riducendo le chiamate inutili all'API, e la possibilità di collocare il load balancer in una sottorete privata specifica.
La rete privata non è un dettaglio: è un requisito di sicurezza
C'è un aspetto che molti tutorial trascurano e che in produzione, soprattutto per applicazioni soggette al GDPR, è un requisito non negoziabile: la rete privata tra i nodi. Di default, due VPS Hetzner comunicano attraverso la rete pubblica. Questo significa che, senza configurazione esplicita, il traffico interno del cluster (la replica di etcd tra i membri, le comunicazioni kubelet-apiserver, il traffico tra pod su nodi diversi) può transitare su interfacce esposte. Per dati personali di utenti europei è una postura inaccettabile.
La soluzione è creare una Cloud Network Hetzner, assegnare ai nodi gli IP privati e legare il networking del cluster a quell'interfaccia con il flag --flannel-iface (presente nei comandi di installazione sopra). La Cloud Network è una funzionalità inclusa, senza costo aggiuntivo. Una volta in piedi, il traffico inter-nodo viaggia su rete privata e il load balancer raggiunge i target sull'IP interno grazie all'annotazione use-private-ip. Questo è il genere di dettaglio che distingue un cluster montato seguendo un tutorial da un cluster pensato per la produzione.
Il Cloud Controller Manager, oltre al provisioning dei load balancer, svolge una seconda funzione critica: il lifecycle management dei nodi. Se un nodo Hetzner viene distrutto, per un guasto hardware o una manutenzione del provider, il CCM lo rileva e notifica Kubernetes, che rischedula i pod su un nodo sano. Senza CCM, Kubernetes continuerebbe a credere che il nodo esista e i pod resterebbero in stato Terminating fino al timeout di default, causando un'interruzione del servizio evitabile. Sullo stesso impianto si innesta il Cluster Autoscaler, che su questo provider può aggiungere e rimuovere nodi worker in base al carico, pagando l'elasticità solo quando serve.
Day-2 operations: backup di etcd, aggiornamenti e monitoring
Il cluster è in piedi, ma il lavoro vero comincia il giorno dopo il deploy. Le tre operazioni che determinano se un cluster k3s sopravvive in produzione sono il backup del datastore, gli aggiornamenti e il monitoring: gli stessi principi che applico a qualsiasi infrastruttura critica, come ho descritto nel mio articolo sull'architettura Docker in produzione su Hetzner.
Il backup del datastore, in configurazione etcd embedded, è integrato in k3s e si configura per snapshot schedulati con retention e push verso uno storage esterno (per esempio un bucket S3-compatibile):
# Snapshot manuale di etcd
k3s etcd-snapshot save --name backup-$(date +%Y%m%d)
# Snapshot automatici via /etc/rancher/k3s/config.yaml (su ogni server):
# etcd-snapshot-schedule-cron: "0 2 * * *"
# etcd-snapshot-retention: 7
# etcd-s3: true
# etcd-s3-endpoint: "<endpoint-s3>"
# etcd-s3-bucket: "<bucket>"Il backup di etcd è l'unica cosa che ti separa da una ricostruzione manuale del cluster dopo un disastro. Uno snapshot giornaliero che resta sullo stesso nodo non è un backup: è un file. Il backup è lo snapshot che vive altrove, su uno storage indipendente, testato in restore almeno una volta.
L'aggiornamento è il punto in cui la semplicità di k3s si fa sentire: si aggiorna il binario e si riavvia il servizio, un nodo alla volta. Su un cluster a tre nodi si svuotano i carichi con kubectl drain, si aggiorna il nodo, si rimette in servizio con kubectl uncordon, poi si passa al successivo. Con almeno due repliche per pod e i PodDisruptionBudget configurati, l'operazione non causa downtime. C'è però un'avvertenza concreta: gli upgrade che attraversano un cambio di versione minore di etcd richiedono attenzione, perché alcune transizioni di versione di Kubernetes hanno comportato aggiornamenti di etcd con vincoli specifici. È esattamente la classe di problema per cui, su questi cluster, leggo le release notes prima di lanciare l'upgrade e non delego l'aggiornamento a un automatismo cieco.
Il monitoring minimo che installo su ogni cluster k3s in produzione è kube-prometheus-stack: Prometheus per le metriche, Grafana per le dashboard, AlertManager per le notifiche. Si installa con Helm e fornisce out-of-the-box le dashboard per stato dei nodi, utilizzo risorse dei pod e alerting su condizioni critiche (nodo NotReady, pod in CrashLoopBackOff, disco pieno). Le alert le indirizzo su un canale dedicato, così il team riceve la notifica entro pochi secondi se un pod crasha o un nodo diventa irraggiungibile.
Hetzner self-managed o infrastruttura gestita: dove sta la scelta
Questo modello, k3s su Hetzner Cloud, è la scelta giusta quando hai in casa la competenza per gestire il control plane, gli aggiornamenti e il backup di etcd, oppure quando affianchi un consulente che la porta. Hetzner resta il riferimento europeo per il cloud self-managed: datacenter di proprietà in Germania e Finlandia, certificazione ISO/IEC 27001:2022, DPA GDPR standard e un rapporto prezzo/prestazioni che è il benchmark del mercato. Il rovescio della medaglia è che il provider ti dà l'infrastruttura, non la gestione: il day-2 è tuo.
Per una PMI italiana che invece vuole infrastruttura gestita, con il dato che non lascia l'Italia, la conversazione cambia. Quando il requisito è la gestione sistemistica inclusa, il disaster recovery presidiato e un datacenter italiano, la prima scelta che consiglio è RHX: private cloud su KVM con storage full SSD, datacenter a Milano (adiacente al MiX) e Padova, registrar accreditato del Registro .it e gestione del sistema in outsourcing. Il confronto con Hetzner non si fa sul prezzo nudo del vCPU, ma sul costo totale di gestione: un cluster Hetzner self-managed costa poco in listino e molto in ore di competenza; una soluzione gestita sposta quel costo dove un'azienda senza reparto IT lo può sostenere meglio. La scelta giusta dipende da chi, in concreto, presidia l'infrastruttura il giorno in cui qualcosa si rompe.
Un cluster k3s su Hetzner in produzione, ben configurato, regge per anni con interruzioni pianificate e zero downtime non previsti, attraversa più aggiornamenti di versione senza fermarsi e serve volumi di traffico significativi a una frazione del costo dei managed Kubernetes dei grandi cloud. La documentazione ufficiale di Kubernetes resta il riferimento per i concetti architetturali, ma per l'implementazione pratica su Hetzner sono k3s, i driver ufficiali e gli strumenti della community a rendere il percorso molto più diretto di quanto la reputazione di Kubernetes lasci intendere. Se stai valutando l'orchestrazione per i tuoi servizi containerizzati e il budget dei grandi provider è fuori portata, oppure se hai già un cluster e vuoi capire se è davvero pronto per la produzione, contattami per una valutazione architetturale: in una sessione mirata analizziamo i requisiti, dimensioniamo il cluster e definiamo un piano di deployment con tempi e costi certi, senza sorprese sul conto del mese dopo.