Replicar los datos a un segundo centro de datos permite recuperar un servicio en minutos cuando cae el principal, con una pérdida de datos de segundos en lugar de las horas que separan dos copias de seguridad. En esta guía montarás una réplica de MySQL 8.0 con GTID y conexión cifrada entre dos servidores Ubuntu 24.04 situados en ubicaciones distintas, sincronizarás los archivos de la aplicación con rsync y verás cómo promover la réplica si el principal deja de estar disponible.

Elegir la estrategia de replicación

Antes de configurar nada conviene saber qué ofrece cada opción habitual:

EstrategiaTipoPérdida de datos si cae el principalAdecuada entre centros de datos
Replicación asíncrona de MySQLLógica, asíncronaSegundos (lo que no se haya replicado)Sí, tolera latencia alta
Streaming replication de PostgreSQLFísica, asíncrona o síncronaSegundos o cero si es síncronaSí; la síncrona añade la latencia del enlace a cada commit
Galera / Group ReplicationSíncrona, multimaestroCeroSolo con latencias bajas y al menos tres nodos
DRBDBloqueCero en modo síncronoPoco recomendable con latencias de WAN
rsync periódicoArchivosLo cambiado desde la última ejecuciónSí, para archivos que cambian poco

Para la mayoría de aplicaciones web la combinación más sensata entre centros de datos es replicación asíncrona de la base de datos y rsync para los archivos, que es lo que se configura aquí. La replicación síncrona entre ubicaciones lejanas penaliza cada escritura con el tiempo de ida y vuelta del enlace.

Requisitos previos

  • Dos servidores con Ubuntu 24.04 LTS en centros de datos distintos, por ejemplo dos VPS de CubePath en ubicaciones diferentes:
    • Principal (primary_ip), con MySQL y los datos actuales.
    • Réplica (replica_ip), recién instalado.
  • Un usuario no root con privilegios sudo en ambos.
  • MySQL 8.0 del repositorio de Ubuntu (sudo apt install mysql-server) en los dos servidores. Deben tener la misma versión.
  • Conectividad entre ambos en el puerto 3306. Lo ideal es un túnel privado (WireGuard o la red privada del proveedor); si usas IP públicas, restringe el acceso con el cortafuegos como se indica en el paso 1.

En la guía, la base de datos de la aplicación se llama your_database.

Paso 1: Configurar el servidor principal

La replicación basada en GTID identifica cada transacción con un ID global, así que la réplica sabe siempre por dónde va sin tener que indicar archivos y posiciones del registro binario. Eso simplifica mucho la conmutación por error.

En el principal, crea un archivo de configuración propio. Los archivos de /etc/mysql/mysql.conf.d/ se leen en orden alfabético, así que este se aplica después de mysqld.cnf y sus valores prevalecen:

sudo nano /etc/mysql/mysql.conf.d/replication.cnf
[mysqld]
server-id                  = 1
bind-address               = primary_ip
log_bin                    = /var/log/mysql/mysql-bin.log
binlog_expire_logs_seconds = 604800
gtid_mode                  = ON
enforce_gtid_consistency   = ON

bind-address hace que MySQL escuche en la IP que usará la réplica (por defecto Ubuntu solo escucha en 127.0.0.1). binlog_expire_logs_seconds conserva los registros binarios 7 días: si la réplica pasa más tiempo desconectada tendrás que volver a inicializarla.

Reinicia MySQL y comprueba que GTID está activo:

sudo systemctl restart mysql
sudo mysql -e "SELECT @@server_id, @@gtid_mode, @@log_bin;"
+-------------+-------------+-----------+
| @@server_id | @@gtid_mode | @@log_bin |
+-------------+-------------+-----------+
|           1 | ON          |         1 |
+-------------+-------------+-----------+

Permite el acceso al puerto 3306 solo desde la réplica:

sudo ufw allow from replica_ip to any port 3306 proto tcp

Paso 2: Crear el usuario de replicación

Crea un usuario que solo pueda conectarse desde la IP de la réplica, que exija TLS y que tenga únicamente el privilegio de replicación. Sustituye your_strong_password por una contraseña larga y aleatoria:

sudo mysql
CREATE USER 'repl'@'replica_ip' IDENTIFIED BY 'your_strong_password' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'replica_ip';
EXIT;

MySQL 8.0 genera certificados TLS propios al instalarse, así que la conexión irá cifrada sin configuración adicional. Esos certificados son autofirmados: cifran el tráfico pero no autentican al servidor, otra razón para limitar el acceso por IP o usar un túnel privado.

Paso 3: Configurar el servidor réplica

En la réplica, crea el mismo archivo con un server-id distinto:

