La guida a Kubernetes che non volevi leggere ma che ti salverà il culo il giorno che il cluster esplode alle 3 di notte.
Da zero a "so quello che sto facendo (più o meno)"
Alla fine K8s non è poi così terribile, basta che qualcuno te lo spieghi senza il tono da "senior architect che ha fatto 14 certificazioni".
Spoiler: non è magia, è solo un sacco di YAML
Immagina di gestire una pizzeria. Hai dei forni (server), delle pizze da fare (container), e dei pizzaioli (processi). Kubernetes è il direttore di sala che decide quale pizza va in quale forno, se un forno si rompe sposta le pizze in un altro, e se arrivano 200 clienti accende altri forni.
Un pacchetto che contiene la tua app + tutto quello che le serve. Come una scatola da trasloco con l'etichetta "FRAGILE" che nessuno rispetta.
Un sistema di orchestrazione di container. Il nome viene dal greco "timoniere" — e il numero 8 sta per le 8 lettere tra la K e la S. Sì, i dev sono pigri quanto te.
SSH sul server → docker run → preghiera → "funziona sulla mia macchina" → il server muore alle 3AM → sveglia → panico → SSH → docker run di nuovo
Definisci cosa vuoi → K8s lo fa → qualcosa muore → K8s lo ricrea da solo → tu dormi → il tuo capo pensa che sei un genio
Ovvero "chi fa cosa in questo casino"
Il "cervello" del cluster. Prende tutte le decisioni. Non ci gira la tua app (di solito).
Le "braccia" del cluster. Qui gira la tua app per davvero.
Le parole che dovrai fingere di capire alle riunioni
L'unità più piccola. Uno o più container che condividono rete e storage. Come un coinquilino che non paga l'affitto.
Mantiene N copie del tuo Pod. Ne muore uno? Ne crea un altro. Come un'idra.
Gestisce i ReplicaSet. Rolling update, rollback. Il tuo migliore amico.
Un IP stabile per raggiungere i Pod. Perché i Pod muoiono e rinascono con IP diversi.
Cartelle virtuali per organizzare le risorse. Tipo "dev", "staging", "prod-che-non-toccare".
Configurazioni e credenziali. I Secret sono base64, NON criptati. Sì, lo so.
Storage persistente. PV = il disco. PVC = "mi serve un disco così". Come Tinder ma per lo storage.
Espone i Service all'esterno con regole HTTP. Il buttafuori del cluster.
Un Pod su OGNI nodo. Perfetto per monitoring e log. Come la muffa: ovunque.
Il primo passo per il tuo nuovo hobby non richiesto
Un cluster K8s single-node sulla tua macchina. Perfetto per imparare senza distruggere niente (di importante).
# macOS (con Homebrew)
brew install minikube
# Linux
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# Avvia il cluster (ci mette un po', vai a fare caffè)
minikube start
# Verifica che funzioni
kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane 42s v1.31.0
Cluster K8s dentro container Docker. Più leggero di minikube, ottimo per test e CI/CD.
brew install kind
kind create cluster --name svogliati-cluster
kubectl cluster-info
Per chi ha il budget (o il free tier). Qualcun altro gestisce il control plane, tu gestisci solo i worker.
# Google GKE
gcloud container clusters create svogliati --num-nodes=3
# AWS EKS
eksctl create cluster --name svogliati --nodes 3
# Azure AKS
az aks create -g myResourceGroup -n svogliati --node-count 3
RKE2 è il K8s di Rancher/SUSE. Un comando, un cluster production-ready. Niente Docker da installare prima, niente dipendenze strane, niente 47 pagine di documentazione. Uno script, un servizio, via. È quello che usi quando hai server bare-metal o VM e vuoi un cluster serio senza vendere l'anima al cloud.
# ===== NODO SERVER (MASTER) =====
# 1. Installa RKE2 server
curl -sfL https://get.rke2.io | sudo sh -
# 2. Abilita e avvia
sudo systemctl enable rke2-server
sudo systemctl start rke2-server
# 3. Aspetta un minuto (vai a fare caffè, di nuovo)
# Controlla che sia partito:
sudo journalctl -u rke2-server -f
# 4. Configura kubectl
mkdir -p ~/.kube
sudo cp /etc/rancher/rke2/rke2.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
export PATH=$PATH:/var/lib/rancher/rke2/bin
echo 'export PATH=$PATH:/var/lib/rancher/rke2/bin' >> ~/.bashrc
# 5. Verifica
kubectl get nodes
NAME STATUS ROLES AGE VERSION
server-01 Ready control-plane,etcd,master 60s v1.30.x+rke2r1
# Prendi il token per aggiungere i worker
sudo cat /var/lib/rancher/rke2/server/node-token
K10abc123...::server:xyz789...
# ===== NODO WORKER (ripeti per ogni worker) =====
# 1. Installa RKE2 in modalità agent
curl -sfL https://get.rke2.io | INSTALL_RKE2_TYPE="agent" sudo sh -
# 2. Configura: dove è il server e qual è il token
sudo mkdir -p /etc/rancher/rke2
sudo tee /etc/rancher/rke2/config.yaml <<EOF
server: https://IP-DEL-SERVER:9345
token: IL-TOKEN-CHE-HAI-COPIATO-PRIMA
EOF
# 3. Avvia
sudo systemctl enable rke2-agent
sudo systemctl start rke2-agent
# 4. Torna sul server e controlla
kubectl get nodes
NAME STATUS ROLES AGE VERSION
server-01 Ready control-plane,etcd,master 5m v1.30.x+rke2r1
worker-01 Ready <none> 30s v1.30.x+rke2r1
worker-02 Ready <none> 15s v1.30.x+rke2r1
# 🎉 Cluster production-ready in 5 minuti. Andiamo a dormire.
# /etc/rancher/rke2/config.yaml sul SERVER
# (crea PRIMA di avviare rke2-server)
# HA: più server per alta disponibilità
tls-san:
- "lb.esempio.com" # DNS del load balancer
- "10.0.0.100" # VIP
# CNI: di default usa Canal, ma puoi cambiare
cni: cilium # canal | cilium | calico | none
# Disabilita componenti che non ti servono
disable:
- rke2-ingress-nginx # se usi un altro ingress controller
# CIDR custom
cluster-cidr: "10.42.0.0/16"
service-cidr: "10.43.0.0/16"
L'unico comando che dovrai imparare. Tutto il resto è contorno.
# macOS
brew install kubectl
# Linux
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install kubectl /usr/local/bin/kubectl
# Verifica
kubectl version --client
# Pro tip: alias per svogliati
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
k invece di kubectl. Hai appena risparmiato 6 lettere per comando. Con 100 comandi al giorno, sono 600 lettere. Ringraziami.
Da "cosa sto facendo" a "forse so cosa sto facendo"
Un Pod con nginx. Niente di più facile.
# Crea un pod al volo (modo imperativo)
kubectl run mio-primo-pod --image=nginx
# Vedi se è vivo
kubectl get pods
NAME READY STATUS RESTARTS AGE
mio-primo-pod 1/1 Running 0 10s
# Bravo! Hai appena deployato qualcosa!
Guarda dentro. Come un dottore, ma per container.
# Dettagli completi
kubectl describe pod mio-primo-pod
# Solo lo YAML (il DNA del Pod)
kubectl get pod mio-primo-pod -o yaml
# Log del container
kubectl logs mio-primo-pod
# Entra DENTRO il container (come SSH ma più figo)
kubectl exec -it mio-primo-pod -- /bin/bash
Ora facciamo le cose per bene. Un Deployment gestisce più repliche e gli aggiornamenti.
# Crea un deployment con 3 repliche
kubectl create deployment mia-app --image=nginx --replicas=3
# Guarda i pod che spuntano
kubectl get pods -w # -w = watch, si aggiorna live
NAME READY STATUS RESTARTS AGE
mia-app-6d8f5c7b9d-abc12 1/1 Running 0 5s
mia-app-6d8f5c7b9d-def34 1/1 Running 0 5s
mia-app-6d8f5c7b9d-ghi56 1/1 Running 0 5s
# Prova a uccidere un pod. K8s ne crea subito un altro
kubectl delete pod mia-app-6d8f5c7b9d-abc12
# ...aspetta 2 secondi...
kubectl get pods
# Tadaaa! 3 pod di nuovo. K8s non molla mai.
Il tuo deployment gira ma nessuno lo vede. Serve un Service.
# Esponi il deployment
kubectl expose deployment mia-app --port=80 --type=NodePort
# Vedi il servizio creato
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mia-app NodePort 10.96.45.123 <none> 80:31234/TCP 5s
# Con minikube, apri nel browser
minikube service mia-app
# Oppure port-forward per test veloce
kubectl port-forward svc/mia-app 8080:80
# Ora apri http://localhost:8080 🎉
Il bello di K8s: scalare è un comando, aggiornare pure.
# Scala a 5 repliche
kubectl scale deployment mia-app --replicas=5
# Aggiorna l'immagine (rolling update!)
kubectl set image deployment/mia-app nginx=nginx:1.25
# Guarda il rollout in tempo reale
kubectl rollout status deployment/mia-app
# Qualcosa è andato storto? ROLLBACK!
kubectl rollout undo deployment/mia-app
# Come se niente fosse successo 😎
Dove l'indentazione sbagliata ti rovina la giornata
In K8s tutto è YAML. Il deployment? YAML. Il service? YAML. Il tuo dolore? Probabilmente causato da YAML con un'indentazione sbagliata di uno spazio. MAI usare TAB nello YAML. Solo spazi. Due spazi. Sempre.
apiVersion: apps/v1 # Versione dell'API. Ogni risorsa ha la sua
kind: Deployment # Tipo di risorsa
metadata: # Info sulla risorsa
name: mia-app # Nome del deployment
labels: # Etichette (tipo hashtag)
app: mia-app
spec: # Le specifiche (cosa vuoi)
replicas: 3 # Quante copie
selector: # Come trova i Pod da gestire
matchLabels:
app: mia-app # Deve matchare con template.labels
template: # Template del Pod
metadata:
labels:
app: mia-app # Label del Pod (deve matchare selector!)
spec:
containers: # Lista dei container nel Pod
- name: nginx
image: nginx:1.25 # Immagine Docker
ports:
- containerPort: 80 # Porta esposta
resources: # SEMPRE settare le risorse!
requests: # Minimo garantito
cpu: "100m" # 100 milliCPU = 0.1 CPU
memory: "128Mi" # 128 MB RAM
limits: # Massimo consentito
cpu: "500m" # Se sfora, viene throttlato
memory: "256Mi" # Se sfora, viene UCCISO (OOMKilled)
apiVersion: v1
kind: Service
metadata:
name: mia-app-service
spec:
type: ClusterIP # ClusterIP = interno | NodePort = porta su nodo | LoadBalancer = IP esterno
selector: # Quali Pod "cattura"
app: mia-app # Pod con label app=mia-app
ports:
- port: 80 # Porta del Service
targetPort: 80 # Porta del container
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mia-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: mia-app.esempio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mia-app-service
port:
number: 80
# Applica un singolo file
kubectl apply -f deployment.yaml
# Applica tutto in una cartella
kubectl apply -f ./manifests/
# Dry-run: vedi cosa succederebbe SENZA farlo davvero
kubectl apply -f deployment.yaml --dry-run=client
# Genera YAML da un comando (per svogliati che non vogliono scrivere YAML)
kubectl create deployment test --image=nginx --dry-run=client -o yaml > deployment.yaml
# 👆 Questo trucco vale ORO. Genera lo YAML base e tu lo modifichi.
kubectl create ... --dry-run=client -o yaml genera lo YAML per qualsiasi risorsa. Non serve ricordare la struttura a memoria. Copia, incolla, modifica. Fatto.
Come i Pod si parlano (e come parli tu con loro)
Ogni Service ha un nome DNS automatico. I Pod si parlano così:
# Formato DNS di un Service:
<nome-service>.<namespace>.svc.cluster.local
# Esempi:
curl http://mia-app-service.default.svc.cluster.local
curl http://mia-app-service # abbreviato, se stesso namespace
# Nel tuo codice, connettiti al DB così:
postgres://db-service:5432/mydb
Firewall per Pod. Di default tutti parlano con tutti. Cattiva idea in prod.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-only-frontend
spec:
podSelector:
matchLabels:
app: backend
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
Perché i container sono effimeri come le tue motivazioni
Quando un Pod muore, tutti i dati dentro muoiono con lui. Se il tuo database gira in un Pod senza storage persistente, ogni restart è un factory reset. Non ideale.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mio-storage
spec:
accessModes:
- ReadWriteOnce # RWO = un nodo alla volta
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-con-storage
spec:
replicas: 1
selector:
matchLabels:
app: app-con-storage
template:
metadata:
labels:
app: app-con-storage
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: dati
mountPath: /data # Dove appare nel container
volumes:
- name: dati
persistentVolumeClaim:
claimName: mio-storage
Quando le cose vanno male (e andranno male)
Il container crasha e K8s continua a riavviarlo con intervalli crescenti (backoff).
# 1. Guarda i log del crash precedente
kubectl logs <pod-name> --previous
# 2. Guarda gli eventi
kubectl describe pod <pod-name> | tail -20
# 3. Se OOMKilled, aumenta memory limits
# 4. Se errore app, fixa il codice/config
# 5. Se manca un env/secret:
kubectl get configmap,secret -n <namespace>
Lo Scheduler non riesce a piazzare il Pod su nessun nodo.
# 1. Guarda gli eventi del Pod
kubectl describe pod <pod-name>
# Cerca "FailedScheduling" negli Events
# 2. Controlla le risorse disponibili nei nodi
kubectl describe nodes | grep -A5 "Allocated resources"
# 3. Il PVC è bindato?
kubectl get pvc
# Se dice "Pending", il problema è lo storage, non il Pod
# 4. Controlla taints e tolerations
kubectl describe nodes | grep Taint
Hai creato il Service ma non risponde. Le label sono il problema nel 99% dei casi.
# 1. Il Service ha degli endpoints?
kubectl get endpoints <service-name>
# Se la lista è vuota, le label non matchano!
# 2. Confronta le label
kubectl get svc <service-name> -o yaml | grep -A3 selector
kubectl get pods --show-labels
# 3. Testa la connessione dall'interno
kubectl run test-debug --image=busybox --rm -it -- wget -qO- http://<service-name>
# 4. La porta è giusta?
kubectl get svc <service-name> -o yaml | grep -A5 ports
Un nodo è andato in vacanza. I Pod sopra verranno ri-schedulati su altri nodi (dopo ~5 min).
# 1. Stato dei nodi
kubectl get nodes
kubectl describe node <node-name>
# 2. Sul nodo (se accessibile via SSH)
systemctl status kubelet
journalctl -u kubelet -f
# 3. Riavvia kubelet
systemctl restart kubelet
# 4. Drain del nodo prima di manutenzione
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data
# Questo sposta tutti i Pod su altri nodi
Se l'API Server non risponde, non è la fine del mondo. I Pod già in esecuzione continuano a girare. Non puoi solo fare modifiche.
# 1. Controlla i componenti del control plane
kubectl get componentstatuses # potrebbe non funzionare se API è down
# 2. Se hai accesso SSH al master
crictl ps | grep -E "etcd|apiserver|scheduler|controller"
# 3. Controlla i log del control plane
journalctl -u kubelet -f # su nodo master
# 4. Backup etcd (FAI QUESTO REGOLARMENTE!)
ETCDCTL_API=3 etcdctl snapshot save backup.db
# 5. Se managed (GKE/EKS/AKS): apri un ticket. È il loro problema.
Stampalo, appendilo, tatuatelo
| Cosa vuoi fare | Comando |
|---|---|
| Vedi tutti i pod | kubectl get pods -A |
| Vedi pod con più dettagli | kubectl get pods -o wide |
| Dettaglio di un pod | kubectl describe pod <nome> |
| Log di un pod | kubectl logs <pod> -f |
| Log del container precedente (crash) | kubectl logs <pod> --previous |
| Entra in un container | kubectl exec -it <pod> -- /bin/sh |
| Vedi eventi recenti | kubectl get events --sort-by='.lastTimestamp' |
| Risorse dei nodi | kubectl top nodes |
| Risorse dei pod | kubectl top pods |
| Vedi tutto in un namespace | kubectl get all -n <ns> |
| Cosa vuoi fare | Comando |
|---|---|
| Applica un manifest | kubectl apply -f file.yaml |
| Crea un deployment | kubectl create deploy <nome> --image=<img> |
| Scala repliche | kubectl scale deploy <nome> --replicas=N |
| Aggiorna immagine | kubectl set image deploy/<nome> <c>=<img:tag> |
| Rollback | kubectl rollout undo deploy/<nome> |
| Edita risorsa live | kubectl edit deploy/<nome> |
| Cancella risorsa | kubectl delete -f file.yaml |
| Port-forward locale | kubectl port-forward svc/<nome> 8080:80 |
| Genera YAML senza creare | kubectl create deploy x --image=y --dry-run=client -o yaml |
| Cosa vuoi fare | Comando |
|---|---|
| Crea ConfigMap da file | kubectl create cm <nome> --from-file=config.txt |
| Crea ConfigMap da literal | kubectl create cm <nome> --from-literal=KEY=val |
| Crea Secret | kubectl create secret generic <nome> --from-literal=pw=xxx |
| Decodifica un Secret | kubectl get secret <nome> -o jsonpath='{.data.pw}' | base64 -d |
| Vedi tutti i context | kubectl config get-contexts |
| Cambia context | kubectl config use-context <nome> |
| Cambia namespace default | kubectl config set-context --current --namespace=<ns> |
# Alias base
alias k=kubectl
alias kg='kubectl get'
alias kgp='kubectl get pods'
alias kgpa='kubectl get pods -A'
alias kd='kubectl describe'
alias kl='kubectl logs -f'
alias kx='kubectl exec -it'
alias kaf='kubectl apply -f'
alias kdf='kubectl delete -f'
alias kns='kubectl config set-context --current --namespace'
# Il più utile di tutti: mostra pod con nodo e IP
alias kwide='kubectl get pods -o wide'
# Watch continuo dei pod
alias kwatch='kubectl get pods -w'
Perché il terminale è bello, ma cliccare è meglio (non ditelo a nessuno)
I puristi ti diranno che "il vero sysadmin usa solo la CLI". Sì vabbè. Intanto loro scrivono 47 comandi kubectl per capire cosa sta succedendo, e tu con due click hai la dashboard con grafici, log in tempo reale e il bottone "riavvia". Chi è lo svogliato intelligente adesso?
La Rolls Royce delle dashboard K8s. Gestisci più cluster da un'unica interfaccia web. Crei cluster, aggiungi nodi, fai deploy, gestisci RBAC, monitori tutto. E puoi anche importare cluster già esistenti (EKS, GKE, AKS, RKE2, qualsiasi cosa).
Punti di forza:
Ideale per:
# 1. Aggiungi il repo Helm
helm repo add rancher-stable https://releases.rancher.com/server-charts/stable
helm repo update
# 2. Crea il namespace
kubectl create namespace cattle-system
# 3. Installa cert-manager (serve per i certificati TLS)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
# Aspetta che sia pronto
kubectl wait --for=condition=Available deployment --all -n cert-manager --timeout=120s
# 4. Installa Rancher
helm install rancher rancher-stable/rancher \
--namespace cattle-system \
--set hostname=rancher.tuodominio.com \
--set bootstrapPassword=admin
# 5. Aspetta che sia pronto
kubectl rollout status deployment rancher -n cattle-system
# 6. Apri https://rancher.tuodominio.com e goditi la GUI 🎉
# Alternativa rapida per test locale:
docker run -d --restart=unless-stopped \
-p 80:80 -p 443:443 \
--privileged \
rancher/rancher:latest
L'IDE per Kubernetes. Un'app desktop che si connette ai tuoi cluster e ti mostra tutto con una GUI pazzesca. Log in tempo reale, shell nei pod, metriche, tutto. È tipo Visual Studio Code ma per K8s.
Punti di forza:
Ideale per:
# macOS
brew install --cask openlens
# Linux (Snap)
sudo snap install kontena-lens --classic
# Oppure scarica da: https://github.com/MuhammedKalworsky/OpenLens/releases
# Apri e lui legge automaticamente il tuo ~/.kube/config
# Tutti i tuoi cluster appaiono. Click, esplora, fatto.
Per chi vuole restare nel terminale ma non vuole scrivere kubectl get pods 400 volte al giorno. k9s è una TUI (Terminal UI) che ti fa navigare il cluster come un file manager. Frecce, invio, e vedi tutto. Il compromesso perfetto tra CLI e GUI.
# Installa
brew install k9s # macOS
sudo snap install k9s # Linux
# Lancia
k9s
# Comandi dentro k9s:
# :pods → vedi i pods
# :deploy → vedi i deployments
# :svc → vedi i services
# :ns → cambia namespace
# /qualcosa → filtra/cerca
# l → log del pod selezionato
# s → shell nel pod
# d → describe
# ctrl-d → delete
# :q → esci (come vim, ma almeno qui funziona)
La dashboard ufficiale del progetto Kubernetes. Basica ma funzionale. Se non vuoi installare niente di terze parti, questa c'è sempre.
# Installa la dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
# Crea un utente admin per accedere
kubectl create serviceaccount dashboard-admin -n kubernetes-dashboard
kubectl create clusterrolebinding dashboard-admin \
--clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin
# Genera il token
kubectl create token dashboard-admin -n kubernetes-dashboard
# Avvia il proxy
kubectl proxy
# Apri: http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
# Incolla il token. Finito.
Nato per Docker, cresciuto per K8s. Interfaccia web pulitissima, perfetta per chi viene dal mondo Docker e vuole gestire K8s senza impazzire. Community Edition gratuita.
kubectl apply -n portainer -f https://downloads.portainer.io/ce2-19/portainer.yaml
# Accedi su https://NODO-IP:30779
# Crea utente admin al primo accesso
Dashboard moderna e leggera di Kinvolk/Microsoft. Desktop app o in-cluster. Interfaccia pulita, plugin system, e non chiede la carta di credito. L'alternativa a Lens per chi vuole restare open source al 100%.
# Desktop app
brew install --cask headlamp
# Oppure in-cluster con Helm
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
helm install headlamp headlamp/headlamp -n headlamp --create-namespace
| Tool | Tipo | Multi-cluster | Costo | Per chi |
|---|---|---|---|---|
| 🐄 Rancher | Web UI (server) | ✅ Top | Gratis | Team, enterprise, multi-cluster |
| 🔎 OpenLens | Desktop app | ✅ Sì | Gratis | Developer, debug quotidiano |
| 🐶 k9s | Terminale (TUI) | ✅ Sì | Gratis | Sysadmin, chi vive nel terminale |
| 📊 K8s Dashboard | Web UI (in-cluster) | ❌ No | Gratis | Chi vuole l'ufficiale, basico |
| 💫 Portainer | Web UI (in-cluster) | ✅ Sì | CE gratis | Chi viene da Docker |
| 💡 Headlamp | Desktop / Web | ✅ Sì | Gratis | Chi vuole open source leggero |
Le regole d'oro per sopravvivere in produzione
Un Pod senza resources.requests e resources.limits è un Pod che può mangiare tutta la RAM del nodo. E lo farà. Alle 3 di notte.
Senza probe, K8s non sa se la tua app è viva o morta. Con le probe, rileva i problemi e riavvia automaticamente.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
image: nginx:latest è la roulette russa del deploy. Non sai cosa stai deployando. Usa sempre tag specifici: nginx:1.25.3
Non buttare tutto in default. Crea namespace per ambiente (dev, staging, prod) o per team. È gratis e ti salva la vita.
etcd è il cervello del cluster. Se lo perdi, perdi tutto. Backup automatico, minimo giornaliero. Se usi managed K8s, il provider lo fa (forse).
Installa Prometheus + Grafana. Se non monitori, non sai cosa succede. Se non sai cosa succede, non puoi fixare. Se non puoi fixare... buona fortuna.
helm repo add prometheus-community \
https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
-n monitoring --create-namespace
Se devi fare qualcosa più di due volte, automatizzalo.
Se devi spiegare qualcosa più di due volte, documentalo.
Se qualcosa ti sveglia alle 3 di notte, metti una probe.
Perché scrivere 14 file YAML a mano è da masochisti
Helm sta a Kubernetes come apt/brew sta a Linux/Mac. Vuoi installare Prometheus con 47 file YAML, 12 ConfigMap, 8 Service e 3 sacrifici al dio del YAML? Oppure vuoi scrivere helm install prometheus e andare a fare merenda? Ecco.
Un Chart è un pacchetto Helm. Contiene tutti i template YAML, i valori di default, e le istruzioni per installare un'applicazione. È tipo un .deb o un .pkg ma per K8s.
Senza Helm devi copincollare YAML tra ambienti, cambiare a mano le variabili, e pregare di non aver dimenticato niente. Con Helm:
# Installa Helm
brew install helm # macOS
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # Linux
# Aggiungi un repository (tipo aggiungere un PPA)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# Cerca un chart
helm search repo nginx
NAME CHART VERSION APP VERSION DESCRIPTION
bitnami/nginx 15.3.4 1.25.3 NGINX is a web server...
# Installa (una riga, un intero stack)
helm install mio-nginx bitnami/nginx
# Installa con valori custom
helm install mio-nginx bitnami/nginx -f miei-valori.yaml
# Oppure override inline
helm install mio-nginx bitnami/nginx --set replicaCount=3,service.type=LoadBalancer
# Vedi cosa hai installato
helm list
NAME NAMESPACE REVISION STATUS CHART APP VERSION
mio-nginx default 1 deployed nginx-15.3.4 1.25.3
# Aggiorna una release
helm upgrade mio-nginx bitnami/nginx --set replicaCount=5
# Qualcosa è andato storto? Rollback!
helm rollback mio-nginx 1 # torna alla revisione 1
# Disinstalla tutto pulito
helm uninstall mio-nginx
# Vedi i valori di default di un chart (per sapere cosa puoi cambiare)
helm show values bitnami/nginx | less
# Dry-run: vedi lo YAML che verrebbe generato SENZA installare
helm template mio-nginx bitnami/nginx -f miei-valori.yaml
# Sovrascrivi solo quello che ti serve, il resto rimane default
replicaCount: 3
image:
tag: "1.25.3" # Mai :latest, ricordi?
service:
type: LoadBalancer
port: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "250m"
memory: "256Mi"
ingress:
enabled: true
hostname: mia-app.esempio.com
Perché dare admin a tutti è come dare le chiavi della Ferrari al tirocinante
RBAC è il sistema di permessi di K8s. Pensa a un palazzo con tanti uffici. Il CEO (cluster-admin) ha le chiavi di tutto. Il developer ha le chiavi solo del suo piano (namespace). Lo stagista può guardare dalla finestra ma non toccare niente (get/list ma non delete). Se non configuri RBAC, tutti sono CEO. E questo finisce male.
# Step 1: Crea un Role (cosa può fare)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: role-sola-lettura
rules:
- apiGroups: [""] # "" = core API (pods, services, etc)
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch"] # Solo leggere, niente delete/create
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
---
# Step 2: Lega il Role all'utente (chi)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: dev
name: binding-stagista
subjects:
- kind: User
name: "mario.rossi@azienda.com" # Lo stagista
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: role-sola-lettura # Il ruolo creato sopra
apiGroup: rbac.authorization.k8s.io
Le app dentro K8s usano ServiceAccount per autenticarsi. Non far girare tutto con il SA di default che ha troppi permessi.
# Crea un ServiceAccount
kubectl create serviceaccount mia-app-sa -n dev
# Crea un Role che può leggere i secret
kubectl create role secret-reader -n dev \
--verb=get,list --resource=secrets
# Lega il SA al Role
kubectl create rolebinding mia-app-binding -n dev \
--role=secret-reader --serviceaccount=dev:mia-app-sa
# Verifica: cosa PUÒ fare questo SA?
kubectl auth can-i get secrets --as=system:serviceaccount:dev:mia-app-sa -n dev
yes
# Cosa NON può fare?
kubectl auth can-i delete pods --as=system:serviceaccount:dev:mia-app-sa -n dev
no
# Cosa posso fare io?
kubectl auth can-i --list
# Cosa può fare un utente specifico?
kubectl auth can-i --list --as=mario.rossi@azienda.com
# Test specifico
kubectl auth can-i create deployments -n production
# Vedi tutti i RoleBinding in un namespace
kubectl get rolebindings -n dev -o wide
# Vedi i ClusterRoleBinding (attenzione, qui si gioca pesante)
kubectl get clusterrolebindings -o wide | grep mario
cluster-admin dato a chi non serve è una bomba a orologeria. Lo stagista che cancella la produzione non è una barzelletta, è un martedi.
La checklist da panico per quando PagerDuty ti sveglia e il tuo cervello è al 3%
Non fixare. Prima capisci.
# I nodi sono vivi?
kubectl get nodes
# Quali pod sono in difficoltà?
kubectl get pods -A | grep -v Running | grep -v Completed
# Cosa è successo di recente?
kubectl get events -A --sort-by='.lastTimestamp' | tail -30
# Qualcuno ha fatto un deploy?
kubectl rollout history deployment -n <namespace>
# Log del pod che crasha
kubectl logs <pod> --previous -n <ns>
# OOMKilled? Controlla qui
kubectl describe pod <pod> -n <ns> | grep -A3 "Last State"
# Se OOMKilled: qualcuno ha bisogno di più RAM
# Fix temporaneo (compra tempo per dormire):
kubectl set resources deployment/<nome> -n <ns> --limits=memory=512Mi
# Se è un deploy recente che ha rotto tutto: ROLLBACK
kubectl rollout undo deployment/<nome> -n <ns>
# Ora torna a dormire. Domani si indaga.
# Quale nodo è down?
kubectl get nodes
kubectl describe node <nodo> | grep -A10 Conditions
# I pod si stanno spostando su altri nodi? (dopo ~5 min)
kubectl get pods -A -o wide | grep <nodo-down>
# Se cloud: il nodo è ancora vivo nell'infrastruttura?
# GKE: gcloud compute instances list
# AWS: aws ec2 describe-instances
# Forza lo spostamento dei pod
kubectl drain <nodo> --ignore-daemonsets --delete-emptydir-data --force
# Se managed K8s: scala il node pool per aggiungere capacità
# I pod della app girano?
kubectl get pods -l app=<nome> -n <ns>
# Il service ha endpoints?
kubectl get endpoints <service> -n <ns>
# Vuoto = le label non matchano. Il problema è lì.
# L'ingress punta al service giusto?
kubectl describe ingress <nome> -n <ns>
# Test dall'interno del cluster
kubectl run debug --rm -it --image=busybox -- wget -qO- http://<service>.<ns>
# Il certificato TLS è scaduto?
kubectl get secret <tls-secret> -n <ns> -o jsonpath='{.data.tls\.crt}' | \
base64 -d | openssl x509 -noout -dates
# PVC bloccato in Pending?
kubectl get pvc -A | grep Pending
# Disco pieno nel nodo?
kubectl describe node <nodo> | grep -A5 "Conditions"
# Cerca DiskPressure = True
# Spazio usato nel container
kubectl exec <pod> -- df -h
# Fix d'emergenza: elimina pod vecchi/completati che occupano spazio
kubectl delete pods --field-selector=status.phase==Succeeded -A
kubectl delete pods --field-selector=status.phase==Failed -A
Alle 3 di notte non si fanno fix permanenti. Si fa triage, si mette una pezza (rollback, scale up, restart), si torna a dormire, e domani mattina con il cervello acceso si fa il fix vero. Il fix fatto alle 3AM col cervello al 3% diventa il bug di domani alle 10AM. Garantito.
Checklist pezza notturna:
☑ C'è stato un deploy recente? → rollback
☑ OOMKilled? → aumenta memory limits
☑ Pod crashano? → scala le repliche funzionanti
☑ Nodo down? → drain + scala il node pool
☑ Non capisci cosa è successo? → rollback all'ultimo stato stabile
☑ Torna a dormire → domani si fa post-mortem