La replicación de MySQL copia de forma continua los cambios de un servidor principal (el origen, antes llamado maestro) a uno o varios servidores réplica (antes llamados esclavos). Sirve para repartir las lecturas, tener un servidor listo si el principal falla y hacer copias de seguridad sin cargar el origen. En este tutorial configurarás una réplica de MySQL 8.0 en Ubuntu 24.04 usando GTID, con el tráfico de replicación cifrado, y comprobarás que los cambios llegan a la réplica.

Desde MySQL 8.0.22 los comandos usan la terminología SOURCE/REPLICA (START REPLICA, SHOW REPLICA STATUS). Esta guía usa la sintaxis actual.

Requisitos previos

Para seguir esta guía necesitas:

  • Dos servidores con Ubuntu 24.04 LTS, por ejemplo dos VPS de CubePath, idealmente conectados por una red privada. En los ejemplos:
    • Origen: db1, IP privada 10.0.0.2.
    • Réplica: db2, IP privada 10.0.0.3.
  • Un usuario no root con privilegios sudo en ambos.
  • MySQL Server 8.0 instalado en los dos (sudo apt install mysql-server). La réplica debe tener la misma versión o una más reciente que el origen.
  • UFW activo en ambos servidores.

Sustituye 10.0.0.2, 10.0.0.3 y los nombres de base de datos por los tuyos en todos los comandos.

Paso 1: Configurar el servidor de origen

En db1, abre el archivo de configuración de MySQL que usa Ubuntu:

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

Dentro de la sección [mysqld], cambia bind-address para que MySQL escuche en la IP privada y añade las opciones de replicación:

[mysqld]
bind-address = 10.0.0.2
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_expire_logs_seconds = 604800
gtid_mode = ON
enforce_gtid_consistency = ON

Qué hace cada opción:

  • server-id: identificador único de cada servidor de la topología.
  • log_bin: el registro binario (binlog) guarda los cambios que la réplica descargará. En MySQL 8.0 está activo por defecto; indicar la ruta lo deja explícito.
  • binlog_expire_logs_seconds: borra los binlogs con más de 7 días. Si una réplica se queda desconectada más tiempo, tendrás que volver a cargarla.
  • gtid_mode y enforce_gtid_consistency: cada transacción recibe un identificador global (GTID), y la réplica sabe por sí sola desde dónde continuar sin anotar archivos ni posiciones.

Si ya existe una línea bind-address o server-id en el archivo, modifícala en lugar de duplicarla. Reinicia MySQL y comprueba los valores:

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 puerto 3306 solo desde la réplica:

sudo ufw allow from 10.0.0.3 to any port 3306 proto tcp

Paso 2: Crear el usuario de replicación

La réplica se conecta al origen con un usuario propio que solo tiene el privilegio de replicación. En db1:

sudo mysql

Sustituye your_strong_password por una contraseña larga y aleatoria:

CREATE USER 'replicador'@'10.0.0.3' IDENTIFIED BY 'your_strong_password' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'replicador'@'10.0.0.3';
EXIT;

El privilegio conserva el nombre antiguo REPLICATION SLAVE. REQUIRE SSL obliga a cifrar la conexión; MySQL 8.0 genera certificados autofirmados al instalarse, así que no necesitas crear ninguno.

Desde db2, comprueba que puedes conectarte con ese usuario:

mysql -h 10.0.0.2 -u replicador -p --ssl-mode=REQUIRED -e "SELECT CURRENT_USER();"
+-----------------------+
| CURRENT_USER()        |
+-----------------------+
| [email protected]   |
+-----------------------+

Paso 3: Volcar los datos del origen

La réplica debe empezar con una copia exacta de los datos del origen. Con GTID, mysqldump incluye en el volcado el conjunto de transacciones ya aplicadas, y la réplica continuará justo a partir de ahí.

En db1, vuelca las bases de datos que quieras replicar (en el ejemplo, appdb):

sudo mysqldump --single-transaction --routines --triggers --events \
  --set-gtid-purged=ON --databases appdb > appdb.sql

--single-transaction hace una copia consistente de las tablas InnoDB sin bloquear las escrituras. Para varias bases de datos, enuméralas tras --databases separadas por espacios. Es normal que aparezca este aviso:

Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database.

Copia el volcado a la réplica:

scp appdb.sql [email protected]:~/

Paso 4: Configurar el servidor réplica

En db2, edita el mismo archivo:

sudo nano /etc/mysql/mysql.conf.d/mysqld.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.log
read_only = ON
super_read_only = ON

server-id debe ser distinto del origen. read_only y super_read_only impiden escribir en la réplica por error, incluso a usuarios administradores; el proceso de replicación sí puede escribir. La réplica también mantiene su binlog, lo que te permitirá promocionarla a origen si hace falta.

Reinicia MySQL:

sudo systemctl restart mysql

Paso 5: Cargar los datos en la réplica

Para importar un volcado con GTID, la réplica no debe tener transacciones propias registradas. Una instalación nueva puede tener alguna (por ejemplo, de mysql_secure_installation), así que límpialas. Abre la consola en db2:

sudo mysql
SET GLOBAL super_read_only = OFF;
RESET MASTER;
EXIT;

