MariaDB Galera Cluster & HAProxy: Alta Disponibilidad Síncrona
Esta guía detalla el despliegue bare-metal y la operación del clúster multi-maestro síncrono MariaDB 11.4 LTS Galera 4 balanceado por HAProxy 3.0 sobre Ubuntu 24.04.3 LTS (Noble Numbat). Diseñado como la capa de persistencia transaccional de alto rendimiento para el núcleo de Mañuti, garantiza consistencia ACID estricta y tolerancia a fallos sin pérdida de datos (RPO = 0).
1. Topología de Red y Arquitectura de Malla
El despliegue implementa una segmentación estricta en una subred privada local 192.168.1.0/24. Los clientes (aplicativo Laravel y consolas administrativas Navicat) interactúan exclusivamente con el balanceador de carga, mientras que los nodos de base de datos se sincronizan mediante una malla de comunicación cerrada.
flowchart TD
APP["[ Laravel App Tier ]<br/>(192.168.1.X)"]
DBA["[ Estación DBA / Navicat ]<br/>(192.168.1.111)"]
LB["Node-LB (HAProxy)<br/>192.168.1.167"]
STATS(["Puerto 9000 (Stats UI)"])
DB1[("Node-DB1<br/>.165")]
DB2[("Node-DB2<br/>.166")]
DB3[("Node-DB3<br/>.168")]
WSREP{"Malla WSREP<br/>Puertos: 4444 (SST)<br/>4567 (Galera)<br/>4568 (IST)"}
APP -->|"Puerto 3306"| LB
DBA -->|"Puerto 3306"| LB
STATS -.-> LB
LB -->|"Tráfico SQL Balanceado (Puerto 3306)"| DB1
LB -->|"Tráfico SQL Balanceado (Puerto 3306)"| DB2
LB -->|"(Backup / Read)"| DB3
DB1 <--> WSREP
DB2 <--> WSREP
DB3 <--> WSREP
Inventario Técnico de Nodos
| Nodo | IP Privada | Rol / Servicio | Especificación Base |
|---|---|---|---|
| Node-LB | 192.168.1.167 |
Balanceador TCP Layer 4 HAProxy 3.0 | 2 vCPU, 4 GB RAM, 30 GB SSD |
| Node-DB1 | 192.168.1.165 |
MariaDB 11.4 Galera (Líder Inicial Bootstrap) | 4 vCPU, 16 GB RAM, 100 GB NVMe |
| Node-DB2 | 192.168.1.166 |
MariaDB 11.4 Galera (Miembro Síncrono) | 4 vCPU, 16 GB RAM, 100 GB NVMe |
| Node-DB3 | 192.168.1.168 |
MariaDB 11.4 Galera (Miembro Síncrono / Quórum) | 4 vCPU, 16 GB RAM, 100 GB NVMe |
| Navicat Admin | 192.168.1.111 |
Gestión y Monitoreo Directo DBA | IP Estática de Acceso |
2. Hardening de Red y Matriz de Firewall (UFW)
Para evitar la exposición indebida y mitigar vectores de denegación de servicio o colisión, el firewall se configura de forma asimétrica y quirúrgica.
2.1 Configuración en Nodos de Datos (Node-DB1, Node-DB2, Node-DB3)
Ejecutar en los tres servidores de base de datos para restringir el acceso a SQL solo al balanceador y a la IP de administración, abriendo los puertos de replicación exclusivamente entre los miembros del clúster:
# 1. Resetear UFW a estado limpio
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 2. Acceso administrativo SSH
sudo ufw allow 22/tcp
# 3. Acceso SQL exclusivo desde HAProxy y Navicat
sudo ufw allow from 192.168.1.167 to any port 3306 comment 'Solo HAProxy'
sudo ufw allow from 192.168.1.111 to any port 3306 comment 'Solo mi Navicat'
# 4. Malla de comunicación Galera (SST, IST y Certificación)
sudo ufw allow from 192.168.1.165 to any port 4444,4567,4568 proto tcp comment 'Galera Replication DB1'
sudo ufw allow from 192.168.1.166 to any port 4444,4567,4568 proto tcp comment 'Galera Replication DB2'
sudo ufw allow from 192.168.1.168 to any port 4444,4567,4568 proto tcp comment 'Galera Replication DB3'
# 5. Habilitar firewall
sudo ufw enable
sudo ufw reload
Verificación esperada con sudo ufw status numbered:
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 3306 ALLOW IN 192.168.1.167 # Solo HAProxy
[ 3] 3306 ALLOW IN 192.168.1.111 # Solo mi Navicat
[ 4] 4444,4567,4568/tcp ALLOW IN 192.168.1.165 # Galera Replication DB1
[ 5] 4444,4567,4568/tcp ALLOW IN 192.168.1.166 # Galera Replication DB2
[ 6] 4444,4567,4568/tcp ALLOW IN 192.168.1.168 # Galera Replication DB3
2.2 Configuración en el Balanceador (Node-LB - 192.168.1.167)
El balanceador es el único componente expuesto a la red interna completa para recibir peticiones:
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow from 192.168.1.0/24 to any port 3306 comment 'Acceso App y Navicat'
sudo ufw allow 9000/tcp comment 'Estadisticas HAProxy'
sudo ufw enable
sudo ufw reload
3. Preparación del Sistema Operativo (Todos los Nodos)
3.1 Resolución de Nombres Local (/etc/hosts)
Garantiza la comunicación interna incluso ante fallas transitorias de DNS:
sudo tee -a /etc/hosts <<EOF
192.168.1.167 lb-proxy
192.168.1.165 node-db1
192.168.1.166 node-db2
192.168.1.168 node-db3
EOF
3.2 Optimización de Descriptores de Archivos (/etc/security/limits.conf)
Previene el error Too many open files en ráfagas de alta concurrencia:
sudo tee -a /etc/security/limits.conf <<EOF
mysql soft nofile 65535
mysql hard nofile 65535
EOF
4. Despliegue de MariaDB 11.4 LTS y Configuración Galera
4.1 Aprovisionamiento de Binarios en Node-DB1
sudo apt-get update && sudo apt-get install -y apt-transport-https curl software-properties-common
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version="11.4"
sudo apt-get update
sudo apt-get install -y mariadb-server mariadb-client mariadb-backup galera-4
sudo systemctl enable mariadb
sudo systemctl start mariadb
4.2 Credenciales Base y Usuario de Replicación SST (Solo en Node-DB1)
sudo mariadb
-- Configurar autenticación nativa para root
ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('MiPassWord');
-- Crear usuario para transferencia de estado (SST - mariabackup)
CREATE USER 'sstuser'@'localhost' IDENTIFIED BY 's3cr3t_pass';
GRANT RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT ON *.* TO 'sstuser'@'localhost';
-- Permitir acceso de administración desde la subred local
GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.1.%' IDENTIFIED BY 'MiPassWord' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EXIT;
4.3 Configuración de Escucha Global y Archivo Galera
En los 3 nodos de datos, asegurarse de que MariaDB no escuche únicamente en 127.0.0.1:
sudo sed -i 's/bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mariadb.conf.d/50-server.cnf
Crear /etc/mysql/mariadb.conf.d/60-galera.cnf en cada nodo con sus datos respectivos:
En Node-DB1 (192.168.1.165):
[galera]
wsrep_on = ON
wsrep_provider = /usr/lib/galera/libgalera_smm.so
wsrep_cluster_address = "gcomm://192.168.1.165,192.168.1.166,192.168.1.168"
wsrep_cluster_name = "galera_laravel_cluster"
wsrep_sst_method = mariabackup
wsrep_sst_auth = "sstuser:s3cr3t_pass"
wsrep_node_address = "192.168.1.165"
wsrep_node_name = "node-db1"
# Optimización Transaccional ACID Estricta
binlog_format = ROW
default_storage_engine = InnoDB
innodb_autoinc_lock_mode = 2
innodb_flush_log_at_trx_commit = 1
En Node-DB2 (192.168.1.166):
# Idéntico a DB1, modificando únicamente:
wsrep_node_address = "192.168.1.166"
wsrep_node_name = "node-db2"
En Node-DB3 (192.168.1.168):
# Idéntico a DB1, modificando únicamente:
wsrep_node_address = "192.168.1.168"
wsrep_node_name = "node-db3"
5. Protocolo de Bootstrap y Unión de Nodos
Regla de Oro: Únicamente el primer nodo debe ejecutar
galera_new_cluster. Los nodos restantes se unen mediante el servicio estándarsystemctl start mariadb.
Paso 1: Inicialización del Clúster en Node-DB1
sudo systemctl stop mariadb
sudo galera_new_cluster
# Verificar inicio (tamaño 1)
mariadb -u root -pMiPassWord -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
# Retorno: Value = 1
Paso 2: Instalación y Unión de Node-DB2
# 1. Instalar binarios
sudo apt-get update && sudo apt-get install -y apt-transport-https curl software-properties-common
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version="11.4"
sudo apt-get update && sudo apt-get install -y mariadb-server mariadb-client mariadb-backup galera-4
sudo systemctl stop mariadb
# 2. Configurar bind-address y galera.cnf (ver sección 4.3)
# 3. Limpiar directorio para forzar clonación SST vía mariabackup
sudo rm -rf /var/lib/mysql/*
# 4. Iniciar servicio
sudo systemctl start mariadb
# 5. Validar quórum ampliado (debe retornar 2)
mariadb -u root -pMiPassWord -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
Paso 3: Instalación y Unión de Node-DB3
# Repetir los mismos pasos que en Node-DB2 ajustando el galera.cnf a node-db3 (192.168.1.168)
sudo rm -rf /var/lib/mysql/*
sudo systemctl start mariadb
# Validar quórum total (debe retornar 3)
mariadb -u root -pMiPassWord -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
6. Configuración de HAProxy 3.0 (Node-LB - 192.168.1.167)
Para evitar deadlocks por certificación simultánea en escrituras paralelas, la configuración define a Node-DB1 como nodo primario de escritura (active), dejando a Node-DB2 y Node-DB3 en modo backup. Si el nodo primario falla, HAProxy conmuta instantáneamente al siguiente nodo sin interrupción de servicio.
6.1 Instalación
sudo add-apt-repository ppa:vbernat/haproxy-3.0 -y
sudo apt-get update
sudo apt-get install -y haproxy
6.2 Configuración (/etc/haproxy/haproxy.cfg)
global
log /dev/log local0
user haproxy
group haproxy
daemon
defaults
log global
mode tcp
option tcplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
# Frontend MySQL: Punto de acceso único para Mañuti
frontend mysql-cluster
bind *:3306
default_backend galera-nodes
# Backend Galera: Prevención de deadlocks por certificación
backend galera-nodes
mode tcp
balance leastconn
option tcp-check
server node-db1 192.168.1.165:3306 check
server node-db2 192.168.1.166:3306 check backup
server node-db3 192.168.1.168:3306 check backup
# Panel de Estadísticas Web (http://192.168.1.167:9000/haproxy_stats)
listen stats
bind *:9000
mode http
stats enable
stats uri /haproxy_stats
stats refresh 10s
stats auth admin:admin_password
6.3 Validación y Arranque
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl restart haproxy
sudo systemctl enable haproxy
7. Troubleshooting y Recuperación de Desastres
7.1 Síntoma: Error 2002 (111) / MariaDB no levanta tras corte de energía
Ocurre cuando el socket no responde o el clúster quedó en estado inconsistente y los nodos se rehúsan a arrancar por pérdida de consenso.
Procedimiento de Desbloqueo y Forzado de Bootstrap:
- En Node-DB1, purgar procesos huérfanos:
sudo killall -9 mariadbd mysqld_safe 2>/dev/null sudo rm -f /run/mysqld/mysqld.sock - Modificar el archivo de estado de quórum para marcar a este nodo como la fuente de verdad:
sudo sed -i 's/safe_to_bootstrap: 0/safe_to_bootstrap: 1/' /var/lib/mysql/grastate.dat - Reinicializar el clúster:
sudo galera_new_cluster - Reincorporar secuencialmente Node-DB2 y Node-DB3:
sudo systemctl start mariadb
7.2 Síntoma: Clúster Fragmentado (wsrep_cluster_size arroja 2 en unos nodos y 1 en otro)
- Identificar el nodo aislado ejecutando en los 3 servidores:
mariadb -u root -pMiPassWord -e "SHOW STATUS LIKE 'wsrep_cluster_size';" - En el nodo aislado (por ejemplo, DB1):
sudo systemctl stop mariadb sudo killall -9 mariadbd 2>/dev/null sudo systemctl start mariadb - Al reiniciar normalmente (sin
--wsrep-new-cluster), el nodo contactará las IPs degcomm://y sincronizará su estado.
8. Integración en el Aplicativo Mañuti (.env)
Laravel se conecta exclusivamente a través de la IP del balanceador:
DB_CONNECTION=mysql
DB_HOST=192.168.1.167
DB_PORT=3306
DB_DATABASE=manuti_core
DB_USERNAME=root
DB_PASSWORD=MiPassWord