High Availability e Load Balancing: come progettare un cluster moderno

HA e load balancing non sono la stessa cosa: SPOF, VIP, HAProxy, Keepalived/VRRP, health check, stato applicativo, database, storage, quorum, fencing e split-brain.

High Availability e Load Balancing vengono spesso citati insieme, ma risolvono due problemi differenti. HA cerca di mantenere disponibile un servizio quando un componente si guasta. Load Balancing distribuisce il lavoro tra più istanze. Possiamo avere uno senza l’altro, e confonderli porta facilmente a costruire cluster ridondanti soltanto sulla carta.

La serie originale di SicurezzaReti del 2012 costruiva un cluster con un balancer e due relay, DRBD dual-primary e OCFS2. I concetti di base erano interessanti, ma oggi quella ricetta non va copiata alla lettera. Le parti successive rimangono online come archivio storico; questa introduzione è invece stata riscritta per descrivere un’architettura moderna.

Il primo errore: rendere ridondanti i backend ma non il load balancer

Se tutti i client entrano attraverso un singolo load balancer, quel nodo diventa uno Single Point of Failure. Due web server dietro un solo HAProxy non costituiscono un servizio altamente disponibile.

Una topologia minima può essere:

                 VIP 10.0.0.10
                      |
             +--------+--------+
             |                 |
          lb01               lb02
      HAProxy+VRRP       HAProxy+VRRP
             |                 |
             +--------+--------+
                      |
              +-------+-------+
              |               |
            web01           web02
          10.0.0.21        10.0.0.22

Un VIP, Virtual IP, viene annunciato dal load balancer attivo. Se quel nodo fallisce, il peer acquisisce l’indirizzo. HAProxy distribuisce invece le richieste ai backend che superano gli health check.

Health check: “porta aperta” non significa applicazione sana

Un controllo TCP dice che il processo accetta connessioni. Un’applicazione può però rispondere 500 a ogni richiesta pur mantenendo la porta 443 perfettamente aperta. Per HTTP è quindi preferibile un endpoint di salute che verifichi le dipendenze necessarie al servizio.

# /etc/haproxy/haproxy.cfg

frontend web
    bind *:80
    default_backend web_nodes

backend web_nodes
    balance roundrobin

    option httpchk GET /health
    http-check expect status 200

    server web01 10.0.0.21:80 check inter 2s fall 3 rise 2
    server web02 10.0.0.22:80 check inter 2s fall 3 rise 2
haproxy -c -f /etc/haproxy/haproxy.cfg
systemctl reload haproxy

HAProxy rimuove dalla rotazione un backend che fallisce i check e continua a verificarlo per rimetterlo online quando torna sano.

Rendere ridondante il VIP con VRRP

Keepalived implementa VRRP e può gestire il passaggio del VIP tra due load balancer. Esempio concettuale sul nodo primario:

# /etc/keepalived/keepalived.conf

vrrp_instance VI_WEB {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1

    virtual_ipaddress {
        10.0.0.10/24
    }
}

Sul secondo nodo usiamo lo stesso virtual_router_id e una priority inferiore. In ambienti reali aggiungiamo anche un track_script che abbassi la priorità se HAProxy non è operativo: avere il VIP su un nodo con il load balancer morto non ci aiuta.

Load balancing funziona bene quando i backend sono stateless

Due web server sono facili da bilanciare se una richiesta può finire indifferentemente su entrambi. Diventa più complicato quando una sessione utente, un file appena caricato o uno stato applicativo esistono soltanto sul nodo che ha gestito la richiesta precedente.

Per questo le architetture moderne cercano di spostare lo stato fuori dai frontend:

  • sessioni in Redis o database condiviso;
  • upload in object storage o storage progettato per accesso concorrente;
  • configurazione distribuita tramite deploy/version control, non filesystem condiviso tra i nodi;
  • database con una propria strategia di replica e failover.

Sticky session: utile, ma non risolve il problema

Il load balancer può mantenere un utente sullo stesso backend. È utile per applicazioni legacy, ma se quel backend cade la sessione locale cade con lui. La persistenza del load balancer è quindi un compromesso, non vera condivisione dello stato.

Database: non montare semplicemente la stessa datadir su due server

Un database non è una collezione di file statici. Lock, cache, WAL/redo log e semantica delle transazioni appartengono al motore. Condividere la datadir tra due istanze MySQL/MariaDB tramite un filesystem clusterizzato non trasforma automaticamente il database in active-active e può corrompere i dati.

Il database deve usare una tecnologia progettata per replica/failover, oppure essere gestito come risorsa single-primary da un cluster manager.

Storage replicato: DRBD è ancora attuale, ma va usato nel modello giusto

DRBD 9 continua a replicare dispositivi a blocchi tra nodi. Il modello canonico per HA è single-primary: un solo nodo usa il filesystem e Pacemaker sposta risorsa, filesystem, VIP e servizio in caso di guasto.

Il dual-primary esiste ancora, ma richiede accesso concorrente gestito da un cluster filesystem come GFS2/OCFS2, replica sincrona, latenza bassa e soprattutto fencing robusto. Senza fencing una perdita di comunicazione può lasciare entrambi i nodi Primary e produrre split-brain.

Split-brain: il guasto peggiore non è il nodo morto

Un nodo chiaramente spento è semplice da gestire. Il caso difficile è quando due nodi non riescono più a parlarsi ma entrambi continuano a funzionare. Ognuno può credere che l’altro sia morto e iniziare a servire la stessa risorsa.

Se la risorsa è un VIP otteniamo traffico imprevedibile. Se è storage o un database possiamo ottenere divergenza o corruzione. È il motivo per cui quorum e fencing sono parte della sicurezza dei dati, non optional “enterprise”.

Fencing / STONITH

Fencing significa rendere certamente incapace un nodo di continuare a usare una risorsa prima di attivarla altrove. Può significare spegnere il server tramite IPMI/PDU oppure revocargli l’accesso allo storage.

Pacemaker considera il fencing uno dei meccanismi centrali per proteggere da split-brain. Il cluster deve poter distinguere “non risponde” da “è sicuramente fuori gioco”.

Tre livelli diversi, tre tecnologie diverse

ProblemaEsempi di soluzione
Distribuire richiesteHAProxy
Rendere ridondante il punto di ingressoKeepalived/VRRP oppure LB esterno
Gestire risorse active/passivePacemaker + Corosync + fencing
Replicare block storageDRBD
Database HAreplica/failover specifici del DB
File condivisistorage distribuito/object storage/NFS HA secondo il caso

Testare i guasti, non soltanto il traffico normale

  • fermare un backend e verificare che esca dalla rotazione;
  • fermare HAProxy sul nodo con VIP e verificare il failover;
  • riavviare un nodo e controllare il ritorno senza flap;
  • interrompere il link di replica in un laboratorio e verificare il comportamento previsto;
  • controllare che logging e monitoring segnalino ogni transizione.

Un cluster che non è mai stato provato durante un guasto non è ancora un cluster verificato: è soltanto una configurazione che funziona quando non serve.

La vecchia serie del 2012

Le tre parti originali su DRBD8 dual-primary, OCFS2 e condivisione applicativa restano online come archivio storico. Mostrano bene il problema che stavamo cercando di risolvere, ma contengono versioni software e scelte architetturali che non vanno replicate oggi senza una revisione profonda, soprattutto per ciò che riguarda fencing, split-brain e condivisione dello stato.

Riferimenti

HAProxy health checks · DRBD 9 User Guide · Pacemaker Explained.