MySQL InnoDB Cluster
En este artículo veremos cómo instalar un clúster MySQL Innodb Community Edition. Este clúster acostumbra a tener tirada en entornos corporativos donde se favorece la posibilidad de obtener soporte de un proveedor conocido. Comentar que, con este mismo motivo, haremos uso del producto "de la casa" como base para el Sistema Operativo: Oracle Linux.
El artículo no tiene el objetivo de cubrir la instalación de Oracle Linux, pero comentar que es muy similar a la instalación de RHEL y en principio los únicos requisitos son que los hosts tengan un nombre de host y dirección IP propia en la misma subred para todos los nodos.
Arquitectura
Oracle ofrece 3 posibilidades para formar un clúster MySQL:
1. MySQL InnoDB Cluster
Se trata de un setup de alta disponibilidad (HA) basado en MySQL Group Replication con failover automático. Puede ser en forma de single-primary o multi-primary. Se gestiona a través de MySQL Shell AdminAPI e incluye MySQL Router para routing automático. Todos los nodos que lo conforman se despliegan en la misma infraestructura.
2. MySQL InnoDB ClusterSet
Es una capa de disaster recovery (DR) que se puede añadir al InnoDB Cluster y que coordina múltiples clústers en diferentes ubicaciones.
3. MySQL InnoDB ReplicaSet
Es una alternativa ligera al InnoDB Cluster que utiliza la clásica asynchronous replication (no Group Replication) con un nodo primario y el resto réplicas. Conviene destacar que en este caso no se dispone de failover automático sino manual.
Nosotros estamos interesados por el InnoDB Cluster, en la forma que se puede ver representada en el siguiente diagrama:

Como podemos ver, se basa en los siguientes componentes:
- MySQL Servers con Group Replication
- MySQL Router
- MySQL Shell (que da acceso al uso de AdminAPI)
Para crearlo, nos harán falta 3 instancias MySQL (de nombre my0[123] y con direcciones IP 192.168.2.8[123]) para que el clúster tenga la capacidad de perder un nodo sin verse afectado.
Setup. Paso a paso
A continuación iremos instalando los componentes necesarios para crear el clúster. Hasta que no se indique lo contrario, todos los pasos de a continuación se deberán realizar en cada uno de los 3 nodos que formarán parte del clúster.
1. Instalación de MySQL Server y dependencias
Este componente es propiamente el servidor de MySQL de toda la vida.
Aunque no es necesaria su consulta, durante este artículo iremos poniendo los enlaces a la documentación que hemos ido revisando al respecto a la hora de realizar este setup:
- 7.4 Deploying a Production InnoDB Cluster
- 6.2 Installing AdminAPI Software Components
- Chapter 2 Installing MySQL
Para realizar la instalación mostraremos los 2 modos principales para llevarla a cabo, y una vez expuestos, cada uno debe escoger el modo que más le convenga:
1.1 Haciendo uso de repositorios YUM
Este método es el más cómodo al estar la paquetería gestionada por el propio repositorio. Nosotros hacemos uso de este, y adicionalmente imaginaremos que queremos instalar una versión concreta (la 8.0):
$ yum update
$ wget https://dev.mysql.com/get/mysql84-community-release-el8-2.noarch.rpm
$ rpm -Uvh mysql84-community-release-el8-2.noarch.rpm
Warning: native mysql package from platform vendor seems to be enabled.
Please consider to disable this before installing packages from repo.mysql.com.
Run: yum module -y disable mysql
$ yum module -y disable mysql
$ yum repolist all | grep mysql
$ dnf config-manager --disable mysql-8.4-lts-community
$ dnf config-manager --disable mysql-tools-8.4-lts-community
$ dnf config-manager --enable mysql80-community
$ dnf config-manager --enable mysql-tools-community
$ yum update
$ yum repolist enabled | grep mysql
mysql-connectors-community MySQL Connectors Community
mysql-tools-community MySQL Tools Community
mysql80-community MySQL 8.0 Community Server
#####################$ yum install mysql80-community # No, esto es un repo, quitar
$ yum install mysql-community-server
$ systemctl start mysqld
$ systemctl status mysqld
Buscamos el password temporal que nos establece la instalación y lo cambiamos (si no igualmente, más adelante nos veremos en la necesidad de hacerlo):
$ grep 'temporary password' /var/log/mysqld.log 2025-11-23T18:02:21.077354Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: r5+JjRwt>-a6 $ mysql -uroot -p mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'MYpass2025!'; mysql> FLUSH PRIVILEGES; mysql> FLUSH TABLES; mysql> quit
1.2 Utilizando paquetes RPM sueltos
Este otro método es útil si tenemos en cuenta que la versión comercial (de pago) funciona interactuando directamente con paquetes sueltos, así que resulta interesante para simular exactamente el mismo escenario que nos encontraríamos si estuviéramos instalando esta versión.
Los paquetes se deben bajar desde aquí. A tener en cuenta el siguiente aviso:
Dependency relationships exist among some of the packages. If you plan to install many of the packages,
you may wish to download the RPM bundle tar file instead, which contains all the RPM packages listed
above, so that you need not download them separately.
In most cases, you need to install the mysql-community-server, mysql-community-client,
mysql-community-client-plugins, mysql-community-libs, mysql-community-icu-data-files,
mysql-community-common, and mysql-community-libs-compat packages to get a functional, standard MySQL
installation. To perform such a standard, basic installation, go to the folder that contains all those
packages (and, preferably, no other RPM packages with similar names), and issue the following command:
$> sudo yum install mysql-community-{server,client,client-plugins,icu-data-files,common,libs}-*
Installation of previous versions of MySQL using older packages might have created a configuration file named /usr/my.cnf. It is highly recommended that you examine the contents of the file and migrate the desired settings inside to the file /etc/my.cnf file, then remove /usr/my.cnf.
$> systemctl start mysqld
Instalamos los paquetes y del mismo modo que antes, tendremos que buscar y cambiar el password temporal que nos pone la instalación.
2. Instalación de MySQL Shell
Este componente podría resumirse como un cliente avanzado de MySQL.
Procedemos a su instalación:
$ yum install mysql-shell
3. Instalación de MySQL Router
Este componente es un middleware que proporciona routing transparente entre la aplicación y el backend de servidores MySQL:

