Replicar servidores dentro de un mismo centro de datos te protege de un fallo de hardware, pero no de un corte de red, de energía o de un incidente que afecte a toda la ubicación. Una arquitectura en varias regiones mantiene una copia viva de tu servicio en otra ubicación geográfica, lista para recibir el tráfico. En esta guía verás los patrones posibles y sus límites reales, y montarás el más habitual y robusto para equipos pequeños: una arquitectura activa-pasiva entre dos regiones, con un túnel WireGuard, replicación en streaming de PostgreSQL 16 sobre Ubuntu 24.04 y conmutación por DNS con un procedimiento de failover ensayado.
Requisitos previos
Para seguir esta guía necesitas:
- Dos servidores con Ubuntu 24.04 LTS en regiones distintas, por ejemplo dos VPS de CubePath en ubicaciones diferentes. En los ejemplos, la región A (primaria) tiene la IP pública
region_a_ipy la región B (secundaria),region_b_ip. - Un usuario no root con privilegios
sudoen ambos. - PostgreSQL 16 instalado en los dos servidores (
sudo apt install postgresql). - Un dominio con DNS gestionado en un proveedor que permita cambiar registros rápidamente, idealmente por API.
Elegir el patrón: activo-pasivo o activo-activo
| Patrón | Cómo funciona | Ventajas | Inconvenientes |
|---|---|---|---|
| Activo-pasivo | Una región atiende todo; la otra recibe réplicas y espera | Sencillo, sin conflictos de escritura, coste bajo | La región pasiva está infrautilizada; el failover lleva minutos |
| Activo-pasivo con lecturas | Como el anterior, pero la región B sirve lecturas | Aprovecha la réplica, menor latencia de lectura para usuarios cercanos | La aplicación debe separar lecturas y escrituras |
| Activo-activo | Ambas regiones aceptan escrituras | Sin tiempo de conmutación, latencia baja para todos | Conflictos de escritura, necesita bases de datos distribuidas o particionar los datos por región |
El activo-activo real es mucho más difícil de lo que parece: dos regiones que aceptan escrituras sobre los mismos datos generan conflictos, y la latencia entre regiones (decenas o cientos de milisegundos) se suma a cada transacción que tenga que coordinarse. Salvo que tu aplicación esté diseñada para ello, empieza por activo-pasivo.
Define también dos objetivos, porque condicionan todas las decisiones:
- RPO (Recovery Point Objective): cuántos datos puedes permitirte perder. Con replicación asíncrona, son los segundos de retraso de la réplica en el momento del fallo.
- RTO (Recovery Time Objective): cuánto puede durar el corte. Con conmutación por DNS, es el tiempo de detección y decisión, más la promoción, más el TTL del registro.
Paso 1: Conectar las regiones con WireGuard
La replicación no debe viajar en claro por Internet ni exponer PostgreSQL públicamente. Un túnel WireGuard entre las dos regiones crea una red privada cifrada: 10.10.0.1 para la región A y 10.10.0.2 para la B.
En ambos servidores, instala WireGuard y genera un par de claves:
sudo apt install wireguard
wg genkey | sudo tee /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key
sudo chmod 600 /etc/wireguard/private.key
El último comando muestra la clave pública; anota la de cada servidor. En la región A, crea la configuración del túnel:
sudo nano /etc/wireguard/wg0.conf
[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = contenido_de_private.key_de_A
[Peer]
PublicKey = clave_publica_de_B
Endpoint = region_b_ip:51820
AllowedIPs = 10.10.0.2/32
PersistentKeepalive = 25
En la región B, crea el mismo archivo con los valores simétricos:
[Interface]
Address = 10.10.0.2/24
ListenPort = 51820
PrivateKey = contenido_de_private.key_de_B
[Peer]
PublicKey = clave_publica_de_A
Endpoint = region_a_ip:51820
AllowedIPs = 10.10.0.1/32
PersistentKeepalive = 25
En ambos, protege el archivo, abre el puerto de WireGuard solo para la otra región y levanta el túnel (en la región B, usa region_a_ip en la regla):
sudo chmod 600 /etc/wireguard/wg0.conf
sudo ufw allow from region_b_ip to any port 51820 proto udp
sudo systemctl enable --now wg-quick@wg0
Comprueba el túnel desde la región A:
sudo wg show wg0 latest-handshakes
ping -c 3 10.10.0.2
El ping debe responder y el tiempo que muestra es la latencia real entre regiones. Anótalo: te dirá si la replicación síncrona es viable (lo verás más adelante).
Paso 2: Preparar el primario en la región A
Crea un usuario dedicado a la replicación. \password pide la contraseña sin que quede en el historial:
sudo -u postgres psql
CREATE ROLE replicator WITH REPLICATION LOGIN;
\password replicator
\q
Configura PostgreSQL para aceptar conexiones de replicación. Crea un archivo en conf.d, que Ubuntu carga automáticamente:
sudo nano /etc/postgresql/16/main/conf.d/replication.conf
listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
max_slot_wal_keep_size = '20GB'
listen_addresses = '*' evita que PostgreSQL falle al arrancar si el túnel aún no está levantado; el acceso real lo limitan pg_hba.conf y el cortafuegos. max_slot_wal_keep_size pone un tope al WAL que el primario retiene para una réplica caída, para que no llene el disco.
Permite la conexión de replicación solo desde la IP del túnel de la región B:
sudo nano /etc/postgresql/16/main/pg_hba.conf
Añade al final:
host replication replicator 10.10.0.2/32 scram-sha-256
Abre el puerto 5432 solo en la interfaz del túnel y reinicia PostgreSQL:
sudo ufw allow in on wg0 from 10.10.0.2 to any port 5432 proto tcp
sudo systemctl restart postgresql@16-main
Paso 3: Crear la réplica en la región B
En la región B, detén PostgreSQL y vacía su directorio de datos, que se reemplazará por una copia del primario:
sudo systemctl stop postgresql@16-main
sudo -u postgres bash -c 'rm -rf /var/lib/postgresql/16/main/*'
Clona el primario con pg_basebackup. -R escribe la configuración de réplica (standby.signal y primary_conninfo), y -C -S crea en el primario un slot de replicación, que garantiza que no borre WAL que la réplica aún no ha recibido:
sudo -u postgres pg_basebackup -h 10.10.0.1 -U replicator -D /var/lib/postgresql/16/main -R -X stream -C -S replica_region_b -P
Introduce la contraseña de replicator cuando la pida. Arranca la réplica:
sudo systemctl start postgresql@16-main
Comprueba en la región B que funciona como réplica:
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"
sudo -u postgres psql -c "SELECT status, sender_host FROM pg_stat_wal_receiver;"
pg_is_in_recovery
-------------------
t
status | sender_host
-----------+-------------
streaming | 10.10.0.1
Y en la región A, mide el retraso de la réplica:
sudo -u postgres psql -c "SELECT client_addr, state, replay_lag FROM pg_stat_replication;"
client_addr | state | replay_lag
-------------+-----------+-----------------
10.10.0.2 | streaming | 00:00:00.041382
replay_lag es, en la práctica, tu RPO en este momento: los datos que perderías si la región A desapareciera ahora.
Replicación síncrona o asíncrona
Por defecto la replicación es asíncrona: el primario confirma cada transacción sin esperar a la réplica. Con synchronous_standby_names puedes hacer que espere, lo que lleva el RPO a cero, pero cada commit tarda al menos un viaje de ida y vuelta entre regiones (el tiempo que midió el ping del paso 1), y si la réplica cae, las escrituras del primario se bloquean. Entre regiones lejanas, lo habitual es mantener la replicación asíncrona y vigilar el retraso.
Paso 4: Replicar el resto del estado
La base de datos no es lo único que necesita la región B para servir la aplicación:
- Código y configuración: despliega la misma versión en ambas regiones desde tu repositorio o pipeline, nunca a mano en una sola.
- Archivos subidos por usuarios: guárdalos en un almacenamiento de objetos compatible con S3 accesible desde ambas regiones, o sincronízalos periódicamente con
rsyncsobre el túnel. - Secretos: las mismas credenciales y claves deben existir en la región B antes de necesitarlas.
La aplicación de la región B puede estar arrancada apuntando a su PostgreSQL local, que es de solo lectura, siempre que no intente escribir. Si no, déjala instalada y detenida.
Paso 5: Preparar la conmutación por DNS
Los usuarios llegan a tu servicio por un nombre, así que el failover consiste en que ese nombre apunte a la región B. Configura el registro A de your_domain apuntando a region_a_ip con un TTL bajo, de 60 segundos. Comprueba el TTL publicado:
dig +noall +answer your_domain A
your_domain. 60 IN A region_a_ip
Hay dos formas de cambiar el registro:
- Manual o por API de tu proveedor DNS, como parte del procedimiento del paso 6. Es lo recomendado cuando solo tienes dos regiones.
- DNS con comprobaciones de salud (servicios de failover o balanceo global de proveedores como Cloudflare o Amazon Route 53), que retiran automáticamente la IP de una región caída. Úsalo para el tráfico, pero no para promocionar la base de datos: lee la siguiente sección.
Algunos clientes y resolutores ignoran TTL bajos y siguen usando la IP antigua durante un tiempo, así que cuenta con unos minutos de cola tras el cambio.
Evitar el split-brain
El mayor riesgo de una arquitectura en dos regiones no es que el failover falle, sino que ocurra cuando no debe. Si el enlace entre regiones se corta, la región B deja de ver a la A, aunque la A siga sirviendo a los usuarios perfectamente. Si en ese momento la B se promociona sola, tendrás dos primarios aceptando escrituras distintas (split-brain), y reconciliar esos datos es manual y doloroso.
Con solo dos nodos no hay forma fiable de distinguir "la otra región ha caído" de "no puedo verla". Por eso:
- Con dos regiones, la promoción debe ser una decisión humana, apoyada en comprobaciones desde un tercer punto (tu monitorización externa, pruebas desde varias ubicaciones).
- Para failover automático necesitas un tercer voto: un clúster de consenso (por ejemplo, etcd con Patroni) con miembros en tres ubicaciones, de modo que solo el lado con mayoría pueda tener el primario.
- Antes de promocionar, aísla el primario antiguo (fencing): detén PostgreSQL o bloquea el tráfico en la región A si todavía es accesible, para que no pueda seguir aceptando escrituras.
Paso 6: Ejecutar un failover
Este es el procedimiento para mover el servicio a la región B. Ensáyalo en una ventana de mantenimiento antes de necesitarlo.
-
Confirma la caída desde fuera: el servicio no responde desde varias ubicaciones y la región A no es accesible, o has decidido moverla de forma planificada.
-
Aísla la región A. Si puedes acceder a ella, detén la aplicación y PostgreSQL:
sudo systemctl stop postgresql@16-main -
Comprueba en la región B hasta dónde ha llegado la réplica, para conocer los datos perdidos:
sudo -u postgres psql -c "SELECT pg_last_xact_replay_timestamp();" -
Promociona la réplica de la región B a primario:
sudo -u postgres psql -c "SELECT pg_promote();"Verifica que ya acepta escrituras;
pg_is_in_recovery()debe devolverf:sudo -u postgres psql -c "SELECT pg_is_in_recovery();" -
Arranca o reconfigura la aplicación de la región B para escribir en su base de datos local, y comprueba que responde:
curl -I -H "Host: your_domain" http://region_b_ip/ -
Cambia el registro A de
your_domainaregion_b_ipen tu proveedor DNS y verifica la propagación:dig +short your_domain A @1.1.1.1
Mide el tiempo desde el paso 1 hasta que dig devuelve la IP nueva: ese es tu RTO real.
Paso 7: Recuperar la región A como réplica
Cuando la región A vuelva, no la arranques como primario: sus datos han divergido de la región B. Reconstrúyela como réplica de la nueva primaria repitiendo los pasos 2 y 3 con los papeles intercambiados: en la región B, añade a pg_hba.conf la entrada de replicación para 10.10.0.1 y abre el puerto 5432 en wg0 para esa IP; en la región A, vacía el directorio de datos y ejecuta pg_basebackup contra 10.10.0.2 con un slot nuevo, por ejemplo replica_region_a.
El slot replica_region_b existía solo en el primario antiguo, así que desaparece al vaciar su directorio de datos. Comprueba en la región B que la nueva réplica está conectada con SELECT client_addr, state, replay_lag FROM pg_stat_replication;.
Para bases de datos grandes, pg_rewind puede resincronizar la región A sin copiarla entera, pero requiere haber activado wal_log_hints = on o las sumas de verificación de datos de antemano.
Si quieres volver a servir desde la región A, haz más adelante un failover planificado siguiendo el paso 6 en sentido inverso.
Solución de problemas
pg_basebackup: error: connection to server ... failed: no pg_hba.conf entry for replication connection. La entrada de pg_hba.conf no coincide con la IP de origen o con el usuario. Comprueba que la conexión llega por el túnel (10.10.0.x) y recarga PostgreSQL tras editar el archivo.
replay_lag crece sin parar. El enlace entre regiones no da abasto o la réplica tiene disco lento. Revisa el ancho de banda del túnel y el uso de disco de la réplica con iostat -x 5 (paquete sysstat).
El disco del primario se llena de WAL. Hay un slot de replicación sin réplica conectada. Lista los slots con SELECT slot_name, active FROM pg_replication_slots; y elimina los inactivos que ya no uses.
wg show no muestra ningún handshake. El puerto UDP 51820 está bloqueado o las claves públicas están intercambiadas. Revisa sudo ufw status en ambos lados y que cada [Peer] lleve la clave pública del otro servidor.
Conclusión
Tienes dos regiones conectadas por un túnel cifrado, una réplica de PostgreSQL que va unos milisegundos por detrás del primario y un procedimiento de failover con un RPO y un RTO medidos. La clave de esta arquitectura no está en la tecnología sino en ensayarla: programa un simulacro de failover cada trimestre y actualiza el procedimiento con lo que aprendas. Como siguientes pasos, añade monitorización externa del retraso de replicación, automatiza el cambio de DNS con la API de tu proveedor y, cuando necesites failover automático, evalúa Patroni con un clúster etcd repartido en tres ubicaciones.