Importa el volcado:

sudo mysql < ~/appdb.sql

Comprueba que el conjunto de GTID importado coincide con el del origen en el momento del volcado:

sudo mysql -e "SELECT @@GLOBAL.gtid_executed;"
+------------------------------------------------+
| @@GLOBAL.gtid_executed                         |
+------------------------------------------------+
| 3e11fa47-71ca-11e1-9e33-c80aa9429562:1-1542    |
+------------------------------------------------+

El UUID debe ser el del origen, que puedes consultar en db1 con SELECT @@server_uuid;.

Paso 6: Iniciar la replicación

En db2, indica a la réplica dónde está el origen y arranca la replicación:

sudo mysql
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST = '10.0.0.2',
  SOURCE_USER = 'replicador',
  SOURCE_PASSWORD = 'your_strong_password',
  SOURCE_AUTO_POSITION = 1,
  SOURCE_SSL = 1;
START REPLICA;
SET GLOBAL super_read_only = ON;

SOURCE_AUTO_POSITION = 1 hace que la réplica pida al origen las transacciones que le faltan según su gtid_executed, sin indicar archivo ni posición. SOURCE_SSL = 1 cifra la conexión, como exige el usuario creado en el paso 2.

Consulta el estado:

SHOW REPLICA STATUS\G

Los campos importantes son estos:

             Replica_IO_State: Waiting for source to send event
                  Source_Host: 10.0.0.2
           Replica_IO_Running: Yes
          Replica_SQL_Running: Yes
                   Last_Error:
        Seconds_Behind_Source: 0
           Source_SSL_Allowed: Yes
                Auto_Position: 1

Replica_IO_Running (descarga de cambios) y Replica_SQL_Running (aplicación de cambios) deben estar en Yes. Seconds_Behind_Source indica el retraso en segundos.

Paso 7: Probar la replicación

En el origen (db1), crea una tabla e inserta una fila:

sudo mysql appdb -e "CREATE TABLE prueba_replica (id INT PRIMARY KEY, nota VARCHAR(50)); INSERT INTO prueba_replica VALUES (1, 'hola desde db1');"

En la réplica (db2), consulta la tabla:

sudo mysql appdb -e "SELECT * FROM prueba_replica;"
+----+----------------+
| id | nota           |
+----+----------------+
|  1 | hola desde db1 |
+----+----------------+

Comprueba también que la réplica rechaza escrituras:

sudo mysql appdb -e "INSERT INTO prueba_replica VALUES (2, 'escrito en db2');"
ERROR 1290 (HY000) at line 1: The MySQL server is running with the --super-read-only option so it cannot execute this statement

Borra la tabla de prueba en el origen; el borrado también se replicará:

sudo mysql appdb -e "DROP TABLE prueba_replica;"

Para añadir más réplicas, repite los pasos 3 a 6 en cada una con un server-id distinto, un usuario de replicación para su IP y la regla de UFW correspondiente en el origen.

Paso 8: Promocionar la réplica si el origen falla

Si el origen deja de estar disponible, puedes convertir la réplica en el nuevo servidor principal. En db2:

STOP REPLICA;
RESET REPLICA ALL;
SET GLOBAL super_read_only = OFF;
SET GLOBAL read_only = OFF;

Quita también read_only y super_read_only de mysqld.cnf para que el cambio sobreviva a un reinicio, y apunta tus aplicaciones a 10.0.0.3. El servidor antiguo no debe volver a recibir escrituras: reconstrúyelo como réplica del nuevo origen siguiendo los pasos 3 a 6.

Solución de problemas

Replica_IO_Running: Connecting con error 2003 o 2005: la réplica no alcanza el puerto 3306 del origen. Comprueba que bind-address en el origen es la IP privada, que MySQL escucha en ella (sudo ss -tlnp | grep 3306) y que existe la regla de UFW para la IP de la réplica.

Error 1045 Access denied for user 'replicador': la contraseña no coincide o el usuario se creó para otra IP. Revisa en el origen con SELECT user, host FROM mysql.user WHERE user = 'replicador';.

Error 2061 Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection: falta SOURCE_SSL = 1 en CHANGE REPLICATION SOURCE TO. Ejecuta STOP REPLICA;, repite el comando con esa opción y START REPLICA;.

Error 1236 Cannot replicate because the source purged required binary logs: la réplica estuvo parada más tiempo que la retención de binlogs. Vuelve a cargarla desde un volcado nuevo (pasos 3 a 6).

Replica_SQL_Running: No con error 1062 (clave duplicada): alguien escribió directamente en la réplica y los datos divergen. No omitas errores con replica_skip_errors: vuelve a cargar la réplica desde un volcado para garantizar que es idéntica al origen.

Conclusión

Tienes una réplica de MySQL 8.0 que recibe los cambios del origen en tiempo real mediante GTID, sobre una conexión cifrada y protegida contra escrituras accidentales. Como siguientes pasos, puedes enviar las consultas de solo lectura de tu aplicación a la réplica, hacer las copias de seguridad desde ella para no cargar el origen y vigilar Seconds_Behind_Source con tu sistema de monitorización.