Procedemos a su instalación:
$ yum install mysql-router-community
Y dejaremos su configuración para más adelante.
4. Preparativos para la creación del cluster MySQL InnoDB
Instalamos python3 como dependencia:
$ yum install python3 $ alternatives --set python /usr/bin/python3
Abrimos los puertos de los firewalls de los hosts para permitir la comunicación del clúster:
$ firewall-cmd --permanent --add-port=3306/tcp success $ firewall-cmd --permanent --add-port=33060/tcp success $ firewall-cmd --permanent --add-port=33061/tcp success $ firewall-cmd --reload
Nos conectamos al MySQL Shell:
$ mysqlsh \connect root@localhost:3306 Password: xxx Creating a session to 'root@localhost:3306' Please provide the password for 'root@localhost:3306': *********** Save password for 'root@localhost:3306'? [Y]es/[N]o/Ne[v]er (default No): Y Fetching schema names for auto-completion... Press ^C to stop. Your MySQL connection id is 19 Server version: 8.0.44 MySQL Community Server - GPL No default schema selected; type \use to set one.
Y desde la misma, crearemos un admin account (InnoDB Cluster server configuration account):
MySQL localhost:3306 ssl JS > dba.configureInstance() Configuring local MySQL instance listening at port 3306 for use in an InnoDB cluster... This instance reports its own address as localhost.localdomain:3306 Clients and other cluster members will communicate with it through this address by default. If this is not correct, the report_host MySQL system variable should be changed. ERROR: User 'root' can only connect from 'localhost'. New account(s) with proper source address specification to allow remote connection from all instances must be created to manage the cluster. 1) Create remotely usable account for 'root' with same grants and password 2) Create a new admin account for InnoDB cluster with minimal required grants 3) Ignore and continue 4) Cancel Please select an option [1]: 2 Please provide an account name (e.g: icroot@%) to have it created with the necessary privileges or leave empty and press Enter to cancel. Account Name: icroot@% Password for new account: MYpass2026! Confirm password: MYpass2026! NOTE: Some configuration options need to be fixed: +----------------------------------------+---------------+----------------+--------------------------------------------------+ | Variable | Current Value | Required Value | Note | +----------------------------------------+---------------+----------------+--------------------------------------------------+ | binlog_transaction_dependency_tracking | COMMIT_ORDER | WRITESET | Update the server variable | | enforce_gtid_consistency | OFF | ON | Update read-only variable and restart the server | | gtid_mode | OFF | ON | Update read-only variable and restart the server | | server_id | 1 | | Update read-only variable and restart the server | +----------------------------------------+---------------+----------------+--------------------------------------------------+ Some variables need to be changed, but cannot be done dynamically on the server. Do you want to perform the required configuration changes? [y/n]: y Do you want to restart the instance after configuring it? [y/n]: y Creating user icroot@%. Account icroot@% was successfully created. Configuring instance... WARNING: '@@binlog_transaction_dependency_tracking' is deprecated and will be removed in a future release. (Code 1287). The instance 'localhost.localdomain:3306' was configured to be used in an InnoDB cluster. Restarting MySQL... NOTE: MySQL server at localhost.localdomain:3306 was restarted. MySQL localhost:3306 ssl JS >
4.1 Una nota sobre los usuarios
En un clúster MySQL InnoDB tendremos al menos 3 tipos de usuarios: el root (un root propio del servicio, no confundir con el root de sistema, que también existirá, pero con permisos solo en el ámbito de localhost), el/los administrator account/s y los relativos al MySQL Router. Puedes encontrar más información al respecto en la documentación de a continuación. En este tutorial haremos uso del admin account creado previamente y del de MySQL Router:
These accounts can be used to administer an InnoDB Cluster after you have completed the configuration process. To create an InnoDB Cluster administrator account for an InnoDB ClusterSet deployment, you issue a cluster.setupAdminAccount() command after you have added all the instances to that cluster.
These accounts are used by MySQL Router to connect to server instances in an InnoDB Cluster. The process to create a MySQL Router account is the same as for an InnoDB Cluster administrator account, but using a cluster.setupRouterAccount() command.
Como los nodos deben poder trabajar por red, ajustamos lo siguiente en el my.cnf para hacerlo posible:
$ vi /etc/my.cnf report_host = 192.168.2.80 :wq $ systemctl restart mysqld
A cada nodo le pondremos su IP (al my01 -> 192.168.2.80, al my02 -> 192.168.2.81 y al my03 -> 192.168.2.82).
Haremos un check para ver si existe algún impedimento para que los nodos puedan pasar a formar parte de un clúster:
$ mysqlsh
mysql-js> dba.checkInstanceConfiguration('icroot@192.168.2.80:3306')
Please provide the password for 'icroot@192.168.2.80:3306': ***********
Save password for 'icroot@192.168.2.80:3306'? [Y]es/[N]o/Ne[v]er (default No): Y
Validating local MySQL instance listening at port 3306 for use in an InnoDB cluster...
This instance reports its own address as 192.168.2.80:3306
Checking whether existing tables comply with Group Replication requirements...
No incompatible tables detected
Checking instance configuration...
Instance configuration is compatible with InnoDB cluster
The instance '192.168.2.80:3306' is valid to be used in an InnoDB cluster.
{
"status": "ok"
}
Todo correcto. Ahora iremos a configurar instancias para poder formar el clúster InnoDB:
$ mysqlsh
MySQL JS > dba.configureInstance('icroot@192.168.2.80:3306')
Configuring local MySQL instance listening at port 3306 for use in an InnoDB cluster...
This instance reports its own address as 192.168.2.80:3306
applierWorkerThreads will be set to the default value of 4.
The instance '192.168.2.80:3306' is valid to be used in an InnoDB cluster.
The instance '192.168.2.80:3306' is already ready to be used in an InnoDB cluster.
Successfully enabled parallel appliers.
A partir de aquí, los siguientes pasos solo serán necesarios hacerlos en el 1er nodo.
5. Creación del clúster MySQL InnoDB
$ mysqlsh
MySQL JS > \connect icroot@192.168.2.80:3306
Creating a session to 'icroot@192.168.2.80:3306'
Fetching schema names for auto-completion... Press ^C to stop.
Your MySQL connection id is 17
Server version: 8.0.44 MySQL Community Server - GPL
No default schema selected; type \use to set one.
MySQL 192.168.2.80:3306 ssl JS > var cluster = dba.createCluster('testCluster')
A new InnoDB Cluster will be created on instance '192.168.2.80:3306'.
Validating instance configuration at 192.168.2.80:3306...
This instance reports its own address as 192.168.2.80:3306
Instance configuration is suitable.
NOTE: Group Replication will communicate with other members using '192.168.2.80:3306'. Use the localAddress option to override.
* Checking connectivity and SSL configuration...
Creating InnoDB Cluster 'testCluster' on '192.168.2.80:3306'...
Adding Seed Instance...
Cluster successfully created. Use Cluster.addInstance() to add MySQL instances.
At least 3 instances are needed for the cluster to be able to withstand up to
one server failure.
MySQL 192.168.2.80:3306 ssl JS > cluster.status()
{
"clusterName": "testCluster",
"defaultReplicaSet": {
"name": "default",
"primary": "192.168.2.80:3306",
"ssl": "REQUIRED",
"status": "OK_NO_TOLERANCE",
"statusText": "Cluster is NOT tolerant to any failures.",
"topology": {
"192.168.2.80:3306": {
"address": "192.168.2.80:3306",
"memberRole": "PRIMARY",
"mode": "R/W",
"readReplicas": {},
"replicationLag": "applier_queue_applied",
"role": "HA",
"status": "ONLINE",
"version": "8.0.44"
}
},
"topologyMode": "Single-Primary"
},
"groupInformationSourceMember": "192.168.2.80:3306"
}
En este punto ya podemos ver el 1er nodo de nuestro clúster, que se ha erigido como primario.
Si lo queremos ver de nuevo, pero por lo que sea hemos cerrado la shell, lo podemos hacer con la siguiente query SQL:
MySQL 192.168.2.80:3306 ssl JS > \sql SELECT * FROM performance_schema.replication_group_members; Fetching global names for auto-completion... Press ^C to stop. +---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+ | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK | +---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+ | group_replication_applier | 8d2b6a7f-c896-11f0-93a9-bc241154d3c8 | 192.168.2.80 | 3306 | OFFLINE | | | MySQL | +---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+ 1 row in set (0.0004 sec)
Añadimos la 2ª instancia al clúster:
MySQL 192.168.2.80:3306 ssl JS > cluster.addInstance('icroot@192.168.2.81:3306')
NOTE: The target instance '192.168.2.81:3306' has not been pre-provisioned (GTID set is empty). The Shell is unable to decide whether incremental state recovery can correctly provision it.
The safest and most convenient way to provision a new instance is through automatic clone provisioning, which will completely overwrite the state of '192.168.2.81:3306' with a physical snapshot from an existing cluster member. To use this method by default, set the 'recoveryMethod' option to 'clone'.
The incremental state recovery may be safely used if you are sure all updates ever executed in the cluster were done with GTIDs enabled, there are no purged transactions and the new instance contains the same GTID set as the cluster or a subset of it. To use this method by default, set the 'recoveryMethod' option to 'incremental'.
Please select a recovery method [C]lone/[I]ncremental recovery/[A]bort (default Clone): C
Validating instance configuration at 192.168.2.81:3306...
This instance reports its own address as 192.168.2.81:3306
Instance configuration is suitable.
NOTE: Group Replication will communicate with other members using '192.168.2.81:3306'. Use the localAddress option to override.
* Checking connectivity and SSL configuration...
A new instance will be added to the InnoDB Cluster. Depending on the amount of
data on the cluster this might take from a few seconds to several hours.
Adding instance to the cluster...
Monitoring recovery process of the new cluster member. Press ^C to stop monitoring and let it continue in background.
Clone based state recovery is now in progress.
NOTE: A server restart is expected to happen as part of the clone process. If the
server does not support the RESTART command or does not come back after a
while, you may need to manually start it back.
* Waiting for clone to finish...
NOTE: 192.168.2.81:3306 is being cloned from 192.168.2.80:3306
** Stage DROP DATA: Completed
** Clone Transfer
FILE COPY ############################################################ 100% Completed
PAGE COPY ############################################################ 100% Completed
REDO COPY ############################################################ 100% Completed
NOTE: 192.168.2.81:3306 is shutting down...
* Waiting for server restart... ready
* 192.168.2.81:3306 has restarted, waiting for clone to finish...
** Stage RESTART: Completed
* Clone process has finished: 75.29 MB transferred in about 1 second (~75.29 MB/s)
State recovery already finished for '192.168.2.81:3306'
The instance '192.168.2.81:3306' was successfully added to the cluster.
Hecho. Pasamos a ver el nuevo estado del clúster ahora ya con 2 nodos, el primario y este que acabamos de añadir, el secundario:
MySQL 192.168.2.80:3306 ssl JS > cluster.status()
{
"clusterName": "testCluster",
"defaultReplicaSet": {
"name": "default",
"primary": "192.168.2.80:3306",
"ssl": "REQUIRED",
"status": "OK_NO_TOLERANCE",
"statusText": "Cluster is NOT tolerant to any failures.",
"topology": {
"192.168.2.80:3306": {
"address": "192.168.2.80:3306",
"memberRole": "PRIMARY",
"mode": "R/W",
"readReplicas": {},
"replicationLag": "applier_queue_applied",
"role": "HA",
"status": "ONLINE",
"version": "8.0.44"
},
"192.168.2.81:3306": {
"address": "192.168.2.81:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": "applier_queue_applied",
"role": "HA",
"status": "ONLINE",
"version": "8.0.44"
}
},
"topologyMode": "Single-Primary"
},
"groupInformationSourceMember": "192.168.2.80:3306"
}
Como podemos ver, en los campos status y statusText nos avisa de que en este estado, por tema de quorum, el clúster todavía no es tolerante a fallos.
Y así es como se ve haciendo la query SQL:
MySQL 192.168.2.80:3306 ssl JS > \sql SELECT * FROM performance_schema.replication_group_members; Fetching global names for auto-completion... Press ^C to stop. +---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+ | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK | +---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+ | group_replication_applier | 8d2b6a7f-c896-11f0-93a9-bc241154d3c8 | 192.168.2.80 | 3306 | ONLINE | PRIMARY | 8.0.44 | MySQL | | group_replication_applier | 9cee4b16-de9a-11f0-b103-bc24116bd82a | 192.168.2.81 | 3306 | ONLINE | SECONDARY | 8.0.44 | MySQL | +---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+ 2 rows in set (0.0005 sec)
Repetiremos el procedimiento para añadir el 3er y último nodo al clúster:
MySQL 192.168.2.80:3306 ssl JS > cluster.addInstance('icroot@192.168.2.82:3306')
Y revisaremos finalmente su estado:
MySQL 192.168.2.80:3306 ssl JS > cluster.status()
{
"clusterName": "testCluster",
"defaultReplicaSet": {
"name": "default",
"primary": "192.168.2.80:3306",
"ssl": "REQUIRED",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"192.168.2.80:3306": {
"address": "192.168.2.80:3306",
"memberRole": "PRIMARY",
"mode": "R/W",
"readReplicas": {},
"replicationLag": "applier_queue_applied",
"role": "HA",
"status": "ONLINE",
"version": "8.0.44"
},
"192.168.2.81:3306": {
"address": "192.168.2.81:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": "applier_queue_applied",
"role": "HA",
"status": "ONLINE",
"version": "8.0.44"
},
"192.168.2.82:3306": {
"address": "192.168.2.82:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": "applier_queue_applied",
"role": "HA",
"status": "ONLINE",
"version": "8.0.44"
}
},
"topologyMode": "Single-Primary"
},
"groupInformationSourceMember": "192.168.2.80:3306"
}
MySQL 192.168.2.80:3306 ssl JS > \sql SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier | 8d2b6a7f-c896-11f0-93a9-bc241154d3c8 | 192.168.2.80 | 3306 | ONLINE | PRIMARY | 8.0.44 | MySQL |
| group_replication_applier | 9cee4b16-de9a-11f0-b103-bc24116bd82a | 192.168.2.81 | 3306 | ONLINE | SECONDARY | 8.0.44 | MySQL |
| group_replication_applier | b7c7fba0-de9b-11f0-a4c2-bc24111ef4d6 | 192.168.2.82 | 3306 | ONLINE | SECONDARY | 8.0.44 | MySQL |
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+----------------------------+
3 rows in set (0.0007 sec)
En este punto ya tenemos el cluster levantado, funcionando y tolerante a la caída de un nodo (cualquiera de ellos). Fuera de esto, podemos tener problemas, así que procuraremos que las intervenciones o sucesos afecten solo a 1 nodo al mismo tiempo en la medida de lo posible.
6. Routing a las instancias
Si hacemos unas cuantas pruebas con el clúster para ver cómo se comporta, veremos que en general, mientras quede siempre un nodo vivo, el clúster se recompone, y si es el máster el nodo afectado, el clúster promociona un esclavo para recuperarlo. Pero si perdemos el nodo máster, si nos conectamos al clúster a través de su dirección IP, ¿no nos quedaremos sin acceso al mismo?
La respuesta es sí, y el motivo es que en tema de clústers de HA no es buena práctica conectarse a una dirección IP en concreto directamente (a no ser que se trate de una IP especial) por motivos obvios: si el nodo de aquella IP no está disponible, no tendremos acceso al servicio. Para eso está el MySQL Router. En su documentación comenta instalarlo en la application server (el mismo host donde se ejecuta la aplicación), pero si no queremos hacer eso (p. ej. para tener una separación más clara/limpia entre el servidor de aplicaciones y los componentes pertenecientes a la base de datos), existe la alternativa de ubicarlo en los mismos nodos del clúster (en todos ellos), bien haciendo uso de un Network Load Balancer o de una Virtual IP. Ejemplo de este último es hacer uso de keepalived, una herramienta que funciona con el protocolo VRRP para dotar de alta disponibilidad a una IP que vivirá flotando entre los nodos.
Así pues, nos pondremos manos a la obra para configurar MySQL Router, así como también para desplegar y configurar keepalived. Estos son los pasos:
6.1 Bootstrap del MySQL Router
Lanzamos el comando mysqlrouter con los parámetros necesarios para (1) generar una configuración de acceso al clúster, así como para (2) crear un usuario con el que este componente (MySQL Router) pueda consultar el estado del cluster y tomar decisiones sobre el enrutado de las consultas en todo momento:
$ mysqlrouter --user=root --bootstrap icroot@localhost:3306 --conf-bind-address=0.0.0.0 --account mysqlrouter --force
Please enter MySQL password for icroot: MYpass2026!
# Bootstrapping system MySQL Router 8.0.44 (MySQL Community - GPL) instance...
Please enter MySQL password for mysqlrouter: MYrouterpass2025!
- Creating account(s)
- Verifying account (using it to run SQL queries that would be run by Router)
- Storing account in keyring
- Adjusting permissions of generated files
- Creating configuration /etc/mysqlrouter/mysqlrouter.conf
Existing configuration backed up to '/etc/mysqlrouter/mysqlrouter.conf.bak'
# MySQL Router configured for the InnoDB Cluster 'testCluster'
After this MySQL Router has been started with the generated configuration
$ /etc/init.d/mysqlrouter restart
or
$ systemctl start mysqlrouter
or
$ mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf
InnoDB Cluster 'testCluster' can be reached by connecting to:
## MySQL Classic protocol
- Read/Write Connections: localhost:6446, /tmp/mysql.sock
- Read/Only Connections: localhost:6447, /tmp/mysqlro.sock
## MySQL X protocol
- Read/Write Connections: localhost:6448, /tmp/mysqlx.sock
- Read/Only Connections: localhost:6449, /tmp/mysqlxro.sock
Adecuamos la configuración generada y algunos permisos:
$ sed -i 's/user=root/#user=root/g' /etc/mysqlrouter/mysqlrouter.conf $ chown -R mysqlrouter:mysqlrouter /etc/mysqlrouter /var/lib/mysqlrouter /var/log/mysqlrouter $ chmod 644 /etc/mysqlrouter/mysqlrouter.conf $ systemctl restart mysqlrouter
Verificamos que funciona correctamente:
[root@localhost ~]# mysql -u mysqlrouter -p -h 127.0.0.1 -P 6446 Enter password: mysql> SELECT @@hostname, @@port; +-----------------------+--------+ | @@hostname | @@port | +-----------------------+--------+ | my01 | 3306 | +-----------------------+--------+
En este punto podemos crear una base de datos, popularla y hacer una prueba de inserción de datos desde un slave para comprobar que se enruta por el máster (el único que puede escribir en la base de datos):
my01$ mysql -u root -p mysql> CREATE DATABASE test; mysql> CREATE TABLE datos (id INT NOT NULL, nombre VARCHAR(20), PRIMARY KEY (id)); mysql> INSERT INTO datos (id,nombre) VALUES (1, "Pepe"); mysql> INSERT INTO datos (id,nombre) VALUES (2, "Luis"); mysql> INSERT INTO datos (id,nombre) VALUES (3, "Alberto"); mysql> INSERT INTO datos (id,nombre) VALUES (4, "Juan"); mysql> INSERT INTO datos (id,nombre) VALUES (5, "Francisco"); mysql> CREATE USER 'test_user'@'%' IDENTIFIED BY 'Pass-word-123'; mysql> GRANT ALL PRIVILEGES ON test.* TO 'test_user'@'%'; mysql> FLUSH PRIVILEGES; mysql> QUIT; my02$ mysql -h 127.0.0.1 -P 6446 -u test_user -p -e "INSERT INTO datos (id,nombre) VALUES (6, 'Damian')" test my02$ mysql -u test_user -p -e "SELECT * FROM datos" test +----+-----------+ | id | nombre | +----+-----------+ | 1 | Pepe | | 2 | Luis | | 3 | Alberto | | 4 | Juan | | 5 | Francisco | | 6 | Damian | +----+-----------+
Llegados aquí, tenemos que da igual en qué nodo se haga la operación de escritura, ya que el MYSQL Router la derivará al nodo master y el resto de peticiones (consultas) las podrá hacer en local. Pero tenemos las siguientes desventajas:
- Las peticiones se envían a las IPs de los 3 nodos. Si uno o varios no están disponibles, fallarán.
- Las escrituras, si acaban en un nodo diferente al máster, MySQL Router las tendrá que redirigir, con el consiguiente overhead de tráfico de red.
Por este motivo, podemos querer hacer uso de una VIP que concentre las peticiones al servicio de base de datos.
6.2 Instalación y configuración de keepalived
$ yum install keepalived
Creamos la configuración del servicio:
$ vi /etc/keepalived/keepalived.conf
global_defs {
script_user keepalived_script
enable_script_security
}
vrrp_script check_mysql {
script "/usr/local/bin/check_mysql_master.sh"
interval 2 # Check every 2 seconds
weight 2 # Add priority if script passes
fall 2 # Require 2 failures to consider it down
rise 2 # Require 2 successes to consider it up
}
vrrp_instance VI_1 {
state BACKUP # Set all to BACKUP; let the script decide
interface ens18 # Your main network interface
virtual_router_id 51 # Must be same on all nodes
priority 100 # Base priority
virtual_ipaddress {
#192.168.2.100 # Enough for 'ip s a'
192.168.2.100/24 dev ens18 label ens18:1 # Needed for 'ifconfig'
}
track_script {
check_mysql
}
}
:wq
Así como el script de check de quién es el máster:
$ vi /usr/local/bin/check_mysql_master.sh
#!/bin/bash
# Connect to the local MySQL and check if it is the PRIMARY node
# This requires a user with permissions to view performance_schema
IS_PRIMARY=$(mysql -u cluster_check_user -p'K33p-4liv3d' -N -s -e "SELECT MEMBER_ROLE FROM performance_schema.replication_group_members WHERE MEMBER_ID=@@server_uuid;")
if [ "$IS_PRIMARY" = "PRIMARY" ]; then
exit 0 # Success: I am the Master
else
exit 1 # Failure: I am a Slave or down
fi
:wq
Creamos el usuario de sistema que ejecutará el script de check (ya que es un requisito de seguridad de keepalived):
$ useradd -r -s /sbin/nologin keepalived_script $ chown keepalived_script:keepalived_script /usr/local/bin/check_mysql_master.sh $ chmod 755 /usr/local/bin/check_mysql_master.sh
Y también creamos el usuario que consultará a MySQL quién es el master:
$ mysql -u root -p mysql> CREATE USER 'cluster_check_user'@'localhost' IDENTIFIED BY 'K33p-4liv3d'; mysql> GRANT SELECT ON performance_schema.* TO 'cluster_check_user'@'localhost'; mysql> FLUSH PRIVILEGES;
Permitimos en el firewall el protocolo VRRP y abrimos los puertos del MySQL Router:
# Open VRRP protocol $ firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept' # Open MySQL Router Read/Write port $ firewall-cmd --permanent --add-port=6446/tcp # Open MySQL Router Read-Only port $ firewall-cmd --permanent --add-port=6447/tcp $ firewall-cmd --reload
Finalmente creamos una custom SELinux policy que permitirá la ejecución del script de check:
$ ausearch -m avc -ts recent | audit2allow -M my_keepalived_mysql $ semodule -i my_keepalived_mysql.pp
Con esto ya tenemos la IP a la que deben atacar las aplicaciones al cluster para hacerlas pasar siempre por el màster.
6.3 Pruebas de keepalived
Podemos probar a tumbar (parar) el nodo master para ver cómo MySQL InnoDB elige otro nodo para traspasarle este rol (el del nuevo master), así como apreciar que keepalived mueve la IP flotante a este mismo nodo para que las consultas se dirijan hacia él.
Tips
He aquí una serie de trucos que han sido utilizados durante la elaboración de este laboratorio.
Check rápido del estado del cluster
Para ser más ágiles, podemos crear un alias con alguno de los siguientes oneliners (vía mysqlsh o sql, respectivamente):
$ mysqlsh icroot@localhost --execute "print(dba.getCluster('testCluster').status())"
ó
$ mysql -h 127.0.0.1 -P 6446 -u icroot -p -e "SELECT MEMBER_HOST, MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members;"
Interrupción en la creación del cluster
Si en el momento de creación del clúster con los 3 nodos ha habido alguna interrupción (p. ej. reinicio del host principal, o paramos el nodo para continuar otro día), será necesario reinstanciar el clúster:
// This will scan the metadata and restart the cluster on this node MySQL 192.168.2.80:3306 ssl JS > var cluster = dba.rebootClusterFromCompleteOutage()
¿Cómo conectarse al clúster desde MySQL Shell una vez que ya está creado?
// Assign the existing cluster to the variable 'cluster' first
MySQL 192.168.2.80:3306 ssl JS > var cluster = dba.getCluster() ó var cluster = dba.getCluster('testCluster')
MySQL 192.168.2.80:3306 ssl JS > cluster.status()
¿Cómo recupero el clúster después de un apagado de todos los nodos?
Cuando se paran todos los nodos (CompleteOutage), el plugin Group Replication (GR) se desactiva por seguridad para prevenir casos de split brain (existe el riesgo de que un nodo con datos más antiguos se erija como el nodo primario antes que el primario real). Debemos iniciar los nodos (da igual el orden) y cuando estén todos iniciados, lanzaremos desde el que creemos que era el master, el siguiente comando para reinicializar el Group Replication:
MySQL 192.168.2.80:3306 ssl JS > var cluster = dba.rebootClusterFromCompleteOutage('testCluster');
Restoring the Cluster 'testCluster' from complete outage...
Cluster instances: '192.168.2.80:3306' (OFFLINE), '192.168.2.81:3306' (OFFLINE), '192.168.2.82:3306' (OFFLINE)
Waiting for instances to apply pending received transactions...
Validating instance configuration at 192.168.2.80:3306...
This instance reports its own address as 192.168.2.80:3306
Instance configuration is suitable.
* Waiting for seed instance to become ONLINE...
192.168.2.80:3306 was restored.
Validating instance configuration at 192.168.2.81:3306...
This instance reports its own address as 192.168.2.81:3306
Instance configuration is suitable.
Rejoining instance '192.168.2.81:3306' to cluster 'testCluster'...
Re-creating recovery account...
NOTE: User 'mysql_innodb_cluster_3068219787'@'%' already existed at instance '192.168.2.80:3306'. It will be deleted and created again with a new password.
* Waiting for the Cluster to synchronize with the PRIMARY Cluster...
** Transactions replicated ############################################################ 100%
The instance '192.168.2.81:3306' was successfully rejoined to the cluster.
Validating instance configuration at 192.168.2.82:3306...
This instance reports its own address as 192.168.2.82:3306
Instance configuration is suitable.
Rejoining instance '192.168.2.82:3306' to cluster 'testCluster'...
Re-creating recovery account...
NOTE: User 'mysql_innodb_cluster_3124054725'@'%' already existed at instance '192.168.2.80:3306'. It will be deleted and created again with a new password.
* Waiting for the Cluster to synchronize with the PRIMARY Cluster...
** Transactions replicated ############################################################ 100%
The instance '192.168.2.82:3306' was successfully rejoined to the cluster.
The Cluster was successfully rebooted.
¿Cómo recupero el clúster si no tengo todos los nodos disponibles?
Lo podemos hacer del mismo modo que el comando del punto previo, pero indicando el parámetro {force: true} para forzar su recuperación a pesar de no disponer de todos los nodos:
MySQL 192.168.2.80:3306 ssl JS > var cluster = dba.rebootClusterFromCompleteOutage('testCluster',{force: true});
Restoring the Cluster 'testCluster' from complete outage...
Cluster instances: '192.168.2.80:3306' (OFFLINE), '192.168.2.81:3306' (UNREACHABLE), '192.168.2.82:3306' (UNREACHABLE)
WARNING: One or more instances of the Cluster could not be reached and cannot be rejoined nor ensured to be OFFLINE: '192.168.2.81:3306', '192.168.2.82:3306'. Cluster may diverge and become inconsistent unless all instances are either reachable or certain to be OFFLINE and not accepting new transactions. You may use the 'force' option to bypass this check and proceed anyway.
Waiting for instances to apply pending received transactions...
Validating instance configuration at 192.168.2.80:3306...
This instance reports its own address as 192.168.2.80:3306
Instance configuration is suitable.
* Waiting for seed instance to become ONLINE...
192.168.2.80:3306 was restored.
The Cluster was successfully rebooted.
Con esto arrancará el clúster, aunque solo sea con 1 solo nodo.
