k3s su un VPS: quando conviene davvero
Scopri quanta RAM richiede k3s, perché la porta 80 può causare conflitti durante l'installazione e quando Docker Compose resta la scelta più adatta.
Che cos'è k3s e cosa offre un singolo nodo
k3s è una distribuzione completa di Kubernetes fornita come singolo binario. Eseguirla su un singolo VPS offre la vera API di Kubernetes senza un control plane composto da tre macchine. È una distribuzione Kubernetes certificata, quindi un manifest applicabile qui funzionerà anche in seguito su un cluster gestito. L'installazione richiede un solo comando e circa un minuto. Il costo è la memoria non più disponibile per le applicazioni, oltre a una serie di scenari di errore che con Docker Compose non si verificano mai.
SUSE sviluppa k3s per siti edge e installazioni di piccole dimensioni. Ogni differenza rispetto a Kubernetes upstream serve a ridurne le dimensioni. Il datastore predefinito è sqlite, tramite uno shim chiamato kine, non etcd. Di conseguenza, non è necessario gestire un quorum etcd. containerd è incorporato nel binario anziché essere installato separatamente. Lo stesso binario fornisce anche CoreDNS per il DNS del cluster, Traefik come ingress controller, ServiceLB (chiamato anche klipper-lb) per consentire il funzionamento dei servizi LoadBalancer senza un cloud provider sottostante, il local-path provisioner per i volumi persistenti, metrics-server e flannel per la rete dei pod. Tutti questi componenti vengono avviati per impostazione predefinita. Per questo, su un VPS che eseguiva già altri servizi, il conflitto di porte descritto di seguito è il problema iniziale più comune.
Quando conviene usare un singolo nodo k3s
Segui questa regola. Usa k3s quando ti serve Kubernetes: per imparare Kubernetes su una macchina che controlli oppure quando il software che vuoi usare pubblica soltanto un chart Helm. Anche la portabilità dei manifest è un vantaggio: un Deployment che scrivi qui può essere trasferito senza modifiche a un cluster gestito. Usa Docker Compose quando ti servono le applicazioni. Compose avvia gli stessi container con molti meno componenti, e un file Compose su un VPS è più facile da leggere anche dopo un anno rispetto a una directory di manifest.
È importante sapere che cosa non offre un singolo nodo.
- Nessuna alta disponibilità. Quando il VPS viene riavviato, tutti i workload si arrestano. Kubernetes ripianifica un pod su un altro nodo, ma non esiste un altro nodo.
- Nessun aggiornamento rolling che mantenga attivo il servizio, a meno che l'applicazione supporti due repliche sulla stessa macchina che condividono un unico volume.
- Storage vincolato alla macchina, per il motivo descritto nella sezione local-path più avanti.
- Un control plane che consuma circa un gigabyte di RAM anche se non esegui alcun deployment.
Niente di tutto questo rende k3s una scelta sbagliata. La rende una scelta sbagliata per il motivo citato più spesso, cioè l'affidabilità. Se ciò che vuoi davvero sono più macchine per creare un cluster multi-node reale, questa decisione viene prima: Proxmox sul tuo hardware rispetto a un VPS in affitto stabilisce da dove provengono i nodi, prima che k3s stabilisca che cosa eseguire su di essi.
Quanto costano k3s in termini di RAM e CPU prima di eseguire il deployment
Il progetto k3s pubblica valori misurati, non stime. Leggili con attenzione, perché il valore citato più spesso non corrisponde a k3s inattivo.
The data behind this chart
[
{
"label": "Server, sqlite datastore",
"ram_mb": "1,596",
"cpu_percent_of_one_core": 6
},
{
"label": "Server, embedded etcd",
"ram_mb": "1,606",
"cpu_percent_of_one_core": 6
},
{
"label": "Agent node only",
"ram_mb": "275",
"cpu_percent_of_one_core": 3
}
]In quel test, un nodo server utilizzava 1,596 MB di RAM al 95° percentile e circa 6 percento di un core. Sono valori pubblicati, non misurazioni di questa guida. Il test eseguiva k3s v1.26.5 con tutti i componenti inclusi abilitati, oltre a uno stack di monitoraggio Prometheus e Grafana. Il valore comprende quindi un carico di lavoro reale, non un cluster vuoto. Sostituendo sqlite con embedded etcd, il consumo è salito a 1,606 MB. Un nodo agent, che esegue kubelet e containerd senza il control plane, utilizzava 275 MB. Il minimo documentato per un server è di 2 core e 2 GB di RAM. Questo minimo copre k3s e i relativi componenti inclusi prima dell'esecuzione dei tuoi carichi di lavoro.
In pratica, su un VPS da 2 GB il control plane e i componenti aggiuntivi inclusi lasciano pochissime risorse disponibili. Il primo effetto della pressione sulle risorse è l'eviction dei pod da parte di kubelet. 4 GB sono una base confortevole per un nodo con alcuni servizi di piccole dimensioni. Misura il tuo server invece di affidarti a qualsiasi valore pubblicato, compreso quello riportato qui.
free -h
sudo k3s kubectl top node
sudo k3s kubectl get pods -AEsegui free -h prima dell'installazione e ripetilo quando ogni pod in kube-system mostra lo stato Running. La differenza indica quanto costa il control plane sul tuo hardware. k3s kubectl top node restituisce error: Metrics API not available per il primo minuto o i primi due minuti dopo l'installazione, perché metrics-server non ha ancora eseguito alcuno scraping. Non è un errore. Se devi dimensionare un unico server per questo servizio e per altri carichi di lavoro eseguiti contemporaneamente, qui si applica senza modifiche il calcolo descritto in dimensionamento di RAM e CPU per un VPS.
Installare k3s bloccando una release, non usando latest
La riga di avvio rapido che tutti copiano usa il riferimento del canale stable nel giorno in cui la esegui. Su una macchina che prevedi di mantenere, blocca la versione. k3s pubblica un canale per ogni versione minor di Kubernetes, quindi INSTALL_K3S_CHANNEL=v1.36 segue le release patch all'interno di v1.36 e non passa automaticamente a una versione minor diversa. Ad agosto 2026 il canale stable punta a v1.36.3+k3s1.
Scrivi prima il file di configurazione, quindi esegui l'installazione. k3s legge /etc/rancher/k3s/config.yaml all'avvio, quindi tutte le impostazioni presenti si applicano sia al primo avvio sia a quelli successivi.
sudo mkdir -p /etc/rancher/k3s
sudo tee /etc/rancher/k3s/config.yaml >/dev/null <<'EOF'
tls-san:
- k3s.example.com
EOF
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=v1.36 sh -Per bloccare una release esatta invece di un canale, usa INSTALL_K3S_VERSION=v1.36.3+k3s1. Il segno più fa parte del tag. Verifica quindi che il servizio sia avviato.
k3s --version
sudo systemctl status k3s
sudo k3s kubectl get node
sudo k3s kubectl get pods -Akubectl get node dovrebbe elencare un nodo con STATUS Ready entro circa trenta secondi, e ogni pod in kube-system dovrebbe raggiungere lo stato Running o Completed. Un nodo bloccato su NotReady indica in genere che il runtime dei container non è mai stato avviato; consulta quindi sudo journalctl -u k3s -n 100 --no-pager. Su un'immagine VPS non standard, esegui sudo k3s check-config prima di eseguire altri controlli: segnala le funzionalità del kernel mancanti e fornisce una risposta molto più rapida rispetto alla lettura dei log.
Perché la porta 80 è già in uso e a cosa rinunciare per risolvere il problema
Questo è il problema che si verifica su un VPS che pubblicava già un servizio. L'installazione termina correttamente. Traefik però non riceve mai un indirizzo e il sito già in esecuzione continua a funzionare. Di conseguenza, non sembra esserci alcun problema finché non si prova a raggiungere un ingress.
Il meccanismo è il seguente: il chart Traefik incluso crea un Service di tipo LoadBalancer sulle porte 80 e 443. ServiceLB soddisfa questa richiesta creando un DaemonSet di pod di piccole dimensioni, con nomi che iniziano con il prefisso svclb-, che riservano quei numeri di porta come hostPort su ogni nodo. hostPort pubblica direttamente la porta del container sul namespace di rete del nodo, esattamente come fa docker run -p 80:80. Se nginx, Caddy, Apache o un altro container utilizza già la porta 80, il kernel non può assegnarla una seconda volta e lo scheduler non trova alcun nodo su cui eseguire il pod.
sudo k3s kubectl -n kube-system get pods
sudo k3s kubectl -n kube-system get svc traefik
sudo ss -lntp '( sport = :80 or sport = :443 )'Verranno visualizzati il pod svclb in stato Pending e il Service senza un indirizzo esterno:
svclb-traefik-8f2c1a-r6k9x 0/2 Pending 0 3m
traefik LoadBalancer 10.43.62.11 <pending> 80:31480/TCP,443:30219/TCPL'esecuzione di kubectl -n kube-system describe pod svclb-traefik-... indica direttamente la causa:
0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports.ss -lntp mostra quale processo occupa la porta. Esistono più soluzioni, ma ognuna comporta una rinuncia.
Assegnare le porte a k3s. Arrestare e disabilitare il web server esistente, quindi lasciare che Traefik gestisca le porte 80 e 443. È la scelta corretta quando il VPS deve essere dedicato a k3s e tutto ciò che veniva pubblicato deve passare attraverso un Ingress.
Disabilitare ServiceLB e mantenere il proxy esistente. Installare con --disable=servicelb. Un Service di tipo LoadBalancer assegna comunque un NodePort. Traefik resta quindi raggiungibile su una porta alta, ad esempio 31480, e nginx o Caddy inoltra le richieste a 127.0.0.1:31480. La rinuncia riguarda l'indirizzo esterno: il Service continua a indicare <pending>, per sempre. Questo può sembrare un errore, ma è una conseguenza della scelta effettuata.
Disabilitare Traefik e usare il proprio proxy. Installare con --disable=traefik. In questo modo non è presente alcun ingress controller, quindi gli oggetti Ingress non eseguono alcuna operazione: restano nell'API senza un controller che li monitori. Questa soluzione è corretta se si inoltra il traffico da un proxy sul nodo verso i NodePort ed è la scelta più trasparente se si sa già come gestire HTTP. Se non si è ancora deciso quale componente mettere davanti ai servizi, definire la scelta tra nginx, Caddy e Traefik come reverse proxy prima di disabilitare qualsiasi componente.
Entrambi i flag devono essere specificati nell'installer oppure nel file di configurazione:
tls-san:
- k3s.example.com
disable:
- traefik
- servicelbÈ possibile modificare il file dopo l'installazione ed eseguire sudo systemctl restart k3s, perché --disable non si limita a saltare un componente durante l'installazione. Elimina anche un componente già distribuito, quindi la modifica diventa effettiva su un cluster in esecuzione.
Per mantenere Traefik ma modificare la configurazione del chart, non modificare /var/lib/rancher/k3s/server/manifests/traefik.yaml. k3s riscrive quel file con i valori predefiniti ogni volta che viene avviato. Aggiungere invece un file separato nella stessa directory, perché tutto ciò che si trova in /var/lib/rancher/k3s/server/manifests viene applicato automaticamente all'avvio e ogni volta che il file cambia sul disco.
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
forwardedHeaders:
trustedIPs:
- 10.0.0.0/8Questo esempio imposta un valore del chart Traefik: gli indirizzi dei proxy considerati attendibili. Lo stesso meccanismo consente di impostare qualsiasi altro valore esposto dal chart, comprese le porte.
Archiviazione persistente su un nodo
k3s include una StorageClass predefinita chiamata local-path, gestita dal provisioner local-path di Rancher. Un PersistentVolumeClaim senza storageClassName la utilizza. I volumi risiedono in /var/lib/rancher/k3s/storage, con una sottodirectory per ogni volume, sul disco del nodo stesso.
sudo k3s kubectl get storageclass
sudo k3s kubectl get pvc -A
sudo ls -l /var/lib/rancher/k3s/storageL'espressione "sul disco del nodo stesso" comporta due conseguenze, che si manifestano in seguito e non immediatamente.
La StorageClass utilizza volumeBindingMode: WaitForFirstConsumer, quindi un nuovo PVC resta in stato Pending finché un pod non lo monta effettivamente. kubectl describe pvc stampa:
waiting for first consumer to be created before bindingÈ un comportamento normale. Creare un PVC da solo e attendere non ne cambierà lo stato.
Dopo il binding, il volume include un'affinità al nodo che lo ha creato. Questo vincola a tale nodo, per tutta la durata del volume, ogni pod che utilizza quella claim. Su un solo nodo il problema non si nota mai. Se in seguito aggiungi un secondo nodo, un pod che non riesce a spostarsi può sembrare vittima di un bug dello scheduler. Esegui kubectl get pv -o yaml e troverai il nome host in nodeAffinity.
I backup sono a tuo carico. Ricreare la VPS elimina quella directory, così come lo script di disinstallazione riportato di seguito. Esegui il backup di /var/lib/rancher/k3s/storage e anche del datastore sqlite in /var/lib/rancher/k3s/server/db/state.db, copiandolo con il servizio arrestato perché si tratta di un database attivo. In alternativa, considera il cluster temporaneo e conserva tutti i manifest in git.
Ingress e TLS
Con Traefik ancora abilitato, è sufficiente un oggetto Ingress standard.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: traefik
tls:
- hosts:
- hello.example.com
secretName: hello-tls
rules:
- host: hello.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello
port:
number: 80I certificati non vengono creati automaticamente. La soluzione consueta è cert-manager, installato dal manifest pubblicato dal progetto, insieme a un ClusterIssuer. La versione v1.21.1 è quella corrente ad agosto 2026.
sudo k3s kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.1/cert-manager.yamlapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
email: you@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- http01:
ingress:
ingressClassName: traefikLa challenge HTTP-01 richiede che il server ACME (automatic certificate management environment) si connetta a http://hello.example.com/.well-known/acme-challenge/... da Internet. Il record DNS A deve quindi puntare già al VPS e la porta 80 deve raggiungere Traefik. Se hai disabilitato ServiceLB e hai configurato un proxy personalizzato davanti a Traefik, anche quel proxy deve inoltrare il percorso della challenge. In caso contrario, cert-manager si blocca e nell'oggetto Challenge compare questo messaggio:
Waiting for HTTP-01 challenge propagation: wrong status code '404', expected '200'Monitora l'emissione con sudo k3s kubectl describe certificate hello-tls e sudo k3s kubectl get order,challenge -A.
Il kubeconfig e perché il server API resta privato
k3s scrive le credenziali amministrative in /etc/rancher/k3s/k3s.yaml. Il file appartiene a root e, per impostazione predefinita, viene creato con permessi 600. Contiene un certificato client con diritti cluster-admin. Chiunque possa leggerlo ha il controllo del cluster.
sudo k3s kubectl get node
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get nodeUn kubectl installato separatamente, senza KUBECONFIG impostato, restituisce The connection to the server localhost:8080 was refused - did you specify the right host or port? perché utilizza un valore predefinito che non ha alcun rapporto con k3s. Imposta KUBECONFIG oppure usa sudo k3s kubectl, che legge automaticamente il file corretto.
Vedrai spesso consigliare --write-kubeconfig-mode 644 per consentire a un utente normale di eseguire kubectl. È importante capire cosa comporta: rende leggibile a ogni account locale del computer una credenziale cluster-admin. Su un server utilizzato da un solo amministratore può essere un compromesso accettabile. Su un server condiviso, invece, non lo è. Copiare il file consente di dare l'accesso a un solo utente senza renderlo disponibile agli altri:
mkdir -p ~/.kube
sudo install -o $USER -g $USER -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/configLa riga server: in quel file contiene https://127.0.0.1:6443. Per usare kubectl dal laptop, non aprire la porta 6443 a Internet. Un'API Kubernetes pubblica è un bersaglio permanente; un'API esposta può portare piccoli cluster a eseguire mining di criptovalute per conto di terzi. Crea un tunnel SSH e lascia invariato l'indirizzo nel file:
ssh -N -L 6443:127.0.0.1:6443 user@your-vps
KUBECONFIG=./k3s.yaml kubectl get nodeSe devi raggiungere l'API tramite un indirizzo di rete privato, installa k3s con tls-san indicando quel nome o quell'indirizzo, quindi modifica la riga server: del file copiato in modo che corrisponda. Senza la voce SAN (subject alternative name), kubectl rifiuta la connessione:
x509: certificate is valid for 127.0.0.1, 10.43.0.1, not 203.0.113.10Le porte in ingresso documentate per un cluster sono TCP 6443 per l'API, UDP 8472 per flannel VXLAN tra i nodi e TCP 10250 per le metriche di kubelet. Su un nodo singolo, nessuna di queste porte deve essere aperta a Internet.
containerd non è Docker
k3s esegue il proprio containerd integrato e non condivide l'archivio delle immagini con Docker. Un'immagine appena creata con docker build non è visibile a k3s. Di conseguenza, il pod termina con ErrImagePull anche se docker images la elenca. Importala esplicitamente:
docker save myapp:0.1 | sudo k3s ctr images import -
sudo k3s crictl imagesEvita quindi il tag :latest per quel container, perché :latest è impostato per impostazione predefinita su imagePullPolicy di Always e kubelet consulta comunque un registry. Qualsiasi altro tag usa per impostazione predefinita IfNotPresent, che utilizza l'immagine importata. Eseguire entrambi sullo stesso VPS è possibile. È inoltre utile sapere che una normale installazione di Docker su un VPS e k3s mantengono ciascuno il proprio archivio delle immagini e le proprie regole iptables sulla stessa macchina.
Come rimuovere k3s
Il programma di installazione crea uno script di disinstallazione. La rimozione non è parziale e non può essere annullata.
sudo /usr/local/bin/k3s-uninstall.shArresta e rimuove il servizio, elimina il datastore, cancella i dati dei volumi persistenti, rimuove la configurazione del nodo e rimuove gli strumenti aggiunti dal programma di installazione. Su un nodo agent, lo script è invece k3s-agent-uninstall.sh. Prima copia fuori dal server tutto ciò che si trova in /var/lib/rancher/k3s/storage, perché quella directory viene rimossa insieme al resto. Al termine, verifica che nessun processo mantenga occupate porte o interfacce con ip link show e sudo ss -lntp. Un'interfaccia cni0 o flannel.1 rimasta attiva viene rimossa al riavvio successivo.
Stabilire che un singolo nodo Kubernetes richiedeva più componenti del necessario è un esito normale, non un fallimento. Di solito riportare questi workload a Compose richiede al massimo un pomeriggio.
Modalità di errore e stringhe visualizzate
Node NotReady oppure k3s si riavvia in un ciclo continuo. Leggere prima sudo journalctl -u k3s -n 200 --no-pager. Su un piccolo VPS, la causa comune è l'out-of-memory killer del kernel, che termina il processo. In dmesg questo evento compare come una riga che indica k3s-server. Il minimo documentato di 2 GB è un requisito effettivo.
Pod bloccato in Pending. kubectl describe pod indica sempre il motivo. Insufficient memory o Insufficient cpu significa che il nodo non ha più risorse disponibili. didn't have free ports indica la collisione hostPort descritta sopra. waiting for first consumer su una PVC significa che WaitForFirstConsumer sta funzionando correttamente.
ImagePullBackOff. Il tag non esiste in alcun registry raggiungibile dal nodo oppure l'immagine è stata creata con Docker e non è mai stata importata in containerd.
Traefik risponde, ma l'applicazione no. Un corpo della risposta pari a 404 page not found proviene direttamente da Traefik e significa che la richiesta è arrivata, ma nessun router corrispondeva. Verificare che host dell'Ingress corrisponda al nome immesso e che ingressClassName sia traefik.
Il DNS del cluster non funziona, mentre la risoluzione sull'host funziona. Controllare CoreDNS con sudo k3s kubectl -n kube-system logs -l k8s-app=kube-dns. Un messaggio come plugin/loop: Loop ... detected for zone "." impedisce l'avvio di CoreDNS. Il problema si verifica perché CoreDNS inoltra le richieste a un resolver che le inoltra nuovamente a CoreDNS; è ciò che accade quando in /etc/resolv.conf è presente un indirizzo di loopback. Indicare a k3s il file contenente il resolver upstream reale con --resolv-conf /run/systemd/resolve/resolv.conf.
FAQ
Vale la pena eseguire k3s su un singolo VPS?
Sì, quando ti serve l'API Kubernetes: per imparare a usarla su una macchina che controlli, mantenere i deployment portabili come manifest, eseguire software che pubblica solo un chart Helm o preparare un ambiente da spostare in seguito su un cluster gestito. Non ne vale la pena se vuoi soltanto eseguire container, perché Docker Compose svolge questa funzione con molto meno lavoro di manutenzione e lascia libera circa un'ulteriore gigabyte di RAM. Un solo nodo non offre alta disponibilità, quindi l'affidabilità non è mai un motivo per sceglierlo.
Perché il servizio k3s LoadBalancer rimane in stato Pending?
ServiceLB crea pod svclb- che riservano le porte del servizio sul nodo come hostPort, quindi possono essere pianificati soltanto dove tali porte sono libere. Se nginx o un altro proxy utilizza già la porta 80, il pod rimane in stato Pending e il servizio non riceve mai un indirizzo esterno. kubectl -n kube-system describe pod svclb-... restituisce 0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports. Libera la porta oppure reinstalla con --disable=servicelb e inoltra le richieste al NodePort che il servizio continua ad allocare.
Quanta RAM richiede k3s su un VPS?
Il minimo documentato per un nodo server è di 2 core e 2 GB, sufficienti per k3s e i componenti inclusi prima dell'esecuzione dei tuoi workload. Le misurazioni del progetto hanno rilevato 1,596 MB per un nodo server con uno stack di monitoraggio in esecuzione, quindi considera 2 GB il limite minimo e 4 GB la prima dimensione con cui un singolo nodo può operare senza problemi. Misura il tuo server con free -h prima dell'installazione e di nuovo dopo che ogni pod in kube-system risulta in stato Running.
Posso eseguire Docker e k3s sullo stesso VPS?
Sì, e rimangono separati. k3s usa il proprio containerd incorporato, quindi un'immagine creata con docker build non è visibile a k3s finché non esegui docker save myapp:0.1 | sudo k3s ctr images import -. Entrambi scrivono inoltre le proprie regole iptables e usano le proprie reti bridge. Controlla la quantità totale di RAM, perché Docker, k3s e i tuoi container non entrano in un server da 2 GB.
Come rimuovo completamente k3s?
Esegui sudo /usr/local/bin/k3s-uninstall.sh su un nodo server oppure sudo /usr/local/bin/k3s-agent-uninstall.sh su un agent. Il comando arresta il servizio, elimina il datastore, cancella i dati dei volumi persistenti in /var/lib/rancher/k3s/storage e rimuove gli strumenti inclusi. Prima copia fuori dal server i dati che vuoi conservare, perché l'operazione non può essere annullata. Un'interfaccia di rete cni0 o flannel.1 rimasta presente scompare al riavvio successivo.