sudo nano /etc/mysql/mysql.conf.d/replication.cnf
[mysqld]
server-id                  = 2
log_bin                    = /var/log/mysql/mysql-bin.log
binlog_expire_logs_seconds = 604800
gtid_mode                  = ON
enforce_gtid_consistency   = ON
relay_log                  = /var/log/mysql/mysql-relay-bin

La réplica también mantiene su registro binario: cuando la promuevas a principal, podrás colgar de ella una nueva réplica sin más cambios.

Reinicia MySQL:

sudo systemctl restart mysql

Paso 4: Cargar los datos iniciales en la réplica

La réplica necesita partir de una copia exacta de los datos, junto con el conjunto de GTID que ya contiene, para empezar a replicar justo desde ese punto.

En el principal, genera un volcado coherente. --set-gtid-purged=ON añade al volcado la lista de transacciones ya incluidas:

sudo mysqldump --single-transaction --routines --triggers --events \
  --set-gtid-purged=ON --databases your_database > ~/initial.sql

mysqldump mostrará un aviso sobre volcados parciales con GTID. Es esperado: indica que el volcado marca como aplicadas todas las transacciones del servidor, no solo las de your_database, que es justo lo que queremos al inicializar una réplica.

Copia el volcado a la réplica:

rsync -avz --progress ~/initial.sql your_user@replica_ip:~/

En la réplica, vacía el historial de GTID (solo es necesario en una réplica nueva, nunca en un servidor con datos propios) e importa el volcado:

sudo mysql -e "RESET MASTER;"
sudo mysql < ~/initial.sql

Comprueba que la réplica conoce ahora los GTID del principal:

sudo mysql -e "SELECT @@GLOBAL.gtid_purged\G"
*************************** 1. row ***************************
@@GLOBAL.gtid_purged: 5c8f2e1a-7b3d-11ef-9a41-525400a1b2c3:1-1843

Paso 5: Iniciar la replicación

En la réplica, indica de dónde replicar. SOURCE_AUTO_POSITION = 1 usa los GTID para calcular desde dónde continuar y SOURCE_SSL = 1 obliga a cifrar la conexión:

sudo mysql
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST = 'primary_ip',
  SOURCE_USER = 'repl',
  SOURCE_PASSWORD = 'your_strong_password',
  SOURCE_AUTO_POSITION = 1,
  SOURCE_SSL = 1;
START REPLICA;
SET PERSIST read_only = ON;
SET PERSIST super_read_only = ON;
EXIT;

super_read_only impide que nadie, ni siquiera root, escriba por error en la réplica. Los hilos de replicación sí pueden escribir. SET PERSIST guarda estos valores para que sobrevivan a un reinicio.

Comprueba el estado de la replicación:

sudo mysql -e "SHOW REPLICA STATUS\G" | grep -E "Replica_IO_Running|Replica_SQL_Running:|Seconds_Behind_Source|Last_.*Error:"
             Replica_IO_Running: Yes
            Replica_SQL_Running: Yes
          Seconds_Behind_Source: 0
                  Last_IO_Error:
                 Last_SQL_Error:

Los dos hilos deben estar en Yes. Para confirmarlo con datos reales, crea una tabla de prueba en el principal:

sudo mysql -e "CREATE TABLE your_database.repl_test (id INT PRIMARY KEY); INSERT INTO your_database.repl_test VALUES (1);"

Y consúltala en la réplica:

sudo mysql -e "SELECT * FROM your_database.repl_test;"
+----+
| id |
+----+
|  1 |
+----+

Borra la tabla en el principal con DROP TABLE your_database.repl_test;; el borrado también se replicará.

Paso 6: Sincronizar los archivos de la aplicación

La base de datos no lo es todo: archivos subidos por usuarios, configuración y certificados también deben estar en el segundo centro de datos. rsync solo transfiere los cambios, así que puede ejecutarse cada pocos minutos.

En la réplica, da a tu usuario permiso de escritura sobre el directorio de destino:

sudo install -d -o your_user -g www-data -m 755 /var/www

En el principal, genera una clave SSH para root sin frase de paso y muestra la parte pública:

sudo ssh-keygen -t ed25519 -f /root/.ssh/app-sync -N "" -C "app-sync"
sudo cat /root/.ssh/app-sync.pub

Añade esa línea al archivo ~/.ssh/authorized_keys de your_user en la réplica. Después, desde el principal, conéctate una vez como root para aceptar la huella del servidor:

sudo ssh -i /root/.ssh/app-sync your_user@replica_ip true

En la réplica los archivos quedarán a nombre de your_user, legibles por el servidor web. Si tu aplicación necesita otro propietario, ajústalo con chown al promover la réplica. Ahora crea un servicio de systemd en el principal:

