resources/views/pages/frontend/docs/mariadb-galera-cluster.md
Última modificación: 24 Oct, 2026

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ándar systemctl 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:

  1. En Node-DB1, purgar procesos huérfanos:
    sudo killall -9 mariadbd mysqld_safe 2>/dev/null
    sudo rm -f /run/mysqld/mysqld.sock
    
  2. 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
    
  3. Reinicializar el clúster:
    sudo galera_new_cluster
    
  4. 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)

  1. Identificar el nodo aislado ejecutando en los 3 servidores:
    mariadb -u root -pMiPassWord -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
    
  2. En el nodo aislado (por ejemplo, DB1):
    sudo systemctl stop mariadb
    sudo killall -9 mariadbd 2>/dev/null
    sudo systemctl start mariadb
    
  3. Al reiniciar normalmente (sin --wsrep-new-cluster), el nodo contactará las IPs de gcomm:// 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
Edita este documento en >> mariadb-galera-cluster.md
← Volver a la Landing Page