Ridondanza non è alta affidabilità: serve anche rilevare il guasto e reagire senza un umano sveglio alle 3 di notte. VRRP, quorum, fencing, Pacemaker e il failover che hai davvero testato.
"Il cluster non ha fatto failover" — la frase che di solito segue "ma tanto era ridondante", detta davanti a un dashboard rosso alle 3 del mattino.
Non è "comprare due server invece di uno". È un sistema che si accorge da solo di essere rotto e reagisce prima che se ne accorga un utente.
Avere un secondo pilota a bordo (ridondanza) non serve a niente se, quando il primo sviene, nessuno se ne accorge o il copilota non sa prendere i comandi. L'alta affidabilità vera richiede tre cose insieme: un componente ridondante (il copilota c'è), un modo di rilevare il guasto (qualcuno controlla che il pilota respiri) e un meccanismo di failover automatico (il copilota prende i comandi senza dover chiedere permesso). Togli anche solo uno di questi tre pezzi, e la ridondanza è solo hardware che costa e non ti salva alle 3 di notte.
| Disponibilità | Downtime/anno | Downtime/mese | Realismo |
|---|---|---|---|
| 99% ("due nove") | ~3,65 giorni | ~7,3 ore | Un server singolo senza troppe pretese |
| 99,9% ("tre nove") | ~8,76 ore | ~43 minuti | Obiettivo ragionevole per la maggior parte dei servizi interni |
| 99,99% ("quattro nove") | ~52,6 minuti | ~4,3 minuti | Richiede HA vera: automazione, non processi manuali |
| 99,999% ("cinque nove") | ~5,26 minuti | ~26 secondi | Costoso, multi-sito, per pochi servizi che lo giustificano davvero |
Un Single Point of Failure è qualsiasi componente la cui rottura, da sola, porta giù l'intero servizio. Non è solo il server: può essere l'alimentatore, lo switch di rete, il singolo tecnico che sa riavviare quel servizio, o il certificato TLS che nessuno ha messo in scadenza automatica. L'HA seria comincia da un esercizio noioso: elencare ogni componente e chiedersi "se questo muore adesso, cosa succede?"
RTO (Recovery Time Objective) è quanto tempo puoi stare giù prima che sia un problema serio. RPO (Recovery Point Objective) è quanti dati puoi permetterti di perdere (l'ultimo minuto? l'ultima ora?). Sono loro, non "vogliamo essere sempre su", a dettare quale architettura di HA ha senso — e quanto costa.
I due pattern fondamentali su cui si costruisce (quasi) ogni sistema ridondante. Scegliere quello sbagliato costa caro in un senso o nell'altro.
Un nodo lavora, uno o più nodi aspettano pronti a subentrare. Semplice da ragionare (non c'è concorrenza tra nodi sullo stesso dato), ma spreca risorse (il passivo sta li' a fare poco) e il failover comporta quasi sempre un piccolo buco di servizio, per quanto breve.
Esempi tipici: coppia di firewall con VRRP (cap. 3), database primary/standby (rimando alla guida Database).
Tutti i nodi lavorano contemporaneamente, il carico è distribuito tra loro. Zero spreco di risorse e continuità anche sotto carico elevato, ma serve gestire correttamente lo stato condiviso (sessioni, cache, dati) e la concorrenza tra nodi — molto più complesso da progettare bene.
Esempi tipici: pool di web server dietro un load balancer, cluster Galera MySQL multi-master.
"N+1" significa: quanti nodi ti servono per reggere il carico (N), più uno di scorta per quando uno si rompe. "N+2" aggiunge un secondo margine, tipico se vuoi reggere un guasto mentre stai già facendo manutenzione pianificata su un altro nodo. Più margine costa di più ma assorbe guasti concorrenti, che nella vita reale capitano più spesso di quanto le statistiche indipendenti farebbero pensare (i guasti raramente sono davvero indipendenti: un aggiornamento buggato, un'ondata di caldo nel datacenter, colpiscono più nodi insieme).
L'attrezzo di HA più semplice e più usato in assoluto: un indirizzo IP che "salta" da un nodo all'altro senza che i client se ne accorgano.
Immagina un numero di telefono aziendale che risponde sempre, anche se il centralinista che lo risponde cambia. I client non chiamano "il server A" o "il server B": chiamano un indirizzo IP virtuale (VIP) che, in ogni momento, è assegnato a uno solo dei nodi del cluster (il "master"). Il protocollo VRRP (Virtual Router Redundancy Protocol) fa sì che i nodi si scambino "battiti cardiaci" e, se il master smette di rispondere, il backup con priorità più alta si prende il VIP in pochi secondi.
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 150 # piu' alto = piu' probabile diventare master
advert_int 1 # battito ogni 1 secondo
authentication {
auth_type PASS
auth_pass segreta
}
virtual_ipaddress {
192.168.1.100/24 # il VIP che i client useranno davvero
}
notify_master "/etc/keepalived/promoted.sh"
notify_backup "/etc/keepalived/demoted.sh"
}
Sul nodo di backup, stessa configurazione ma con state BACKUP e priority più bassa (es. 100). Quando il master sparisce (crash, rete giù, servizio non risponde), il backup nota l'assenza dei battiti VRRP entro pochi secondi e si assegna il VIP da solo.
ip addr show eth0 | grep 192.168.1.100
sudo systemctl status keepalived
journalctl -u keepalived -f
track_script che controlla davvero il servizio (es. nginx risponde? Postgres accetta connessioni?), keepalived tiene il VIP sul nodo master anche se il servizio sopra è morto ma la macchina è ancora accesa e risponde alla rete. Aggiungi sempre un controllo applicativo, non solo "il nodo è vivo".
Distribuire il traffico tra più nodi non è solo scalabilità: è anche il modo più comune di nascondere ai client che un nodo è appena morto.
backend web_pool
balance leastconn
option httpchk GET /health
http-check expect status 200
server web1 10.0.0.11:80 check inter 2000 rise 2 fall 3
server web2 10.0.0.12:80 check inter 2000 rise 2 fall 3
server web3 10.0.0.13:80 check inter 2000 rise 2 fall 3 backup
HAProxy interroga /health ogni inter millisecondi; un server viene tolto dal pool dopo fall controlli falliti consecutivi, e reinserito dopo rise controlli riusciti. Il flag backup tiene web3 fuori dal bilanciamento finché gli altri due non sono entrambi giù.
| Algoritmo | Quando usarlo |
|---|---|
| round robin | Richieste stateless, carico simile per richiesta |
| leastconn | Richieste di durata variabile (alcune lunghe, altre brevi) |
| ip hash / sticky session | Serve che lo stesso client finisca sempre sullo stesso nodo (sessioni in memoria non condivisa) |
Il problema più sottile e più pericoloso dell'alta affidabilità: cosa succede quando i nodi non riescono più a parlarsi tra loro, ma sono tutti vivi.
Immagina un cluster di 2 nodi dove ognuno crede di essere il primary perché ha perso i "battiti" dell'altro — ma l'altro è vivissimo, è solo la rete tra i due ad essersi rotta (un cavo tranciato, uno switch impazzito). Ora hai due capitani che danno ordini diversi alla stessa nave: due nodi che credono entrambi di poter scrivere sullo stesso storage condiviso, o servire lo stesso VIP. Questo si chiama split-brain, ed è uno degli scenari più distruttivi in assoluto: può corrompere dati silenziosamente, non solo causare downtime.
Il quorum risolve il problema chiedendo: "quanti nodi vedo io in questo momento, rispetto al totale del cluster?" Solo la partizione di rete che vede la maggioranza dei nodi (quorum) ha il diritto di continuare a operare; l'altra si blocca da sola, anche se i suoi nodi sono tecnicamente vivi.
| Nodi totali | Serve per il quorum | Nota |
|---|---|---|
| 2 | Impossibile decidere da soli | Serve un terzo "arbitro" (quorum device / witness) |
| 3 | 2 su 3 | Il minimo dispari ragionevole, tollera 1 guasto |
| 5 | 3 su 5 | Tollera 2 guasti simultanei |
qdevice in Corosync) su una terza macchina, o più semplicemente passare a 3 nodi.
quorum {
provider: corosync_votequorum
two_node: 1 # eccezione ESPLICITA per 2 nodi: entrambi votano "quorate" se si vedono
wait_for_all: 1 # al primo avvio, aspetta di vedere tutti i nodi prima di agire
}
two_node: 1 è un compromesso dichiarato: dice a Corosync "so che sono solo 2 nodi, lascia che ognuno agisca anche da solo se perde l'altro". Funziona solo in combinazione con il fencing (cap. 6): senza fencing, questa configurazione è esattamente la ricetta per uno split-brain.
Il nome più brutale dell'informatica è anche uno dei concetti più importanti: a volte, per essere sicuri, devi spegnere qualcosa con la forza.
Non è umorismo da sysadmin stanchi (anche se un po' sì): è il nome tecnico reale del meccanismo. Quando un cluster sospetta che un nodo sia "cattivo" (irraggiungibile, ma potenzialmente ancora vivo e scrivente), l'unico modo certo di evitare uno split-brain è spegnerlo fisicamente o isolarlo dalla rete/storage, non semplicemente "smettere di parlargli". Se non puoi essere sicuro che sia morto, uccidilo tu.
Il metodo più solido: il cluster comanda una presa di corrente intelligente (PDU) o l'interfaccia di gestione remota del server (IPMI, iLO, iDRAC) per spegnere fisicamente il nodo sospetto. Se non ha corrente, non può scrivere da nessuna parte: fine della storia.
Alternative quando il power fencing non è disponibile: SBD (Storage-Based Death) usa un disco condiviso come "lavagna" su cui i nodi si dicono a vicenda "sono vivo", combinato con un watchdog hardware che riavvia il nodo se smette di aggiornare la lavagna in tempo.
pcs stonith create fence_nodo1 fence_ipmilan \
pcmk_host_list="nodo1" ipaddr="10.0.0.101" \
login="admin" passwd="segreta" lanplus=1
pcs stonith status
pcs stonith fence nodo1 # testa il fencing manualmente, DA FARE prima di fidarsene
stonith-enabled "per velocizzare i test": è l'esatto scenario per cui il fencing esiste.
Lo stack di cluster resource manager standard su Linux: chi decide cosa gira dove, e con quali regole.
Corosync è il "sistema nervoso": si occupa di far sapere ai nodi chi c'è, chi manca, e di calcolare il quorum (cap. 5). Pacemaker è il "cervello decisionale": usa le informazioni di Corosync per decidere quali risorse (un VIP, un servizio, un filesystem) devono girare su quale nodo, e le sposta quando serve — chiamando il fencing (cap. 6) quando necessario.
sudo apt install pacemaker corosync pcs
sudo systemctl enable --now pcsd
# Autentica i nodi tra loro (una volta, da uno qualsiasi)
sudo pcs host auth nodo1 nodo2 nodo3 -u hacluster
# Crea il cluster: genera corosync.conf su tutti i nodi indicati
sudo pcs cluster setup mio_cluster nodo1 nodo2 nodo3
sudo pcs cluster start --all
sudo pcs cluster enable --all
-- Un IP virtuale gestito dal cluster stesso (alternativa a keepalived, dentro Pacemaker)
pcs resource create vip_cluster ocf:heartbeat:IPaddr2 \
ip=192.168.1.100 cidr_netmask=24 --group grp_servizio
pcs resource create nginx_svc systemd:nginx --group grp_servizio
-- Un gruppo tiene le risorse SEMPRE sullo stesso nodo, nello stesso ordine di avvio
pcs status
| Constraint | A cosa serve |
|---|---|
| colocation | Due risorse devono (o non devono) stare sullo stesso nodo |
| order | Una risorsa deve partire solo dopo che un'altra è già su |
| location | Preferenza/divieto per una risorsa di girare su un nodo specifico |
ocf:heartbeat:*) già pronti per i casi comuni — IP virtuali, filesystem, database, server web. Cerca prima se esiste già l'agent giusto prima di scriverne uno custom.
Il livello più delicato: qui non basta "far ripartire il servizio altrove", perché i dati devono essere lì, coerenti, quando ripartono.
Replicare un database (PostgreSQL streaming replication, MySQL/MariaDB con GTID, failover con Patroni/Orchestrator) è l'argomento centrale della guida PostgreSQL, MySQL & SQL, capitolo 12: setup passo passo, monitoraggio del lag, promozione di uno standby, e la manutenzione continua di una replica. Qui vale solo ribadire il principio che lega tutto insieme.
Quando la rete si spezza (partition), un sistema distribuito deve scegliere tra Consistency (tutti i nodi vedono sempre lo stesso dato) e Availability (il sistema risponde comunque, anche isolato). Non puoi avere entrambe al 100% durante una partizione di rete — questo è il teorema CAP. Un database relazionale con replica sincrona sceglie tipicamente coerenza (si blocca piuttosto che rispondere con dati potenzialmente vecchi); un sistema eventually-consistent sceglie disponibilità (risponde sempre, ma potresti leggere un dato non aggiornatissimo).
Quando VRRP non basta più perché i due nodi non sono nella stessa rete locale, ma in due datacenter diversi.
Alcuni provider DNS (Route 53, Cloudflare, PowerDNS con moduli dedicati) possono monitorare attivamente gli endpoint dietro un record e smettere di restituirlo se risulta non sano, restituendo solo gli IP dei siti/nodi ancora vivi. È il modo più semplice di fare failover tra datacenter geograficamente distanti, dove un VIP Layer 2 (VRRP) non può funzionare.
I resolver DNS (e i client) cachano le risposte per il tempo indicato dal TTL. Un TTL alto (es. 3600s) significa che, anche se il DNS smette di restituire il nodo morto, alcuni client continueranno a provare quell'IP per fino a un'ora. Per un failover DNS reattivo servono TTL bassi (30-60s) — a costo di più query DNS in generale.
Il Global Server Load Balancing estende l'idea: non solo "smetti di rispondere con il nodo morto", ma "rispondi con il datacenter più vicino/più sano all'utente che chiede". Serve quando l'HA multi-sito diventa anche una questione di latenza, non solo di sopravvivenza.
Ogni meccanismo di failover di questa guida dipende da una cosa sola: sapere, in modo affidabile, se qualcosa è vivo o morto.
Un probe attivo interroga periodicamente il servizio (HTTP GET su /health, query SQL di test) chiedendo esplicitamente "stai bene?". Un controllo passivo osserva il traffico reale e nota se le richieste vere iniziano a fallire, senza generare traffico extra. I sistemi seri usano entrambi: il passivo è più realistico (misura l'esperienza utente vera), l'attivo rileva problemi anche quando non c'è traffico.
Timeout troppo aggressivi (controllo ogni secondo, 1 fallimento = fuori) causano flapping: un nodo momentaneamente lento viene espulso e riammesso in continuazione, peggiorando la stabilità invece di migliorarla. Timeout troppo permissivi (controllo ogni minuto, serve 10 fallimenti) lasciano gli utenti a sbattere contro un nodo morto per minuti. Il valore giusto dipende dal servizio: un load balancer HTTP può permettersi controlli aggressivi (secondi), un cluster di storage con failover costoso no.
/health dedicato che verifica anche le dipendenze critiche (connessione al database, spazio disco, thread pool non esaurito) dà un segnale molto più onesto.
Come questi pezzi si compongono davvero, in stack che probabilmente hai già visto o dovrai costruire.
Un cluster Proxmox VE usa esattamente Corosync per membership e quorum tra i nodi fisici, con un HA Manager integrato che, se un nodo host muore, riavvia le VM/container che ospitava su un altro nodo del cluster — a patto che lo storage sia condiviso (Ceph, NFS, ZFS via iSCSI) e non locale al nodo caduto.
keepalived (VIP) davanti a una coppia di HAProxy → pool di web server stateless dietro → PostgreSQL con streaming replication gestita da Patroni per il failover automatico del database. Ogni livello ha il suo meccanismo di HA, e insieme coprono rete, applicazione e dati.
Un cluster Kubernetes incorpora molti di questi concetti nel suo modello: il control plane può girare su più nodi con etcd (che usa un protocollo di consenso simile nello spirito al quorum di Corosync), i Pod vengono riavviati automaticamente su nodi sani (liveness/readiness probe = health check), e i Service fanno da load balancer interno verso repliche sane. Non elimina la necessità di capire questi concetti: li automatizza, ma solo se li configuri correttamente (replica count, PodDisruptionBudget, anti-affinity tra repliche).
Un failover mai provato di proposito è una teoria, non un piano. E le teorie falliscono proprio quando iniziano a servire davvero.
Esattamente come un backup mai ripristinato (vedi la guida Database), un cluster HA mai fatto fallire di proposito è uno Schrödinger's cluster: potrebbe funzionare, potrebbe no, e lo scopri nel peggior momento possibile. Il fencing configurato male, un constraint sbagliato, uno script notify che non gira con i permessi giusti: sono tutti bug che restano invisibili finché non li forzi a manifestarsi in un ambiente controllato.
Ogni test di failover dovrebbe produrre numeri concreti: quanti secondi è durato il downtime reale (RTO effettivo vs obiettivo), quante richieste sono fallite durante la transizione, se i dati dopo il failover sono coerenti (RPO effettivo). Confronta questi numeri con gli obiettivi del cap. 1: se non li rispetti, sai esattamente cosa correggere prima del prossimo incidente vero.
Tutto quello che serve, su una pagina. Bookmark questa sezione e dimentica il resto.
sudo systemctl status keepalived
ip addr show | grep VIP_ADDR
journalctl -u keepalived -f
pcs status
pcs cluster start --all
pcs resource move risorsa nodo2
corosync-quorumtool -s
pcs stonith fence nodoX
sudo systemctl reload haproxy
# Pagina statistiche live, se abilitata in config:
# stats uri /haproxy?stats
corosync-quorumtool -s
corosync-cfgtool -s
stonith-enabled=false "per ora"two_node consapevoletrack_script: il VIP resta su un nodo con servizio morto