sudo nano /etc/systemd/system/app-sync.service
[Unit]
Description=Sincronizar archivos de la aplicación con la réplica
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/rsync -az --delete --bwlimit=20000 -e "ssh -i /root/.ssh/app-sync" /var/www/ your_user@replica_ip:/var/www/

--bwlimit=20000 limita la transferencia a unos 20 MB/s para no saturar el enlace entre centros de datos. --delete borra en la réplica lo que se borre en el principal: revisa bien las rutas antes de activarlo, porque un error en el origen se propaga al destino. Por eso la replicación no sustituye a las copias de seguridad.

Crea un temporizador que lo ejecute cada 5 minutos:

sudo nano /etc/systemd/system/app-sync.timer
[Unit]
Description=Sincronización periódica de archivos con la réplica

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min

[Install]
WantedBy=timers.target

systemd no lanza una nueva ejecución de un servicio oneshot mientras la anterior sigue activa, así que no hace falta un bloqueo adicional. Actívalo y comprueba el resultado:

sudo systemctl daemon-reload
sudo systemctl enable --now app-sync.timer
sudo journalctl -u app-sync.service -n 5

Paso 7: Vigilar el retraso de la réplica

Una réplica que se ha parado sin que nadie lo sepa no protege de nada. Los dos indicadores a vigilar son que ambos hilos estén en Yes y que Seconds_Behind_Source sea bajo.

Crea en la réplica un usuario de solo lectura para tu sistema de monitorización:

CREATE USER 'monitor'@'localhost' IDENTIFIED BY 'your_monitor_password';
GRANT REPLICATION CLIENT ON *.* TO 'monitor'@'localhost';

Como super_read_only está activo en la réplica, crea este usuario en el principal: se replicará solo. Con él, herramientas como el exportador mysqld_exporter de Prometheus o el agente de Zabbix pueden leer SHOW REPLICA STATUS y alertar, por ejemplo, si el retraso supera 60 segundos durante más de 5 minutos o si algún hilo deja de estar en Yes.

Paso 8: Promover la réplica si cae el principal

Cuando el principal no está disponible y has decidido conmutar, el procedimiento es corto. Antes de nada, asegúrate de que el principal antiguo no puede volver a recibir escrituras (apágalo o bloquea el puerto 3306): dos servidores aceptando escrituras a la vez generan datos divergentes muy difíciles de reconciliar.

En la réplica, comprueba hasta dónde llegó la replicación, detenla y elimina su configuración:

sudo mysql
SHOW REPLICA STATUS\G
STOP REPLICA;
RESET REPLICA ALL;
SET PERSIST super_read_only = OFF;
SET PERSIST read_only = OFF;
EXIT;

Después:

  1. Cambia bind-address en /etc/mysql/mysql.conf.d/replication.cnf a la IP por la que se conectará la aplicación y reinicia MySQL.
  2. Crea el usuario de la aplicación para la nueva IP de origen si es distinta y abre el puerto 3306 en UFW para los servidores de aplicación.
  3. Apunta la aplicación al nuevo servidor, cambiando la cadena de conexión o el registro DNS que usa.
  4. Detén el temporizador app-sync.timer en el antiguo principal cuando vuelva, para que no sobrescriba archivos nuevos.

Cuando el antiguo principal se recupere, no lo reincorpores tal cual: puede tener transacciones que nunca llegaron a la réplica. Lo seguro es reinstalarlo como nueva réplica del servidor promovido, repitiendo los pasos 3 a 5 en sentido inverso.

Solución de problemas

  • Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection: la réplica se conecta sin TLS. Asegúrate de haber usado SOURCE_SSL = 1 en CHANGE REPLICATION SOURCE TO.
  • Replica_IO_Running: Connecting: la réplica no llega al principal. Comprueba bind-address, la regla de UFW y que puedes conectar con nc -zv primary_ip 3306 desde la réplica.
  • Error 1236, Cannot replicate because the source purged required binary logs: la réplica estuvo desconectada más tiempo del que se conservan los registros binarios. Vuelve a inicializarla desde el paso 4.
  • Seconds_Behind_Source crece sin parar: la réplica no da abasto aplicando cambios. Revisa el disco y la CPU de la réplica y busca consultas masivas en el principal (por ejemplo UPDATE sin WHERE sobre tablas grandes).

Conclusión

Tienes una réplica de MySQL cifrada en otro centro de datos, los archivos de la aplicación sincronizados cada 5 minutos y un procedimiento claro para promover la réplica. Esta configuración reduce el RPO a segundos, pero no protege frente a errores lógicos como un borrado accidental, que se replica al instante: mantén también copias de seguridad con retención. Como siguientes pasos, ensaya la promoción en un entorno de pruebas, automatiza el cambio de tráfico con conmutación por error de DNS y documenta el procedimiento en tu plan de recuperación ante desastres.