Pots reconstruir srv-tramontana en cinquanta minuts. Pots provar qualsevol canvi abans d'aplicar-lo, detectar una intrusió, xifrar els secrets, diagnosticar una latència anòmala i recuperar una arrencada trencada. I tot i així, si aquella màquina cau, Tramontana Reserves està caiguda.
Un disc que falla, una font d'alimentació, un tall de xarxa del proveïdor, un nucli que no arrenca després d'una actualització, o simplement el manteniment programat de la infraestructura on viu. En el millor dels casos, cinquanta minuts d'interrupció — i això suposant que algú estigui despert per llançar el playbook. Tota la feina de set mòduls descansa sobre una única màquina, que és la definició exacta d'un punt únic de fallada.
Aquesta lliçó ataca aquell problema, i ho fa amb un advertiment per endavant: l'alta disponibilitat és cara, complexa i, mal feta, empitjora la fiabilitat en lloc de millorar-la. Un sistema amb més peces té més maneres de fallar. La lliçó acaba amb l'anàlisi honesta de si Tramontana necessita això, perquè la resposta professional no sempre és que sí.
Contingut
- Vocabulari: disponibilitat, nous, MTBF i MTTR
- Escalat vertical i horitzontal
- Redundància: actiu-passiu i actiu-actiu
- L'estat és el problema difícil
- Balanceig de càrrega: capes, algorismes i comprovacions de salut
- HAProxy a la pràctica
- Alta disponibilitat del balancejador: keepalived i VRRP
- Cervell dividit i quòrum
- Replicació de dades
- Clústers, Pacemaker i delimitació
- Disseny per a Tramontana i anàlisi de cost
- Provar les fallades provocant-les
Vocabulari: disponibilitat, nous, MTBF i MTTR
L'àrea és plena de termes que es fan servir com a sinònims i no ho són. Fixar-los estalvia malentesos cars.
La disponibilitat és la fracció de temps en què el servei respon correctament. S'expressa en percentatge, i en «nous»:
| Disponibilitat | Nom | Caiguda màxima a l'any | Al mes | A la setmana |
|---|---|---|---|---|
| 99 % | «dos nous» | 3 dies 15 h | 7 h 18 min | 1 h 41 min |
| 99,9 % | «tres nous» | 8 h 46 min | 43 min 50 s | 10 min 5 s |
| 99,95 % | 4 h 23 min | 21 min 55 s | 5 min 2 s | |
| 99,99 % | «quatre nous» | 52 min 34 s | 4 min 23 s | 1 min |
| 99,999 % | «cinc nous» | 5 min 15 s | 26 s | 6 s |
Mirar aquella taula a poc a poc corregeix la intuïció de gairebé tothom. 99,99 % significa menys d'un minut de caiguda a la setmana, incloses les actualitzacions, els reinicis per nucli nou i les fallades del proveïdor. Amb un únic servidor, un sol reinici mensual de tres minuts ja et situa per sota de tres nous i mig. I cada nou addicional multiplica el cost aproximadament per tres.
Tres distincions que convé tenir clares:
- Fiabilitat és la probabilitat de funcionar sense fallada durant un interval. Un sistema pot ser molt disponible i poc fiable: falla sovint, però es recupera en segons.
- Durabilitat es refereix a les dades: la probabilitat de no perdre-les. És independent de la disponibilitat — un sistema pot estar caigut i tenir les dades perfectament segures.
- MTBF (temps mitjà entre fallades) i MTTR (temps mitjà de reparació) descomponen la disponibilitat:
I aquella fórmula conté la decisió estratègica de tota la lliçó: hi ha dues formes de pujar la disponibilitat. Augmentar l'MTBF —fallar menys: millor maquinari, més proves, menys canvis— o reduir l'MTTR —recuperar-se abans—. A la pràctica, reduir l'MTTR és gairebé sempre més barat i més eficaç, perquè l'MTBF té un sostre físic i l'MTTR no.
És exactament el que vas fer a 07-06 sense anomenar-ho així: passar l'MTTR de 8 hores a 50 minuts va multiplicar la disponibilitat sense comprar ni un sol component.
I la relació amb el que ja tens acordat amb la Marta:
| Mètrica | Què mesura | Valor actual |
|---|---|---|
| RPO | Quantes dades es poden perdre | 4 hores |
| RTO | Quant pot durar la interrupció | 2 hores (revisat a 07-06) |
| Disponibilitat | Fracció de temps funcionant | Sense objectiu formal |
Hi ha un forat aquí: ningú no ha fixat un objectiu de disponibilitat. I sense ell, no es pot decidir quant invertir.
Escalat vertical i horitzontal
| Vertical (scale up) | Horitzontal (scale out) | |
|---|---|---|
| Què es fa | Màquina més gran | Més màquines |
| Límit | El maquinari més gran que existeix | Pràcticament cap |
| Cost | Creix més que linealment | Aproximadament lineal |
| Complexitat | Cap | Alta: repartiment, estat, coherència |
| Efecte en la disponibilitat | Cap: continua havent-hi un punt únic de fallada | És el que la habilita |
| Requereix canvis a l'aplicació | No | Gairebé sempre sí |
La fila que importa aquí és la penúltima. Duplicar la memòria de srv-tramontana la faria més ràpida i exactament igual de fràgil. L'escalat horitzontal és el que permet que la fallada d'una màquina no sigui la fallada del servei, i per això aquesta lliçó va d'escalat horitzontal encara que el problema no sigui de capacitat.
Redundància: actiu-passiu i actiu-actiu
Tenir dues màquines no és tenir redundància: cal decidir què fa cadascuna.
| Actiu-passiu | Actiu-actiu | |
|---|---|---|
| El segon node | Espera, sense atendre trànsit | Atén trànsit també |
| Aprofitament | 50 % del maquinari | ~100 % |
| Temps de commutació | Segons a minuts | Immediat |
| Complexitat | Mitjana | Alta |
| Exigeix que l'aplicació… | Arrenqui a l'altre node | Sigui capaç d'executar-se en diverses instàncies alhora |
| Risc característic | Que el passiu no funcioni quan calgui | Cervell dividit, coherència de dades |
El risc de l'actiu-passiu se subestima sempre i mereix nom propi: un node passiu que no es fa servir mai és un node del qual no saps si funciona. Es va instal·lar fa vuit mesos, no ha rebut les últimes actualitzacions, té un certificat caducat o un disc ple. El dia de la fallada, descobreixes que el suplent també falla. L'única defensa és fer-lo servir periòdicament: commutar expressament cada poques setmanes.
L'estat és el problema difícil
Aquí està el nucli conceptual de la lliçó, i el que separa un disseny que funciona d'un que sembla que funciona.
Replicar processos és fàcil: arrenques la mateixa aplicació en dues màquines i ja en tens dues. Replicar estat és difícil, i l'estat és en més llocs dels que sembla.
Inventari de l'estat de Tramontana Reserves:
| Estat | On viu | Problema en duplicar |
|---|---|---|
| Dades de reserves | PostgreSQL | El problema central: dues bases de dades divergeixen |
| Fitxers pujats | /opt/tramontana/shared/uploads |
Una foto pujada al node A no és al B |
| Sessions d'usuari | A la memòria del procés | Una petició al node B no reconeix la sessió iniciada a A |
| Registres | /var/log/tramontana/ |
Es reparteixen entre nodes: cal centralitzar per diagnosticar |
| Tasques programades | Temporitzadors de systemd | S'executarien DUES vegades: dues còpies de seguretat, dues purgues |
| Certificat TLS | /etc/letsencrypt/ |
Cal sincronitzar-lo o acabar-lo al balancejador |
Aquella fila de les tasques programades és la que més problemes dona a la pràctica, perquè ningú no hi pensa en duplicar un servidor. Amb dos nodes idèntics, tramontana-copia.timer es dispara a les 02:30 en tots dos: dues còpies simultànies al mateix destí, competint pel flock —que és local a cada màquina i per tant no protegeix res—. La solució exigeix un blocatge distribuït o executar les tasques només en un node designat.
Una aplicació sense estat (stateless) és aquella les instàncies de la qual no guarden res entre peticions: tot l'estat és a la base de dades o en un magatzem compartit. És el requisit per a l'actiu-actiu, i gairebé mai no es compleix sense feina de desenvolupament. A Tramontana:
| Component | Sense estat? | Què caldria |
|---|---|---|
| Servir pàgines i consultar reserves | Sí | Res |
| Sessions d'usuari | No | Moure-les a Redis o a la base de dades |
| Pujada de fotos | No | Emmagatzematge compartit o magatzem d'objectes |
| Base de dades | No, per definició | Replicació |
Conclusió honesta: Tramontana no és una aplicació sense estat avui. Abans de muntar res, calen dos canvis a l'aplicació: sessions externalitzades i fitxers en emmagatzematge compartit. Això és feina de desenvolupament, i dir-l'hi a la Marta abans de comprar servidors és part de la feina.
Balanceig de càrrega: capes, algorismes i comprovacions de salut
Un balancejador reparteix peticions entre diversos servidors. Opera en una de dues capes:
| Capa 4 (transport) | Capa 7 (aplicació) | |
|---|---|---|
| Què inspecciona | IP i port | Capçaleres HTTP, rutes, galetes |
| Rendiment | Molt alt | Alt, però menor |
| Terminació TLS | No pot | Sí |
| Repartiment per ruta o domini | No | Sí |
| Reintent d'una petició fallida | No | Sí |
| Exemples | IPVS, nftables, NLB |
HAProxy, Nginx, Traefik |
Per a HTTP, la capa 7 és gairebé sempre la correcta: permet acabar TLS en un punt, repartir segons la ruta, reintentar una petició que falla i fer comprovacions de salut significatives.
Algorismes
| Algorisme | Com reparteix | Quan |
|---|---|---|
roundrobin |
Per torns | Peticions homogènies, servidors iguals |
leastconn |
Al que menys connexions té | Peticions de durada variable |
source |
Hash de la IP d'origen | Quan cal afinitat (amb reserves) |
uri |
Hash de la ruta | Memòries cau: la mateixa ruta sempre al mateix node |
random |
A l'atzar, amb dos sondejos | Molts balancejadors en paral·lel |
leastconn és el més adequat per a Tramontana: una consulta de disponibilitat triga 40 ms i un informe de facturació uns quants segons, així que repartir per torns amuntegaria les lentes al mateix node.
Comprovacions de salut
És la peça que fa útil el balancejador: sense ella, continuaria enviant trànsit a un servidor caigut.
| Tipus | Com funciona | Avantatge |
|---|---|---|
| Activa | El balancejador sondeja cada N segons | Detecta la fallada encara que no hi hagi trànsit |
| Passiva | Observa els errors del trànsit real | Sense cost afegit; detecta fallades parcials |
El correcte és fer servir totes dues. I aquí encaixa una cosa que tens des del Mòdul 4: revisio_salut.sh retorna 0, 1 o 2 — correcte, avís, crític. Aquella gradació és exactament el que un balancejador necessita, si l'aplicació exposa un extrem equivalent:
| Estat | HTTP | Què ha de fer el balancejador |
|---|---|---|
| 0 correcte | 200 | Enviar trànsit normalment |
| 1 avís | 200 amb capçalera d'avís | Enviar, però amb menys pes |
| 2 crític | 503 | No enviar trànsit |
I la distinció que gairebé ningú no fa bé: una comprovació superficial és pitjor que cap. Si l'extrem de salut només respon «estic viu» sense comprovar la base de dades, el balancejador continuarà enviant trànsit a un node que no pot atendre cap petició. Si comprova massa —per exemple, una consulta pesada—, un problema de la base de dades marca tots els nodes com a caiguts i el servei desapareix del tot, quan podria haver servit almenys les pàgines estàtiques.
L'equilibri: comprovar les dependències crítiques amb una operació barata. Una consulta SELECT 1 amb temps límit, no un informe.
Sessions persistents: un antipatró
La sticky session lliga cada client a un node, per galeta o per IP. Resol el problema de les sessions en memòria... i en crea d'altres:
- El repartiment es desequilibra: un node pot acabar amb el triple de càrrega.
- Quan un node cau, tots els seus usuaris perden la sessió de cop.
- Impedeix drenar un node per a manteniment sense afectar ningú.
- Emmascara el problema real en lloc de resoldre'l.
És una solució transitòria acceptable mentre s'externalitzen les sessions. Com a solució permanent, no.
Drenatge de connexions
Per desplegar sense tallar el servei, un node ha de poder sortir del repartiment ordenadament: deixar de rebre peticions noves i acabar les que ja té en curs.
# 1. Treure el node del repartiment (deixa de rebre peticions noves)
$ echo "set server tramontana/app1 state drain" | \
sudo socat stdio /run/haproxy/admin.sock
# 2. Esperar que acabin les connexions en curs
$ while [[ "$(echo 'show stat' | sudo socat stdio /run/haproxy/admin.sock \
| awk -F, '$1=="tramontana" && $2=="app1" {print $5}')" != "0" ]]; do
sleep 1
done
# 3. Desplegar amb tranquil·litat
$ ssh [email protected] 'sudo ~/scripts/desplegar.sh 3.3.0'
# 4. Tornar-lo al repartiment
$ echo "set server tramontana/app1 state ready" | \
sudo socat stdio /run/haproxy/admin.sockRepetit node a node, això és un desplegament sense interrupció. I és la resposta a una cosa que va quedar pendent des de 04-07: desplegar.sh reinicia el servei, i aquell reinici són uns segons de peticions rebutjades. Amb dos nodes i drenatge, cap.
HAProxy a la pràctica
# /etc/haproxy/haproxy.cfg
global
log /dev/log local0
chroot /var/lib/haproxy
# Socol d administracio: permet drenar nodes en calent
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
maxconn 4000
# Perfil TLS modern (06-05). Mai no inventis la llista de suites.
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
# Reintenta en un ALTRE servidor si el primer falla. Nomes es segur per a
# peticions idempotents: per aixo 'if-none', que exclou POST.
retry-on all-retryable-errors
option redispatch
retries 3
timeout connect 5s
timeout client 30s
timeout server 30s
timeout http-request 10s
# Drenatge: espera que acabin les connexions en treure un node
default-server init-addr last,libc,none
# ---------- Entrada ----------
frontend tramontana_https
bind *:443 ssl crt /etc/haproxy/certs/reserves.tramontana.example.pem alpn h2,http/1.1
bind *:80
# Redirigeix HTTP a HTTPS, llevat del desafiament de Let's Encrypt
acl acme_challenge path_beg /.well-known/acme-challenge/
http-request redirect scheme https code 301 unless { ssl_fc } || acme_challenge
# HSTS (06-05). Comencar amb max-age curt i pujar-lo despres.
http-response set-header Strict-Transport-Security "max-age=63072000"
# Limit de taxa per IP: complementa fail2ban a la capa 7
stick-table type ip size 100k expire 60s store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
default_backend tramontana_app
# ---------- Servidors d aplicacio ----------
backend tramontana_app
# leastconn: les peticions tenen durada molt variable
balance leastconn
# Comprovacio de salut ACTIVA a la capa 7.
# L extrem /salut comprova les dependencies critiques de forma barata.
option httpchk
http-check send meth GET uri /salut ver HTTP/1.1 hdr Host reserves.tramontana.example
http-check expect status 200
# Capcaleres perque l aplicacio sapiga qui es el client real
http-request set-header X-Forwarded-Proto https if { ssl_fc }
option forwardfor
# inter: cada quant sondejar. rise/fall: quantes vegades seguides
# per considerar el node sa o caigut. slowstart: en tornar, puja
# el pes progressivament en lloc de rebre tota la carrega de cop.
server app1 10.0.2.21:8080 check inter 3s rise 2 fall 3 slowstart 30s maxconn 200
server app2 10.0.2.22:8080 check inter 3s rise 2 fall 3 slowstart 30s maxconn 200
# Comprovacio PASSIVA: si un servidor dona errors al transit real,
# es treu temporalment encara que el seu /salut respongui.
option redispatch
# ---------- Estadistiques, nomes des de la xarxa interna ----------
listen stats
bind 10.0.2.20:8404
stats enable
stats uri /
stats refresh 5s
stats admin if TRUE
acl xarxa_interna src 10.0.2.0/24
http-request deny unless xarxa_interna$ sudo haproxy -c -f /etc/haproxy/haproxy.cfg
Configuration file is valid
$ sudo systemctl reload haproxy
$ echo "show stat" | sudo socat stdio /run/haproxy/admin.sock | \
awk -F, 'NR==1 || $1=="tramontana_app" {print $1","$2","$18","$5}'
# pxname,svname,status,scur
tramontana_app,app1,UP,12
tramontana_app,app2,UP,9
tramontana_app,BACKEND,UP,21Quatre detalls d'aquella configuració que mereixen explicació:
slowstart 30s. Quan un node torna després d'una fallada, les seves memòries cau són fredes i els seus grups de connexions buits. Enviar-li un terç del trànsit de cop el pot tombar un altre cop, i entrar en un cicle de caigudes. slowstart puja el seu pes progressivament durant 30 segons.
rise 2 fall 3. Asimètric a propòsit: calen tres fallades seguides per treure un node (evita falsos positius per un pic) però només dos èxits per tornar-lo. Treure de cop per una fallada transitòria és com un balancejador provoca una caiguda.
option redispatch amb retry-on. Si el servidor triat falla, reintenta en un altre. Amb un matís important: reintentar un POST pot duplicar una reserva. HAProxy només reintenta quan la petició no s'ha enviat o el mètode és idempotent.
El stick-table amb límit de taxa. És el fail2ban de 06-03 a la capa 7: compta peticions per IP en finestres de 10 segons i retorna 429 en superar el llindar. Complementari, no substitut: fail2ban bloqueja a nivell de xarxa i aquest a nivell d'aplicació.
Nginx pot fer el mateix amb upstream, i és l'opció natural si ja el tens com a servidor intermediari invers — que és el que muntaràs a 08-01:
upstream tramontana {
least_conn;
server 10.0.2.21:8080 max_fails=3 fail_timeout=30s;
server 10.0.2.22:8080 max_fails=3 fail_timeout=30s;
}La diferència pràctica: Nginx només fa comprovacions passives a la seva versió lliure; les actives són de la versió comercial. HAProxy les porta de sèrie, i per això és preferible quan el balanceig és la funció principal.
Alta disponibilitat del balancejador: keepalived i VRRP
I aquí està l'error de disseny més comú de l'àrea, tan freqüent que mereix enunciar-se sol:
Posar un balancejador davant de dos servidors no elimina el punt únic de fallada: el mou al balancejador.
Abans tenies una màquina que podia caure. Ara en tens tres, i si cau la que reparteix, el servei desapareix igual — amb l'agreujant que ara hi ha més peces que poden fallar.
La solució és VRRP (Virtual Router Redundancy Protocol): dos balancejadors comparteixen una IP virtual flotant, la que apunta el DNS. Un la té; si deixa d'anunciar-se, l'altre se la queda en segons.
# /etc/keepalived/keepalived.conf — NODE PRINCIPAL (lb1, 10.0.2.20)
global_defs {
router_id lb1
enable_script_security
script_user keepalived_script
}
# Comprovacio del servei LOCAL. Si HAProxy mor en aquest node, la
# prioritat baixa i l altre es queda la IP. Sense aixo, un node amb
# keepalived viu i HAProxy mort reté la IP i el servei cau.
vrrp_script comprovar_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight -40 # baixa la prioritat 40 punts si falla
fall 2
rise 2
}
vrrp_instance TRAMONTANA_VIP {
state MASTER
interface enp0s3
virtual_router_id 51 # IDENTIC en tots dos nodes
priority 100 # mes gran que el suplent
advert_int 1 # anunci cada segon
authentication {
auth_type PASS
auth_pass {{ vault_vrrp_pass }}
}
virtual_ipaddress {
10.0.2.30/24 dev enp0s3
}
track_script {
comprovar_haproxy
}
notify_master "/usr/local/sbin/vrrp-notificar master"
notify_backup "/usr/local/sbin/vrrp-notificar backup"
notify_fault "/usr/local/sbin/vrrp-notificar fault"
}# NODE SUPLENT (lb2, 10.0.2.21): identic llevat de tres linies
state BACKUP
priority 90
router_id lb2$ sudo systemctl enable --now keepalived
$ ip -brief addr show enp0s3
enp0s3 UP 10.0.2.20/24 10.0.2.30/24 # <- la VIP es a lb1
# A lb2, la VIP NO apareix: esta en espera
$ ssh [email protected] 'ip -brief addr show enp0s3'
enp0s3 UP 10.0.2.21/24El mecanisme: el node amb més prioritat anuncia la VIP cada segon. Si el suplent deixa de sentir anuncis durant tres intervals, assumeix que el principal ha caigut, s'assigna la IP i envia un ARP gratuït perquè els commutadors de la xarxa actualitzin les seves taules. La commutació triga entre 3 i 4 segons.
El track_script amb weight -40 és el que evita la fallada més ximple d'aquesta arquitectura: sense ell, un node on keepalived funciona però HAProxy està mort reté la VIP alegrement, i el servei queda caigut encara que l'altre balancejador estigui perfectament. Amb ell, la prioritat d'lb1 baixa de 100 a 60, per sota dels 90 d'lb2, i la VIP es mou.
I enable_script_security amb script_user és una precaució de seguretat real: keepalived corre com a root i executa aquells scripts; sense aquelles directives, un script amb permisos laxos és una escalada de privilegis.
Cervell dividit i quòrum
La fallada característica de qualsevol sistema redundant, i la que causa les pèrdues de dades més greus.
El cervell dividit (split-brain) passa quan els nodes deixen de veure's entre si però tots dos continuen funcionant. Cadascun conclou que l'altre ha caigut, i tots dos es declaren principals:
- Dos balancejadors anuncien la mateixa IP virtual → trànsit impredictible, connexions tallades.
- Dues bases de dades accepten escriptures → les dades divergeixen, i reconciliar-les després pot ser impossible.
L'escenari que ho provoca no és que un node caigui —això es gestiona bé—, sinó que la xarxa entre ells es talli mentre tots dos continuen vius i atenent clients. És més freqüent del que sembla: un commutador que falla, una regla de tallafocs mal posada, una saturació momentània.
Les tres defenses:
| Defensa | Com funciona | Limitació |
|---|---|---|
| Quòrum | Només actua el grup amb majoria de nodes | Exigeix 3 o més nodes senars |
| Delimitació (STONITH) | El supervivent apaga l'altre per maquinari | Requereix control remot d'energia o de l'hipervisor |
| Testimoni | Un tercer recurs arbitra (un disc, una IP) | Menys robust que el quòrum real |
I la conclusió que cal retenir:
Amb dos nodes no hi ha quòrum possible. Cap dels dos no pot tenir majoria de dos. Per això un clúster seriós té tres nodes, o dos més un testimoni.
Això té una conseqüència directa en el disseny de Tramontana: un parell de balancejadors amb VRRP és acceptable, perquè el pitjor cas —dos nodes anunciant la mateixa IP uns segons— és molest però recuperable. Un parell de bases de dades amb commutació automàtica no ho és: el pitjor cas és la divergència de dades, i no es recupera. D'aquí la recomanació de l'apartat següent.
Replicació de dades
PostgreSQL replica mitjançant el registre d'escriptura anticipada (WAL): el primari envia els seus canvis a una o més rèpliques.
| Mode | El primari confirma quan… | Risc de pèrdua | Cost |
|---|---|---|---|
| Asíncrona | Ha escrit localment | Les últimes transaccions | Cap |
| Síncrona | La rèplica ha confirmat | Cap | Latència de cada escriptura |
I el compromís, que cal entendre abans de triar: amb replicació síncrona, cada escriptura espera el viatge d'anada i tornada a la rèplica. A la mateixa xarxa local, un o dos mil·lisegons. Entre centres de dades, desenes. I si la rèplica cau, el primari es bloqueja esperant confirmació — llevat que es configuri synchronous_standby_names amb la sintaxi adequada, cosa que obre la porta a perdre dades justament quan més importa.
Per a Tramontana, amb un RPO de 4 hores acordat, la replicació asíncrona és més que suficient: perdre els últims segons de transaccions està molt dins del que es tolera.
# postgresql.conf al PRIMARI
wal_level = replica
max_wal_senders = 3
wal_keep_size = 1GB
archive_mode = on
archive_command = 'test ! -f /srv/wal/%f && cp %p /srv/wal/%f'
hot_standby = on# Crear la replica des de zero
$ sudo -u postgres pg_basebackup -h 10.0.2.15 -U replicador \
-D /var/lib/postgresql/16/main -R -P -X stream -c fast
$ sudo -u postgres psql -c "SELECT client_addr, state, sync_state,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS retard_bytes FROM pg_stat_replication;"
client_addr | state | sync_state | retard_bytes
-------------+-----------+------------+--------------
10.0.2.22 | streaming | async | 0Aquell retard_bytes és la mètrica que cal vigilar: és quantes dades es perdrien si el primari caigués ara. Va directa a la monitoració, al costat de l'antiguitat de l'última còpia de 05-08.
Commutació: manual davant d'automàtica
| Manual | Automàtica | |
|---|---|---|
| Temps de recuperació | Minuts | Segons |
| Risc de cervell dividit | Cap | Alt sense quòrum |
| Requereix personal disponible | Sí | No |
| Eines | pg_ctl promote |
Patroni, repmgr, pg_auto_failover |
La recomanació professional, i va contra la intuïció:
Amb dos nodes, la commutació de base de dades ha de ser manual. Sense quòrum, una fallada de xarxa entre primari i rèplica pot promoure la rèplica mentre el primari continua acceptant escriptures, i aleshores hi ha dues bases de dades divergents. Recuperar-se d'allà pot significar perdre transaccions o reconciliar a mà.
Eines com Patroni fan commutació automàtica segura, però requereixen un magatzem de configuració distribuït amb quòrum propi (etcd o Consul), que al seu torn necessita tres nodes. És a dir: automatitzar la commutació de forma segura exigeix, com a mínim, cinc màquines. És una decisió d'arquitectura, no una casella per marcar.
Emmagatzematge compartit
Per a /opt/tramontana/shared/uploads:
| Opció | Avantatge | Inconvenient |
|---|---|---|
| NFS | Simple, funciona sense tocar l'aplicació | És un nou punt únic de fallada |
| GlusterFS / Ceph | Distribuït i replicat | Complexitat alta |
| Magatzem d'objectes (S3, MinIO) | Escalable, gestionat, replicat | Requereix canviar l'aplicació |
rsync periòdic |
Trivial | No és temps real: es perden fitxers |
NFS té la ironia característica: es munta per donar alta disponibilitat i, si no es fa també redundant, empitjora la fiabilitat, perquè ara dos servidors depenen d'una tercera màquina.
La resposta moderna és el magatzem d'objectes: és el que fan servir les aplicacions dissenyades per escalar horitzontalment. Exigeix feina de desenvolupament, i és la mateixa conclusió de l'apartat de l'estat.
Clústers, Pacemaker i delimitació
Per a escenaris més complexos —bases de dades, sistemes de fitxers compartits, recursos amb dependències— existeix la pila Pacemaker + Corosync:
- Corosync: comunicació entre nodes, pertinença al clúster i quòrum.
- Pacemaker: gestió de recursos, amb dependències i restriccions.
$ sudo pcs status
Cluster name: tramontana
Cluster Summary:
* Stack: corosync
* Current DC: node1 (version 2.1.6) - partition with quorum
* 3 nodes configured
* 4 resource instances configured
Full List of Resources:
* vip_tramontana (ocf:heartbeat:IPaddr2): Started node1
* postgresql (ocf:heartbeat:pgsql): Master node1Un recurs és qualsevol cosa que el clúster gestiona (una IP, un servei, un sistema de fitxers), i les restriccions expressen regles: «la IP ha de ser on és el primari de PostgreSQL», «aquest servei ha d'arrencar després d'aquell».
I el concepte sense el qual res d'això no funciona de debò:
Delimitació (o STONITH, Shoot The Other Node In The Head): abans de prendre els recursos d'un node que sembla caigut, el supervivent s'assegura que està apagat, tallant-li l'energia o destruint la seva màquina virtual.
Per què és imprescindible: si el node A sembla caigut però en realitat només està congelat o incomunicat, i B pren els seus recursos —munta el sistema de fitxers, promou la base de dades, agafa la IP—, quan A es recuperi tots dos escriuran a les mateixes dades. La corrupció resultant pot ser irrecuperable.
# Delimitacio sobre libvirt: destrueix la maquina virtual de l altre node
$ sudo pcs stonith create fence_node2 fence_virsh \
ip=10.0.2.10 login=fence identity_file=/etc/pacemaker/id_rsa \
pcmk_host_list=node2 action=offEn maquinari físic es fa amb IPMI, iLO o unitats de distribució d'energia gestionables. I la regla de l'àrea és contundent: un clúster sense delimitació configurada no és un clúster fiable. Molts projectes la desactiven «temporalment» perquè complica les proves, i aquella és exactament la configuració que provoca la pèrdua de dades el dia de la fallada.
Disseny per a Tramontana i anàlisi de cost
graph TD
I["Internet"] --> DNS["DNS: reserves.tramontana.example<br/>→ 10.0.2.30 (VIP)"]
DNS --> VIP{"IP virtual flotant<br/>10.0.2.30"}
VIP -.->|VRRP| LB1["lb1 · HAProxy<br/>MASTER prio 100"]
VIP -.->|VRRP| LB2["lb2 · HAProxy<br/>BACKUP prio 90"]
LB1 --> APP1["app1 · Tramontana<br/>10.0.2.21:8080"]
LB1 --> APP2["app2 · Tramontana<br/>10.0.2.22:8080"]
LB2 -.-> APP1
LB2 -.-> APP2
APP1 --> BD1["PostgreSQL primari<br/>10.0.2.15"]
APP2 --> BD1
BD1 -->|WAL asincron| BD2["PostgreSQL replica<br/>10.0.2.16"]
APP1 --> OBJ["Magatzem d objectes<br/>uploads"]
APP2 --> OBJ
Què protegeix cada capa, i què no:
| Fallada | Coberta? | Temps de recuperació |
|---|---|---|
Cau app1 o app2 |
Sí, automàtic | 3-9 s (fall 3 × inter 3s) |
Cau lb1 |
Sí, automàtic | 3-4 s (VRRP) |
| Cau el primari de PostgreSQL | No: promoció manual deliberada | 5-15 min |
| Falla la xarxa entre lb1 i lb2 | Parcial: cervell dividit breu, recuperable | Segons |
| Cau l'amfitrió que allotja tot | No | RTO complet |
| Una fallada lògica (esborrat, corrupció) | No: es replica a l'instant | Restaurar còpia |
| Un desplegament defectuós | No: es desplega en tots dos | Reversió |
Les tres últimes files són les que cal subratllar davant de la direcció. La redundància no protegeix contra els errors lògics: un DELETE equivocat es replica en mil·lisegons a la rèplica. Per a això hi ha les còpies de 05-08, i aquesta arquitectura no les substitueix en absolut.
L'anàlisi de cost
El que la lliçó ve prometent, i el que la Marta fa tres respostes que demana:
Cost de l'arquitectura completa (5 màquines davant d'1):
| Concepte | Estimació anual |
|---|---|
| 4 màquines addicionals | Cost del VPS actual × 4 |
| Feina de desenvolupament (sessions + magatzem d'objectes) | 3-4 setmanes, una vegada |
| Muntatge i automatització amb Ansible | 2 setmanes, una vegada |
| Manteniment addicional | ~4 h/mes de forma indefinida |
| Magatzem d'objectes | Baix, per volum |
| Complexitat: més modes de fallada | Difícil de quantificar, real |
Cost de la indisponibilitat. La pregunta que cal respondre abans:
Reserves al mes: ~500 Import mitja: 313,70 € Ingres mitja per hora (24×30): 500 × 313,70 / 720 ≈ 218 €/h
Però aquell càlcul brut és enganyós per tres motius, i dir-ho és part de l'anàlisi:
- Les reserves no són uniformes. Es concentren a la tarda i en cap de setmana. Una caiguda de dues hores un dimarts de matinada pot costar zero; la mateixa caiguda un divendres a la tarda, molt més que la mitjana.
- Una reserva perduda no sempre es perd. Molts clients ho tornen a intentar. El cost real està entre el 20 % i el 50 % de la xifra bruta.
- Hi ha cost reputacional, no lineal i difícil de quantificar, especialment si la caiguda coincideix amb una campanya.
Estimació raonable del cost anual d'indisponibilitat, amb diferents objectius:
| Disponibilitat | Caiguda anual | Cost estimat (a 218 €/h × 35 %) |
|---|---|---|
| 99,5 % (situació actual estimada) | 43,8 h | ~3.340 € |
| 99,9 % | 8,8 h | ~670 € |
| 99,95 % | 4,4 h | ~335 € |
L'estalvi de passar de 99,5 % a 99,9 % és d'uns 2.700 € l'any. Això cal comparar-ho amb el cost de l'arquitectura completa —quatre màquines més, sis setmanes de feina inicial i quatre hores mensuals indefinides—, que molt probablement el supera.
I aquí està l'anàlisi que gairebé ningú no fa, i que canvia la recomanació:
| Mesura | Cost | Disponibilitat estimada |
|---|---|---|
| Situació actual | — | ~99,5 % |
| Només dos nodes d'aplicació + un balancejador | 2 màquines, 1 setmana | ~99,8 % |
| Arquitectura completa amb BD replicada | 4 màquines, 6 setmanes | ~99,9 % |
El primer pas captura la major part del benefici per una fracció del cost, i això és l'habitual en disponibilitat: els primers nous són barats i els següents s'encareixen exponencialment. La raó és que la majoria de les interrupcions no vénen d'una fallada de maquinari, sinó de desplegaments, reinicis i manteniment — i tot això es cobreix amb dos nodes d'aplicació i drenatge de connexions, sense tocar la base de dades.
Recomanació
Fase 1 (ara): fixar un objectiu formal de disponibilitat amb la Marta. Sense objectiu no es pot decidir quant invertir. I mesurar-la, perquè el 99,5 % actual és una estimació, no una dada.
Fase 2 (1-2 mesos): dos nodes d'aplicació i un balancejador. Sessions externalitzades i fitxers en magatzem d'objectes, que és feina de desenvolupament. Elimina les interrupcions per desplegament i per fallada d'un node, que són la majoria.
Fase 3 (avaluar després): segon balancejador amb VRRP, i rèplica de PostgreSQL amb promoció manual.
Fase 4 (probablement mai): commutació automàtica de base de dades. Exigeix quòrum, és a dir, cinc màquines i un magatzem distribuït. Per a una empresa d'aquesta mida, la complexitat supera el benefici.
Provar les fallades provocant-les
Un sistema d'alta disponibilitat no provat no és un sistema d'alta disponibilitat: és una arquitectura que et penses que funciona. I la història de l'àrea és plena de casos en què el suplent va fallar el dia que va caldre.
# --- Prova 1: cau un node d aplicacio ---
$ ssh [email protected] 'sudo systemctl stop tramontana'
# Esperat: HAProxy el treu en ~9 s (fall 3 x inter 3s), sense errors visibles
$ for i in {1..60}; do
curl -s -o /dev/null -w '%{http_code} ' https://reserves.tramontana.example/cases
sleep 1
done; echo
# Criteri: cap resposta != 200
# --- Prova 2: cau el balancejador principal ---
$ ssh [email protected] 'sudo systemctl stop keepalived'
# Esperat: la VIP es mou a lb2 en 3-4 s
$ ssh [email protected] 'ip -brief addr show enp0s3 | grep 10.0.2.30'
# --- Prova 3: HAProxy mor pero keepalived viu ---
# Es la prova que valida el track_script. Sense ell, la VIP NO es mou
# i el servei cau amb tots dos balancejadors "vius".
$ ssh [email protected] 'sudo systemctl stop haproxy'
$ sleep 6 && ssh [email protected] 'ip -brief addr show enp0s3 | grep 10.0.2.30'
# --- Prova 4: particio de xarxa entre balancejadors (cervell dividit) ---
$ ssh [email protected] 'sudo nft add rule inet filter input ip saddr 10.0.2.21 drop'
# Esperat: TOTS DOS prenen la VIP. Documentar el comportament observat.
$ ssh [email protected] 'sudo nft flush chain inet filter input'
# --- Prova 5: desplegament sense interrupcio ---
$ echo "set server tramontana_app/app1 state drain" | sudo socat stdio /run/haproxy/admin.sock
$ ssh [email protected] 'sudo ~/scripts/desplegar.sh 3.3.0'
$ echo "set server tramontana_app/app1 state ready" | sudo socat stdio /run/haproxy/admin.sock
# Criteri: cap error durant tot el procesLa prova 4 és la més incòmoda i la més important: no es pot «aprovar» amb dos nodes, perquè sense quòrum el cervell dividit és inevitable. El que es fa és documentar el comportament observat i el procediment de recuperació, perquè el dia que passi ningú no improvisi.
L'enginyeria del caos porta això més lluny: injectar fallades de forma contínua i automatitzada en producció, amb la lògica que si les fallades passen cada dia, el sistema està obligat a tolerar-les i l'equip obligat a saber respondre. Per a Tramontana és desproporcionat; el principi —provoca les fallades tu, en horari laboral, en lloc d'esperar-les— és aplicable a qualsevol escala, i encaixa amb el simulacre semestral que vas proposar a 07-06.
Errors Comuns i Consells
- Posar un balancejador i creure que ja hi ha alta disponibilitat. Només has mogut el punt únic de fallada. El balancejador necessita la seva pròpia redundància.
- Configurar VRRP sense
track_script. Un node ambkeepalivedviu i HAProxy mort reté la IP virtual, i el servei queda caigut amb tots dos balancejadors «funcionant». - Commutació automàtica de base de dades amb dos nodes. Sense quòrum, una partició de xarxa produeix dos primaris i dades divergents. Manual, fins que hi hagi tres nodes.
- Desactivar la delimitació «temporalment». És la configuració que provoca la corrupció de dades el dia de la fallada. Un clúster sense delimitació no és fiable.
- Comprovacions de salut superficials. Un
/salutque només diu «estic viu» fa que el balancejador enviï trànsit a un node que no pot atendre res. - Comprovacions de salut massa profundes. Una fallada de la base de dades marca tots els nodes com a caiguts, quan haurien pogut servir alguna cosa.
- Treure un node a la primera fallada. Un pic transitori treu el node, la càrrega es concentra en els altres, i cauen en cascada.
fall 3islowstart. - Oblidar que els temporitzadors s'executen a tots els nodes. Dues còpies de seguretat simultànies, dues purgues. El
flockés local i no protegeix. - Sessions persistents com a solució permanent. Emmascara el problema real, desequilibra la càrrega, i quan un node cau perd totes les seves sessions.
- Creure que la replicació substitueix les còpies. Un esborrat accidental es replica en mil·lisegons. La redundància protegeix de la fallada de maquinari, no de l'error humà.
- NFS sense redundància per donar alta disponibilitat. Afegeixes un punt únic de fallada del qual ara depenen dos servidors.
- No provar les fallades. Un sistema d'alta disponibilitat no provat és una arquitectura que et penses que funciona.
- Consell de mètode. Abans de dissenyar res, fixa l'objectiu de disponibilitat i mesura l'actual. Sense aquells dos números, qualsevol inversió és una intuïció disfressada d'arquitectura.
Exercicis
Exercici 1
Dissenya l'extrem /salut que ha d'exposar l'aplicació perquè HAProxy prengui bones decisions. Especifica què comprova, què no, quins codis retorna i amb quina latència, i explica com es relaciona amb revisio_salut.sh.
Exercici 2
Escriu el rol d'Ansible que desplega la configuració d'HAProxy i keepalived als dos balancejadors, tenint en compte que la configuració difereix entre ells i que aplicar-la malament deixa el servei sense IP.
Exercici 3
La Marta pregunta directament: «Necessitem alta disponibilitat?». Redacta la resposta amb l'anàlisi completa i una recomanació clara.
Solucions
Solució 1
Disseny de l'extrem /salut:
| Comprova | Com | Per què |
|---|---|---|
| El procés respon | Que la petició arribi | Trivial però necessari |
| Connexió a PostgreSQL | SELECT 1 amb temps límit d'1 s |
Sense base de dades no pot atendre res |
| Grup de connexions | Que n'hi hagi almenys una de lliure | Amb el grup exhaurit, les peticions s'encuen |
| Magatzem de fitxers | Escriptura d'un byte cada 30 s (en memòria cau) | Sense ell fallen les pujades, però no les consultes |
Espai a /var/log |
Llindar del 95 % (en memòria cau) | Sense lloc per als registres, es perd la traçabilitat |
| NO comprova | Per què |
|---|---|
| Consultes de negoci reals | Cares; un problema de dades tombaria tots els nodes |
| Serveis externs (correu, pagaments) | Una fallada aliena trauria tot el servei del repartiment |
| Estat d'altres nodes | Cada node informa només de si mateix |
| Mètriques de rendiment | Això és monitoració, no salut |
Els tres estats i la seva resposta:
// 200 OK — estat 0, correcte
{
"estat": "ok",
"versio": "3.2.1",
"comprovacions": {
"bd": {"ok": true, "latencia_ms": 2},
"pool": {"ok": true, "lliures": 68, "total": 80},
"magatzem": {"ok": true},
"disc_logs": {"ok": true, "us_pct": 34}
}
}// 200 OK amb capcalera X-Salut-Avis: degradat — estat 1
// CONTINUA rebent transit: pot atendre consultes
{
"estat": "degradat",
"avisos": ["magatzem_no_disponible"],
"comprovacions": {
"bd": {"ok": true, "latencia_ms": 3},
"pool": {"ok": true, "lliures": 12, "total": 80},
"magatzem": {"ok": false, "error": "timeout"},
"disc_logs": {"ok": true, "us_pct": 34}
}
}// 503 Service Unavailable — estat 2, critic
// NO rep transit
{
"estat": "critic",
"errors": ["bd_inaccessible"],
"comprovacions": {
"bd": {"ok": false, "error": "connection refused"}
}
}La relació amb revisio_salut.sh és la part conceptual de l'exercici. Tots dos responen a la mateixa pregunta des de costats diferents, i aquella complementarietat cal aprofitar-la en lloc de duplicar feina:
revisio_salut.sh |
/salut |
|
|---|---|---|
| Qui pregunta | L'administrador o un temporitzador | HAProxy, cada 3 s |
| Des d'on | Fora del procés, a la màquina | Des de dins del procés |
| Veu l'estat intern | No | Sí: grup de connexions, memòries cau |
| Freqüència | Minuts | Segons |
| Cost acceptable | Alt | Molt baix |
| Codis | 0 / 1 / 2 | 200 / 200+capçalera / 503 |
El correcte és que revisio_salut.sh consumeixi /salut en lloc de reimplementar les comprovacions:
# Fragment de revisio_salut.sh, adaptat
comprovar_aplicacio() {
local resposta codi estat
resposta="$(curl -s -m 5 -w '\n%{http_code}' \
"http://127.0.0.1:${TRAMONTANA_PORT}/salut")" || {
error "l aplicacio no respon"
return 2
}
codi="$(tail -1 <<<"$resposta")"
estat="$(sed '$d' <<<"$resposta" | jq -r '.estat')"
case "$estat" in
ok) log "aplicacio correcta"; return 0 ;;
degradat) error "degradada: $(sed '$d' <<<"$resposta" | jq -r '.avisos|join(", ")')"
return 1 ;;
critic) error "critica: $(sed '$d' <<<"$resposta" | jq -r '.errors|join(", ")')"
return 2 ;;
*) error "estat desconegut (HTTP $codi)"; return 2 ;;
esac
}Quatre decisions de disseny que cal justificar:
- L'estat degradat retorna 200, no 503. És contraintuïtiu i és correcte: si el magatzem de fitxers falla, el node pot continuar atenent consultes de disponibilitat, que són la majoria del trànsit. Treure'l del repartiment reduiria la capacitat sense guanyar res. La capçalera permet que la monitoració ho detecti sense que el balancejador actuï.
- Latència acotada per disseny. Amb 3 segons d'interval i dos nodes, són 40 peticions per minut. L'objectiu és menys de 50 ms, i d'aquí que les comprovacions cares estiguin a la memòria cau 30 segons.
- Sense autenticació, però restringit per xarxa. Ha de ser accessible sense credencials perquè HAProxy el consulti, i no ha d'estar exposat a Internet: el detall del JSON és informació útil per a un atacant. Es bloqueja al frontend:
http-request deny if { path_beg /salut } !{ src 10.0.2.0/24 } - Sense efectes secundaris i sense blocatge. Un
/salutque escriu a la base de dades o pren un blocatge pot provocar la fallada que pretén detectar. La comprovació d'escriptura del magatzem va a la memòria cau i en segon pla.
I l'advertiment final, que és l'error més comú: /salut no ha de comprovar dependències que no són pròpies d'aquest node. Si comprovés l'estat de la base de dades amb una consulta pesada, un problema de PostgreSQL trauria tots els nodes alhora i el servei desapareixeria del tot, quan podria haver retornat almenys una pàgina d'error decent.
Solució 2
# roles/balanceador/defaults/main.yml
haproxy_vip: 10.0.2.30
haproxy_vip_cidr: 24
haproxy_interficie: enp0s3
haproxy_vrrp_id: 51
haproxy_backends:
- { nom: app1, adreca: 10.0.2.21, port: 8080 }
- { nom: app2, adreca: 10.0.2.22, port: 8080 }
haproxy_check_inter: 3s
haproxy_check_rise: 2
haproxy_check_fall: 3
haproxy_slowstart: 30s# inventari/produccio.yml (fragment)
balancejadors:
hosts:
lb1:
ansible_host: 10.0.2.20
vrrp_estat: MASTER
vrrp_prioritat: 100
lb2:
ansible_host: 10.0.2.21
vrrp_estat: BACKUP
vrrp_prioritat: 90# roles/balanceador/tasks/main.yml
---
# =====================================================================
# PRECAUCIO: una configuracio incorrecta de keepalived pot deixar el
# servei SENSE IP VIRTUAL, es a dir, completament caigut. Aquest rol
# desplega els nodes D UN EN UN i verifica entre ells.
# =====================================================================
- name: "Verificar que no s executa en paral·lel sobre tots dos balancejadors"
ansible.builtin.assert:
that: ansible_play_hosts | length == 1
fail_msg: >-
Aquest rol s ha d executar amb --limit sobre UN balancejador cada
vegada, o amb serial: 1 al play. Configurar tots dos alhora pot
deixar el servei sense IP virtual.
- name: Instal·lar HAProxy i keepalived
ansible.builtin.apt:
name: [haproxy, keepalived, socat]
state: present
tags: [paquets]
# ---------- HAProxy ----------
- name: Crear el directori de certificats
ansible.builtin.file:
path: /etc/haproxy/certs
state: directory
owner: root
group: haproxy
mode: '0750'
tags: [tls]
- name: Instal·lar el certificat combinat per a HAProxy
ansible.builtin.copy:
content: "{{ vault_certificat_pem }}"
dest: /etc/haproxy/certs/reserves.tramontana.example.pem
owner: root
group: haproxy
mode: '0640'
no_log: true # conte la clau privada
notify: Recarregar haproxy
tags: [tls]
- name: Desplegar la configuracio d HAProxy
ansible.builtin.template:
src: haproxy.cfg.j2
dest: /etc/haproxy/haproxy.cfg
owner: root
group: root
mode: '0644'
backup: true
# VALIDA abans d instal·lar: una config trencada impedeix arrencar
validate: 'haproxy -c -f %s'
notify: Recarregar haproxy
tags: [haproxy]
- name: Activar HAProxy
ansible.builtin.systemd_service:
name: haproxy
enabled: true
state: started
tags: [haproxy]
# ---------- keepalived ----------
# El sysctl que permet a HAProxy escoltar en una IP que aquest node
# encara no te (la VIP quan es a l altre). Sense aixo, HAProxy no
# arrenca al node BACKUP.
- name: Permetre bind sobre IP no locals
ansible.posix.sysctl:
name: net.ipv4.ip_nonlocal_bind
value: '1'
sysctl_file: /etc/sysctl.d/71-balanceador.conf
sysctl_set: true
reload: true
tags: [keepalived]
- name: Crear l usuari per als scripts de keepalived
ansible.builtin.user:
name: keepalived_script
system: true
shell: /usr/sbin/nologin
create_home: false
tags: [keepalived]
- name: Instal·lar l script de notificacio de VRRP
ansible.builtin.template:
src: vrrp-notificar.j2
dest: /usr/local/sbin/vrrp-notificar
owner: root
group: root
mode: '0755' # NO escrivible per l usuari de l script
tags: [keepalived]
- name: Desplegar la configuracio de keepalived
ansible.builtin.template:
src: keepalived.conf.j2
dest: /etc/keepalived/keepalived.conf
owner: root
group: root
mode: '0600' # conte la contrasenya de VRRP
backup: true
no_log: true
notify: Reiniciar keepalived
tags: [keepalived]
- name: Activar keepalived
ansible.builtin.systemd_service:
name: keepalived
enabled: true
state: started
tags: [keepalived]
# ---------- Verificacio ----------
- name: Forcar els manejadors abans de verificar
ansible.builtin.meta: flush_handlers
- name: "Verificar | HAProxy respon al socol d administracio"
ansible.builtin.shell:
cmd: >-
set -o pipefail;
echo "show stat" | socat stdio /run/haproxy/admin.sock
| awk -F, '$1=="tramontana_app" && $2=="BACKEND" {print $18}'
register: estat_backend
changed_when: false
failed_when: "'UP' not in estat_backend.stdout"
tags: [verificacio]
- name: "Verificar | Els backends estan sans"
ansible.builtin.shell:
cmd: >-
set -o pipefail;
echo "show stat" | socat stdio /run/haproxy/admin.sock
| awk -F, '$1=="tramontana_app" && $2 ~ /^app/ && $18=="UP"' | wc -l
register: backends_up
changed_when: false
failed_when: backends_up.stdout | int < 1
tags: [verificacio]
- name: "Verificar | La VIP es al MASTER i nomes alla"
ansible.builtin.shell:
cmd: "ip -brief addr show {{ haproxy_interficie }} | grep -c {{ haproxy_vip }} || true"
register: te_vip
changed_when: false
tags: [verificacio]
- name: "Verificar | Coherencia de l estat de la VIP"
ansible.builtin.assert:
that:
- (vrrp_estat == 'MASTER' and te_vip.stdout | int == 1) or
(vrrp_estat == 'BACKUP' and te_vip.stdout | int == 0)
fail_msg: >-
Estat de VIP incoherent a {{ inventory_hostname }}
({{ vrrp_estat }}, VIP present: {{ te_vip.stdout }}).
Revisar abans de continuar amb l altre balancejador.
tags: [verificacio]
- name: "Verificar | El servei respon a traves de la VIP"
ansible.builtin.uri:
url: "https://reserves.tramontana.example/salut"
validate_certs: true
status_code: 200
timeout: 10
delegate_to: localhost
become: false
run_once: true
tags: [verificacio]# roles/balanceador/handlers/main.yml
- name: Recarregar haproxy
ansible.builtin.systemd_service:
name: haproxy
state: reloaded # reload, no restart: no talla les connexions
- name: Reiniciar keepalived
ansible.builtin.systemd_service:
name: keepalived
state: restarted{# roles/balanceador/templates/keepalived.conf.j2 #}
# GENERAT PER ANSIBLE — NO EDITAR A MA
global_defs {
router_id {{ inventory_hostname }}
enable_script_security
script_user keepalived_script
}
vrrp_script comprovar_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight -40
fall 2
rise 2
}
vrrp_instance TRAMONTANA_VIP {
state {{ vrrp_estat }}
interface {{ haproxy_interficie }}
virtual_router_id {{ haproxy_vrrp_id }}
priority {{ vrrp_prioritat }}
advert_int 1
authentication {
auth_type PASS
auth_pass {{ vault_vrrp_pass }}
}
virtual_ipaddress {
{{ haproxy_vip }}/{{ haproxy_vip_cidr }} dev {{ haproxy_interficie }}
}
track_script {
comprovar_haproxy
}
notify_master "/usr/local/sbin/vrrp-notificar master"
notify_backup "/usr/local/sbin/vrrp-notificar backup"
notify_fault "/usr/local/sbin/vrrp-notificar fault"
}I el play que garanteix el desplegament d'un en un:
# balancejadors.yml
- name: Configurar els balancejadors
hosts: balancejadors
become: true
# serial: 1 -> un node cada vegada. Si el primer falla, el segon NO es toca.
serial: 1
# max_fail_percentage: 0 -> avorta davant de la primera fallada
max_fail_percentage: 0
# ORDRE DELIBERAT: primer el BACKUP. Si alguna cosa surt malament, el
# MASTER continua amb la VIP i el servei no s interromp.
order: reverse_inventory
roles:
- balanceadorLes decisions que eviten deixar el servei sense IP:
| Decisió | Contra què protegeix |
|---|---|
serial: 1 + assert d'un sol host |
Configurar tots dos alhora i quedar-se sense cap node amb VIP |
order: reverse_inventory (BACKUP primer) |
Si el rol falla, el MASTER conserva la VIP i el servei continua |
max_fail_percentage: 0 |
Que una fallada a lb2 no impedeixi detectar-se abans de tocar lb1 |
validate: haproxy -c -f %s |
Instal·lar una configuració que impedeix arrencar HAProxy |
meta: flush_handlers abans de verificar |
Verificar l'estat anterior als canvis |
state: reloaded a HAProxy |
restart tallaria totes les connexions en curs |
assert de coherència de la VIP |
Detectar que tots dos o cap tenen la VIP |
uri contra la VIP amb run_once |
Comprovar el servei d'extrem a extrem, no només les peces |
net.ipv4.ip_nonlocal_bind |
HAProxy no arrenca al BACKUP si no pot escoltar a la VIP absent |
no_log: true a certificat i VRRP |
La clau privada i la contrasenya sortirien a la sortida |
Script 0755 propietat de root |
enable_script_security rebutja scripts escrivibles per l'usuari que els executa |
I el que cal afegir al manual d'operació: aquest rol es prova primer a l'entorn de proves amb dos balancejadors virtuals, i en producció s'executa amb --check --diff abans que de debò. La virtual_router_id ha de ser única al segment de xarxa: dos clústers VRRP amb el mateix ID a la mateixa xarxa interfereixen entre si, i és una fallada desconcertant de diagnosticar.
Solució 3
Necessita Tramontana Reserves alta disponibilitat? Per a: Marta Vidal · De: Operacions de sistemes · 18 d'agost de 2026
Resposta breu: alta disponibilitat completa, no. Un primer pas, sí, i amb una relació cost-benefici molt favorable. Recomano dos servidors d'aplicació amb un balancejador, que captura la major part del benefici per una fracció del cost.
Primer, una dada que ens falta. No tenim un objectiu de disponibilitat acordat ni la mesurem. Estimo que som al voltant del 99,5 %, unes 44 hores de caiguda a l'any, comptant reinicis per actualització, desplegaments i algun incident. És una estimació, no una dada, i el primer que proposo és començar a mesurar-ho — costa molt poc i sense aquell número qualsevol inversió és una intuïció.
El que ja hem aconseguit sense gastar res. La disponibilitat depèn de dues coses: amb quina freqüència falla el sistema i quant triga a recuperar-se. En les últimes setmanes hem reduït dràsticament la segona: reconstruir el servidor del tot ha passat de vuit hores a cinquanta minuts, automatitzant-ne la configuració. Això ja ha millorat la disponibilitat sense comprar ni una sola màquina, i és la mena de millora que cal esgotar abans de duplicar infraestructura.
Què costaria estar caiguts. Amb unes 500 reserves mensuals de 313,70 € de mitjana, l'ingrés mitjà és d'uns 218 € per hora. Aquell número cal matisar-lo en tres sentits:
- Les reserves es concentren a la tarda i en cap de setmana. Una caiguda de matinada un dimarts pot no costar res; la mateixa un divendres a la tarda costa bastant més que la mitjana.
- Molts clients que troben el web caigut ho tornen a intentar. El cost real està probablement entre el 20 % i el 50 % de la xifra bruta.
- Hi ha un cost d'imatge, no lineal i difícil de quantificar, sobretot si coincideix amb una campanya.
Amb una estimació prudent (35 %):
Disponibilitat Caiguda a l'any Cost estimat 99,5 % (situació actual) 44 h ~3.340 € 99,8 % 17,5 h ~1.330 € 99,9 % 8,8 h ~670 € Les tres opcions, amb el seu cost real.
Què inclou Inversió Disponibilitat Estalvi anual A. No fer res L'actual 0 ~99,5 % — B. Dos servidors + balancejador 2 màquines, 1 setmana de feina Baixa ~99,8 % ~2.000 € C. Arquitectura completa 4 màquines, 6 setmanes, +4 h/mes Alta ~99,9 % ~2.670 € L'important està a la comparació entre B i C: l'opció C costa diverses vegades més que la B i aporta tot just 670 € addicionals d'estalvi anual. La raó és que els primers nous són barats i els següents s'encareixen molt — i, sobretot, que la majoria de les nostres interrupcions no vénen de fallades de maquinari, sinó de desplegaments, reinicis i manteniment, i això es resol sencer amb l'opció B.
Què ens donaria exactament l'opció B:
- Desplegaments sense interrupció. Avui cada desplegament són uns segons de servei tallat. Amb dos servidors s'actualitza l'un mentre l'altre atén, i el client no nota res.
- Manteniment sense finestra. Reiniciar per un nucli nou deixa de ser una interrupció programada.
- Tolerància a la fallada d'un servidor d'aplicació. El sistema ho detecta en menys de deu segons i deixa d'enviar-li trànsit tot sol.
- Capacitat per a pics, com a benefici secundari.
Què NO ens donaria, i cal dir-ho amb claredat:
- Si cau la base de dades, el servei cau. La promoció de la rèplica seria manual, de 5 a 15 minuts.
- Si cau el balancejador, el servei cau. És l'opció C.
- Si cau la infraestructura del proveïdor, tot cau.
- I res d'això no protegeix contra un esborrat accidental o una corrupció de dades: allò es replicaria a l'instant a tots els nodes. Per a això hi ha les còpies, que continuarien sent igual de necessàries.
Hi ha una condició prèvia que no és tècnica de sistemes. Tramontana Reserves guarda avui dues coses dins de cada servidor: les sessions de les persones que naveguen, i les fotos que es pugen. Amb dos servidors, qui iniciés sessió en un apareixeria desconnectat en ser atès per l'altre, i una foto pujada a l'un no es veuria des de l'altre. Abans de muntar res cal feina de desenvolupament —externalitzar les sessions i desar els fitxers en un magatzem compartit—, unes tres o quatre setmanes. És la part del projecte que cal planificar amb el Luis, i sense ella l'opció B no funciona.
I un advertiment professional. La redundància afegeix peces, i cada peça pot fallar de maneres noves. Un sistema mal muntat és menys fiable que un de simple ben mantingut. Per això no recomano l'opció C: la complexitat que afegeix —replicació, promoció, risc que dos servidors escriguin alhora dades incompatibles— exigeix un nivell de vigilància i de proves que avui no podem sostenir amb la plantilla actual. És millor una arquitectura senzilla que entenem i provem que una de sofisticada que ningú no ha assajat.
Recomanació, en tres passos.
- Aquest mes, cost zero: fixar un objectiu formal de disponibilitat —proposo 99,8 %— i començar a mesurar-la. Amb això podrem decidir amb dades en lloc d'estimacions.
- D'aquí a dos o tres mesos: desenvolupament de les dues condicions prèvies, i desplegament de l'opció B. Començaria amb un sol balancejador, assumint que és un punt únic de fallada però molt menys propens a caure que l'aplicació completa.
- Revisar d'aquí a un any, o abans si canvia alguna cosa: si el volum de reserves creix de manera significativa, si una caiguda ens costa un client important, o si mesurem una disponibilitat pitjor de l'estimada.
I una última cosa. A part de tot l'anterior, vull programar un simulacre semestral: provocar les fallades expressament, en horari laboral, i comprovar que la recuperació funciona. Un sistema de suport que no s'ha provat mai és un sistema del qual no sabem res, i això val tant per a l'arquitectura futura com per al procediment de reconstrucció que ja tenim. El primer, al setembre.
Conclusió
Saps de què es parla quan es parla d'alta disponibilitat, i amb la precisió que el tema exigeix. Els nous tenen números concrets —99,99 % és menys d'un minut de caiguda a la setmana—, la disponibilitat es descompon en MTBF i MTTR, i reduir l'MTTR sol ser molt més barat que augmentar l'MTBF: és exactament el que vas fer a 07-06 en baixar la reconstrucció de vuit hores a cinquanta minuts, sense comprar res. Saps que l'escalat vertical no aporta disponibilitat, que un node passiu que no es fa servir mai és un node del qual no saps res, i sobretot que l'estat és el problema difícil: les sessions, els fitxers pujats i les tasques programades són el que impedeix duplicar un servidor sense més.
Has muntat un balancejador amb HAProxy que reparteix per leastconn, sondeja la salut cada tres segons amb fall 3 i rise 2 asimètrics, torna els nodes amb slowstart per no tombar-los de nou, i permet drenar connexions per desplegar sense tallar — resolent el que va quedar pendent des de 04-07. Li has donat redundància amb keepalived i una IP virtual flotant, amb el track_script que evita la fallada més ximple d'aquesta arquitectura: un balancejador amb keepalived viu i HAProxy mort retenint la IP. I coneixes els dos conceptes sense els quals tot això és teatre: el cervell dividit, que amb dos nodes no es pot evitar perquè no hi ha quòrum possible, i la delimitació, sense la qual un clúster no és fiable per molt que arrenqui.
I has fet l'anàlisi que la Marta feia tres lliçons que demanava, amb la conclusió que no sempre agrada: l'arquitectura completa no compensa. Dos servidors d'aplicació amb un balancejador capturen la major part del benefici per una fracció del cost, perquè la majoria de les interrupcions no vénen de fallades de maquinari sinó de desplegaments i manteniment. Que la resposta professional a «necessitem alta disponibilitat?» pugui ser «una part, i per aquest ordre» és tan important com saber muntar-la. Juntament amb l'advertiment que tanca l'assumpte: la redundància mal feta empitjora la fiabilitat, i un sistema senzill que s'entén i es prova val més que un de sofisticat que ningú no ha assajat.
Amb això es tanca el Mòdul 7, i amb ell la part més avançada del curs. Has après a mirar dins del sistema: la cadena d'arrencada completa i com intervenir-hi quan es trenca, el diagnòstic amb strace, perf i eBPF quan els comptadors no basten, i l'ajust del nucli amb la regla de no tocar el que no s'ha mesurat. I has après a multiplicar-lo: virtualització amb KVM i l'entorn de proves que el curs trobava a faltar des del Mòdul 4, contenidors entesos com el que són —processos aïllats, no màquines petites—, automatització amb Ansible que va convertir la configuració en codi i l'RTO en cinquanta minuts, i per fi la redundància que treu a srv-tramontana la seva condició de punt únic de fallada.
Al Mòdul 8: Projectes Pràctics tot allò s'aplica a construccions completes, de principi a fi. Muntaràs un servidor web amb Nginx com a servidor intermediari invers i TLS davant de l'aplicació — i allà es tanca per fi el xifratge en trànsit que va quedar preparat a 06-05 i que fa des d'aleshores que espera. Configuraràs un servidor de base de dades PostgreSQL en condicions, amb el seu ajust, les seves còpies i la seva replicació. Construiràs un servidor de mitjans domèstic, que és el projecte on comprovar que tot l'après s'aplica igual fora de la feina. Aixecaràs un servidor VPN amb WireGuard per accedir a la xarxa interna sense exposar res. Desplegaràs un clúster de Kubernetes, que és on els contenidors de 07-05 i l'orquestració es troben. I tancaràs amb la posada en producció: la llista de verificació completa, la monitoració amb Prometheus i Grafana que es va esmentar a 05-07, i el procediment d'operació diària d'un sistema que ja no és un laboratori. Actualitza la instantània de la teva VM i ens hi veiem.
Curs de Linux: De Principiant a Administrador de Sistemes
Mòdul 1: Introducció a Linux
- Què és Linux?
- Història de Linux
- Distribucions de Linux
- Instal·lant Linux
- Primer Contacte amb el Sistema
- Estructura del Sistema de Fitxers de Linux
Mòdul 2: Comandes Bàsiques de Linux
- Introducció a la Línia de Comandes
- Obtenir Ajuda i Documentació del Sistema
- Navegant pel Sistema de Fitxers
- Operacions amb Fitxers i Directoris
- Visualització i Edició de Fitxers
- Enllaços Durs i Simbòlics
- Permisos i Propietat dels Fitxers
Mòdul 3: Habilitats Avançades en la Línia de Comandes
- L'Entorn del Shell: Variables, Àlies i Historial
- Ús de Comodins i Expressions Regulars
- Cerca de Fitxers i Contingut: find, locate i grep
- Canonades i Redirecció
- Processament de Text: cut, sort, uniq, sed i awk
- Gestió de Processos
- Programació de Tasques amb Cron
- Comandes de Xarxa
Mòdul 4: Scripting en Shell
- Introducció al Scripting en Shell
- Variables i Tipus de Dades
- Entrada, Sortida i Arguments d'un Script
- Estructures de Control
- Funcions i Biblioteques
- Depuració i Gestió d'Errors
- Scripts de Producció: Bones Pràctiques
Mòdul 5: Administració del Sistema
- Gestió d'Usuaris i Grups
- sudo i Permisos Especials
- Gestió de Paquets
- Gestió de Discs
- systemd i la Gestió de Serveis
- Registres del Sistema: journald i syslog
- Monitoratge del Sistema i Optimització del Rendiment
- Còpies de Seguretat i Restauració
Mòdul 6: Xarxes i Seguretat
- Configuració de Xarxes
- SSH i Accés Remot
- Tallafocs i Seguretat Perimetral
- Sistemes de Detecció d'Intrusions
- Gestió de Secrets i Certificats TLS
- Assegurant Sistemes Linux
Mòdul 7: Temes Avançats
- El Procés d'Arrencada i la Recuperació del Sistema
- Diagnòstic Avançat: strace, perf i eBPF
- Optimització del Nucli de Linux
- Virtualització amb Linux
- Contenidors de Linux i Docker
- Automatització amb Ansible
- Alta Disponibilitat i Balanceig de Càrrega
