Configuración avanzada de failover con PostgreSQL y repmgr
Introducción
La alta disponibilidad (HA) en bases de datos PostgreSQL no es un lujo, es un requisito operativo en entornos de producción. Cuando el tiempo de inactividad se mide en segundos y las pérdidas económicas en miles de euros, una configuración de failover automatizada y fiable se convierte en la columna vertebral de la infraestructura. repmgr (Replication Manager) es la herramienta de referencia para gestionar clústeres de replicación lógica y failover en PostgreSQL, ofreciendo un control granular sobre la promoción de réplicas, la detección de fallos y la reconfiguración del clúster.
Este artículo profundiza en la configuración avanzada de un clúster de failover con PostgreSQL 16 y repmgr 5.4. No cubriremos la instalación básica de PostgreSQL ni los fundamentos de la replicación física; asumimos que ya tienes un par de servidores PostgreSQL sincronizados mediante streaming replication. Nos centraremos en la arquitectura, la configuración de repmgr, la automatización del failover y las estrategias de recuperación post-fallo.
Arquitectura del Clúster y Planificación
Antes de teclear un solo comando, debemos definir la topología del clúster. Para este artículo, utilizaremos una configuración de tres nodos:
- Nodo Primario (PG1):
192.168.1.10- El servidor de lectura/escritura inicial. - Nodo Standby (PG2):
192.168.1.20- Réplica sincrónica o asíncrona. - Nodo Witness (PG3):
192.168.1.30- Un nodo ligero que actúa como testigo para el quórum de failover y prevención de split-brain.
¿Por qué un nodo Witness?
En un clúster de dos nodos (primario + standby), si el primario falla, el standby puede promocionarse sin problemas. El problema surge si la red falla, no el servidor. En ese escenario, ambos nodos pueden creer que el otro ha muerto e intentar promocionarse, creando dos primarios (split-brain). El nodo witness actúa como un juez imparcial: solo se concede la promoción si el witness puede confirmar que el primario original está realmente inaccesible. Esto requiere un mínimo de tres nodos para formar quórum.
Requisitos de Red y Sistema
- Resolución de nombres: Todos los nodos deben poder resolverse por nombre. Usaremos
/etc/hostspara simplicidad. - Conectividad SSH sin contraseña:
repmgrutiliza SSH para ejecutar comandos remotos durante el failover. La clave SSH del usuariopostgresen cada nodo debe estar en losauthorized_keysde los demás. - Puertos abiertos: PostgreSQL (5432), repmgr (por defecto usa SSH, pero puede configurarse un puerto dedicado).
- Sincronización de tiempo (NTP): Esencial para la consistencia de los logs y la detección de fallos.
Instalación y Configuración de repmgr
repmgr está disponible en los repositorios oficiales de PostgreSQL (PGDG). Asumimos que tienes PostgreSQL 16 instalado desde PGDG.
Instalación en Todos los Nodos
# En PG1, PG2 y PG3
sudo apt-get install -y postgresql-16-repmgr
Configuración de repmgr.conf
El archivo de configuración principal de repmgr es /etc/repmgr/16/repmgr.conf. Debe ser idéntico en todos los nodos, excepto por el parámetro node_id y node_name.
Configuración Base (PG1 - Primario)
# /etc/repmgr/16/repmgr.conf
node_id=1
node_name='pg1'
conninfo='host=192.168.1.10 port=5432 user=repmgr dbname=repmgr'
data_directory='/var/lib/postgresql/16/main'
# Configuración de failover
failover=automatic
promote_command='/usr/bin/repmgr standby promote -f /etc/repmgr/16/repmgr.conf --log-to-file'
follow_command='/usr/bin/repmgr standby follow -f /etc/repmgr/16/repmgr.conf --log-to-file --upstream-node-id=%n'
# Monitoring
monitoring_history=yes
monitor_interval_secs=2
connection_check_type=ping
reconnect_attempts=6
reconnect_interval=10
# Logging
log_level=INFO
log_file='/var/log/postgresql/repmgr.log'
Explicación de parámetros clave:
conninfo: Cadena de conexión a la base de datosrepmgr. Esta base de datos se crea en el primario y se replica a los standbys.repmgralmacena aquí el estado del clúster.promote_command: Script que se ejecuta en el nodo que se va a promocionar.repmgr standby promotees el comando estándar.follow_command: Script que se ejecuta en los standbys restantes para que sigan al nuevo primario. El token%nse reemplaza con elnode_iddel nuevo upstream.monitor_interval_secs: Cada 2 segundos,repmgrd(el daemon de monitoreo) verifica la salud del primario.connection_check_type=ping: Usapg_isreadypara comprobar la conexión. Otras opciones:query(ejecuta una consulta) oconnect(solo intenta conectar).reconnect_attemptseinterval: Número de reintentos y tiempo entre ellos antes de declarar un nodo como caído.
Configuración en PG2 (Standby)
# /etc/repmgr/16/repmgr.conf
node_id=2
node_name='pg2'
conninfo='host=192.168.1.20 port=5432 user=repmgr dbname=repmgr'
data_directory='/var/lib/postgresql/16/main'
failover=automatic
promote_command='/usr/bin/repmgr standby promote -f /etc/repmgr/16/repmgr.conf --log-to-file'
follow_command='/usr/bin/repmgr standby follow -f /etc/repmgr/16/repmgr.conf --log-to-file --upstream-node-id=%n'
monitoring_history=yes
monitor_interval_secs=2
connection_check_type=ping
reconnect_attempts=6
reconnect_interval=10
log_level=INFO
log_file='/var/log/postgresql/repmgr.log'
Configuración en PG3 (Witness)
# /etc/repmgr/16/repmgr.conf
node_id=3
node_name='pg3'
conninfo='host=192.168.1.30 port=5432 user=repmgr dbname=repmgr'
data_directory='/var/lib/postgresql/16/main'
failover=automatic
promote_command='/usr/bin/repmgr standby promote -f /etc/repmgr/16/repmgr.conf --log-to-file'
follow_command='/usr/bin/repmgr standby follow -f /etc/repmgr/16/repmgr.conf --log-to-file --upstream-node-id=%n'
monitoring_history=yes
monitor_interval_secs=2
connection_check_type=ping
reconnect_attempts=6
reconnect_interval=10
log_level=INFO
log_file='/var/log/postgresql/repmgr.log'
Nota importante: El nodo witness no necesita una instancia de PostgreSQL completa.
repmgrpuede funcionar con un directorio de datos vacío. Sin embargo, para simplificar, asumiremos que PostgreSQL está instalado y la base de datosrepmgres accesible desde todos los nodos.
Creación de la Base de Datos y el Usuario repmgr
En el nodo primario (PG1), ejecuta:
sudo -u postgres psql -c "CREATE USER repmgr WITH REPLICATION SUPERUSER LOGIN ENCRYPTED PASSWORD 'tu_contraseña_segura';"
sudo -u postgres psql -c "CREATE DATABASE repmgr OWNER repmgr;"
Edita pg_hba.conf para permitir conexiones desde todos los nodos:
# /etc/postgresql/16/main/pg_hba.conf
host repmgr repmgr 192.168.1.0/24 md5
host replication repmgr 192.168.1.0/24 md5
Recarga la configuración de PostgreSQL:
sudo -u postgres psql -c "SELECT pg_reload_conf();"
Registro del Primario y Clonación del Standby
En el primario (PG1), registra el nodo:
sudo -u postgres repmgr -f /etc/repmgr/16/repmgr.conf primary register
Ahora, en el standby (PG2), clona los datos del primario:
sudo -u postgres repmgr -h 192.168.1.10 -U repmgr -d repmgr -f /etc/repmgr/16/repmgr.conf standby clone --dry-run
# Si el dry-run es exitoso, ejecuta sin --dry-run
sudo -u postgres repmgr -h 192.168.1.10 -U repmgr -d repmgr -f /etc/repmgr/16/repmgr.conf standby clone
Inicia PostgreSQL en PG2 y registra el standby:
sudo systemctl start postgresql@16-main
sudo -u postgres repmgr -f /etc/repmgr/16/repmgr.conf standby register
Registro del Nodo Witness
En PG3, registra el witness:
sudo -u postgres repmgr -f /etc/repmgr/16/repmgr.conf witness register -h 192.168.1.10 -U repmgr -d repmgr
Verificación del Estado del Clúster
En cualquier nodo, ejecuta:
sudo -u postgres repmgr -f /etc/repmgr/16/repmgr.conf cluster show
La salida debería ser similar a:
ID | Name | Role | Status | Upstream | Location | Priority | Timeline | Connection string
----+------+---------+-----------+----------+----------+----------+----------+------------------------------------------
1 | pg1 | primary | * running | | default | 100 | 1 | host=192.168.1.10 port=5432 user=repmgr dbname=repmgr
2 | pg2 | standby | running | pg1 | default | 100 | 1 | host=192.168.1.20 port=5432 user=repmgr dbname=repmgr
3 | pg3 | witness | * running | pg1 | default | 0 | | host=192.168.1.30 port=5432 user=repmgr dbname=repmgr
Automatización del Failover con repmgrd
repmgrd es el daemon de monitoreo que detecta fallos y ejecuta el failover automático. Debe ejecutarse en todos los nodos del clúster (incluyendo el witness).
Inicio del Daemon en Todos los Nodos
sudo systemctl enable repmgrd
sudo systemctl start repmgrd
Mecanismo de Detección de Fallos
repmgrd utiliza un sistema de votación. Cada nodo monitorea al primario. Si un standby detecta que el primario no responde, intenta contactar al witness y a otros standbys. Si la mayoría (quórum) está de acuerdo en que el primario está caído, se inicia el proceso de failover.
Parámetros de failover en repmgr.conf:
failover=automatic: Habilita la promoción automática.priority: (Se define por nodo) Controla el orden de promoción. El standby con mayor prioridad (número más bajo) será promovido primero. Si falla, se prueba el siguiente.replication_type=synchronous: (Opcional) Si se configura replicación síncrona, el failover es más seguro, pero impacta en la latencia de escritura.
Configuración de Prioridades
En repmgr.conf de PG2, podemos añadir:
priority=50
En un hipotético PG4 (otro standby), podríamos poner priority=100. En un failover, repmgr intentará promocionar el nodo con la prioridad más baja (50 en este caso). Si ese nodo no está disponible, promocionará el siguiente (100).
El Proceso de Failover Paso a Paso
- Detección:
repmgrden PG2 y PG3 detectan que PG1 no responde después dereconnect_attempts(6) intentos cadareconnect_interval(10 segundos). - Votación: PG2 contacta a PG3 (witness). PG3 confirma que tampoco puede contactar a PG1. Se forma quórum (2 de 3 nodos).
- Promoción:
repmgrden PG2 ejecutapromote_command(repmgr standby promote). PG2 se convierte en el nuevo primario. - Notificación:
repmgractualiza el estado del clúster en la base de datosrepmgr. - Seguimiento: PG3 (witness) ejecuta
follow_commandpara apuntar al nuevo primario (PG2). - Reconfiguración de aplicaciones: Este paso NO lo maneja
repmgr. Debes tener un mecanismo externo (HAProxy, pgpool-II, o un script personalizado) para redirigir las conexiones de las aplicaciones al nuevo primario.
Estrategias Avanzadas de Failover y Recuperación
Failover Manual (Planificado)
Para mantenimiento, puedes forzar un failover manual sin tiempo de inactividad:
# En el nodo a promocionar (PG2)
sudo -u postgres repmgr standby switchover -f /etc/repmgr/16/repmgr.conf
switchover realiza una conmutación controlada: detiene la replicación, promociona PG2 y reconfigura PG1 para que sea standby de PG2. Es una operación segura y sin pérdida de datos.
Recuperación Post-Failover (Reincorporación del Antiguo Primario)
Una vez que el antiguo primario (PG1) se recupera (hardware reparado, red restablecida), no puede simplemente unirse al clúster como standby porque su timeline (historial de cambios) es diferente al del nuevo primario (PG2). Debe ser "re-clonado".
Procedimiento:
-
Detén PostgreSQL en PG1:
sudo systemctl stop postgresql@16-main -
Limpia los datos antiguos (¡CUIDADO!):
sudo rm -rf /var/lib/postgresql/16/main/* -
Clona desde el nuevo primario (PG2):
sudo -u postgres repmgr -h 192.168.1.20 -U repmgr -d repmgr -f /etc/repmgr/16/repmgr.conf standby clone -
Inicia PostgreSQL y registra el nodo:
sudo systemctl start postgresql@16-main sudo -u postgres repmgr -f /etc/repmgr/16/repmgr.conf standby register --forceLa opción
--forcees necesaria porquerepmgrdetectará que el nodo ya existía en el clúster con un ID diferente.
Gestión de Split-Brain (Caso Extremo)
Un split-brain ocurre cuando la comunicación de red se pierde, pero ambos nodos permanecen activos. Si se promociona un standby mientras el primario original sigue vivo, tendremos dos primarios. repmgr intenta prevenirlo con el witness, pero no es infalible.
Detección: Ejecuta repmgr cluster show en ambos nodos. Si ambos se muestran como primary, hay split-brain.
Resolución:
- Identifica el primario "real": El que tiene los datos más recientes (timeline más alto) o el que las aplicaciones están usando.
- Degrada el otro primario: En el nodo que debe ser standby, ejecuta:
Esto forzará a esesudo -u postgres repmgr standby follow -f /etc/repmgr/16/repmgr.conf --upstream-node-id=<ID_del_primario_real> --force
