Balanceando la carga de MySQL con HAProxy

Alexis Tenorio
11 min readJun 6, 2021

--

Balancearemos la carga de peticiones en un entorno de alta disponibilidad con MySQL. Para eso, es indispensable montar un cluster de MySQL, para este ejercicio bastará con un nodo de administración (MGM), dos nodos de almacenamiento (NDB) que también servirán de nodos SQL (MYSQLD), y un nodo con el único rol de ejecutar peticiones a la base de datos. Gráficamente esta sería la arquitectura:

Representación gráfica del cluster montado con MySQL
Aguilera, R. (2012, 14 abril). Representación gráfica del cluster [Gráfico]. https://www.adictosaltrabajo.com/2012/04/14/mysql-cluster/. https://www.adictosaltrabajo.com/wp-content/uploads/tutorial-data/MySQLCluster/MySQLCluster_img1.png

Utilizamos una máquina virtual por cada nodo en el cluster, en nuestro caso utilizaremos la plataforma de Docker para descargar la imagen de Ubuntu, y virtualizar tantas máquinas se requieran. Si usted ya es usuario de sistemas operativos tipo Unix, probablemente le parezcan triviales algunos detalles, pero si ese no es el caso, sería de todo mi agrado hacer notar con este ejercicio lo divertido e inmersivo que es trabajar en estos ambientes.

Como primer paso debemos instalar Docker Desktop e iniciar sesión, una cuenta gratuita es suficiente para realizar este ejercicio. Después, debemos bajar la imagen del sistema operativo Ubuntu, por lo que requeriremos de una nueva línea de comandos (en caso de Windows, presionamos la combinación de las teclas WIN+R y escribimos “cmd”). En la línea de comandos escribimos la siguiente instrucción:

docker pull ubuntu

Una vez que termine la descarga de la imagen, seremos capaces de ejecutar un nuevo contenedor con Ubuntu desde Docker Desktop. Para esto, accedemos a la pestaña de “Images” en Docker Desktop, y luego a la subpestaña de “LOCAL”, donde podremos ver la imagen de ubuntu, en la que debemos dar clic en “RUN”.

Se nos preguntará por el detalle del contenedor, aquí sólo será necesario especificar su nombre, por lo que recomiendo nombrar a este primer nodo como “MGM”, para identificar fácilmente que se trata del nodo administrador. Damos clic en “Run” para crear e iniciar el nuevo contenedor. Más adelante deberemos repetir este mismo proceso para iniciar los contenedores correspondientes al resto de nodos del cluster.

Al dar clic en “Run” cambiamos a la pestaña de “Containers / Apps” para iniciar el contenedor desde el botón “START”.

Cuando un contenedor está en ejecución podemos acceder a su línea de comandos desde el botón “CLI” en Docker Desktop, aunque yo recomendaría volver a la línea de comandos de Windows, ya que esta nos ofrece una interfaz con más atajos de teclado.

Desde la línea de comandos de Windows ejecutamos lo siguiente:

docker exec -it MGM bash

De esta forma anclamos la línea de comandos de Windows a la máquina virtual que está corriendo en Docker, deberíamos ver un resultado similar al siguiente:

¡Ya hemos logrado ejecutar una instancia de Ubuntu!, ahora podemos proceder a configurar nuestro primer nodo administrador, pero antes, es necesario preparar el sistema operativo con ciertas librerías que estaremos utilizando en la configuración.

Con este primer comando actualizamos la lista de paquetes disponibles y sus versiones:

apt-get update && apt-get install -y lsb-release && apt-get clean all

Una vez que termine el proceso de actualización, deberíamos ser capaces de descargar los siguientes paquetes:

apt-get install gnupg2 && apt-get install wget

Ambos serán necesarios para descargar y descomprimir la lista de repositorios de los paquetes de MySQL, así que cuando termine la instalación de ambos paquetes procedemos a ubicar una ruta de fácil acceso, por ejemplo:

cd /home

En esta ruta ejecutamos el siguiente comando para descargar un archivo desde un URL:

wget https://dev.mysql.com/get/mysql-apt-config_0.8.17-1_all.deb

Cuando la descarga se haya completado, hay que descomprimir el archivo con el siguiente comando:

dpkg -i mysql-apt-config_0.8.17–1_all.deb

Es importante notar que se nos preguntará por los servicios que deseamos instalar, primero seleccionamos la opción 1 (MySQL Server & Cluster), y después seleccionamos la opción 2 (mysql-cluster-8.0). En las opciones 2 y 3 seleccionamos “Disabled”, y confirmamos nuestra selección con la opción 4 (Ok).

Cuando el proceso haya terminado, debemos actualizar una vez más la lista de paquetes disponibles con:

apt-get update

Si no obtuvimos errores en los pasos anteriores, deberíamos ser capaces de ejecutar el siguiente comando:

apt-get install mysql-cluster-community-management-server

Esto nos instalará el paquete que nos permite configurar un nodo de administración en un cluster de MySQL. Cuando la descarga haya concluido, debemos tener listo el archivo de configuración para este nodo, por lo tanto, creamos un nuevo documento con el siguiente comando:

touch /etc/mysql/ndb_mgmd.cnf

La ruta donde almacenaremos la configuración y el nombre del archivo puede ser el que tu desees, pero debes recordar donde estará almacenada.

Una vez que tengamos el nuevo archivo, debemos editarlo, para esto descargamos el siguiente paquete:

apt-get install vim

Antes de editar el archivo de configuración, es necesario conocer la IP del nodo de administración. Con el siguiente comando podremos conocer la IP de cualquier nodo:

hostname –I

Ahora, con el comando vim podemos editar cualquier archivo, por lo que si ubicamos a la línea de comandos en la misma carpeta que el archivo de configuración, seremos capaces de ejecutar lo siguiente:

vim ndb_mgmd.cnf

Hemos entrado al modo de visualización de archivos. Para comenzar a insertar texto presionamos la tecla ‘I’ del teclado, y podemos pegar el siguiente texto al archivo, dando clic derecho sobre la línea de comandos:

[NDBD DEFAULT]
NoOfReplicas=2
DataMemory=256M

[MYSQLD DEFAULT]

[NDB_MGMD DEFAULT]

[TCP DEFAULT]

[NDB_MGMD]
HostName=<IP_NODO_ADMINISTRADOR>

[NDBD]
HostName=<IP_NODO_DATOS_1>
DataDir=/var/lib/mysql-cluster

[NDBD]
HostName=<IP_NODO_DATOS_2>
DataDir=/var/lib/mysql-cluster

[MYSQLD]
HostName=<IP_NODO_DATOS_1>

[MYSQLD]
HostName=<IP_NODO_DATOS_2>

[MYSQLD]
HostName=<IP_NODO_SQL>

Debes cambiar las etiquetas “<…>” con las IP’s reales de tu cluster.

Para este caso estamos determinando que nuestro cluster se va a componer de dos replicas de datos que están descritas en las secciones [NDBD], donde indicamos cual es la IP de la máquina y donde se van a almacenar los datos del cluster en esa máquina. Luego en las secciones [MYSQLD] determinamos la dirección IP de las máquinas que van a poder acceder a los datos del cluster. En este caso las máquinas NDB también adoptaran el rol de nodo SQL y solo una de ellas tendrá exclusivamente este rol. Como se veía en la figura de la arquitectura propuesta.

Sé que aún no hemos ejecutado ni configurado los otros nodos, pero al menos en mi caso, si corremos los contenedores en orden, es decir, primero el MGM, luego el NDBD1, luego el NDBD2, y luego el MYSQLD, las IP’s irán en orden, por ejemplo, las IP’s en mi configuración son: 172.17.0.2, 172.17.0.3, 172.17.0.4. y 172.17.0.5, par cada nodo en el orden mencionado. La propiedad DataDir si puede cambiar, pero debes recordar cuál estás asignando.

Ya que tengamos el texto en el archivo, presionamos la tecla “ESC” del teclado para salir del modo de inserción. Si queremos hacer algún cambio, podemos desplazarnos por el texto con las flechas del teclado, y dando nuevamente a “I” podemos insertar texto. Si queremos guardar los cambios regresamos al modo visualización con “ESC” y escribimos:

:wq

Nota que el símbolo “:” es parte del comando. Si no quisiéramos guardar los cambios, siempre podemos salir con el siguiente comando:

:exit

Ya tenemos lista la configuración del cluster, ahora sólo es necesario correr el siguiente comando para iniciar el proceso del nodo de administración:

ndb_mgmd — initial -f /etc/mysql/ndb_mgmd.cnf

Si hemos cambiado la ruta del archivo de configuración, esta debería ir después de “-f”.

Si todo ha salido bien y el clúster se ha configurado correctamente, ahora el clúster debería estar operando. Puedes probar esto invocando el cliente del nodo de administración:

ndb_mgm

Después ejecutamos:

SHOW

Aquí podemos ver la configuración del cluster. Podemos ver que los dos nodos NDB aún no están conectados, pero que el cluster está recibiendo la conexión desde las IP’s que configuramos. También vemos como el nodo MGM sí está conectado. Por último, vemos los tres nodos MYSQLD, con sus respectivas IP’s.

Si deseamos salir del cliente y volver a la línea de Ubuntu, simplemente ingresamos el comando:

exit

¡Ya hemos configurado el primer nodo! Ahora debemos replicar 3 veces más los pasos de: ejecutar un contenedor con Ubuntu, actualizar los paquetes, instalar GNUPG2, WGet y Vim, y descomprimir el repositorio de paquetes de MySQL.

Para cambiar entre contenedores no hace falta más de una línea de comandos. Si dejamos presionada la tecla “CTRL” y después presionamos una vez “P”, y luego una vez “Q”, habremos ingresado la secuencia de salida. El contenedor en el que estábamos seguirá en ejecución, y podemos anclarnos a cualquier otro.

Vamos a configurar primero los nodos de datos. Instalamos el siguiente paquete:

apt-get install mysql-cluster-community-data-node

Una vez que se haya instalado, hay que asegurarnos que el DataDir que asignamos en la configuración del nodo administrador exista en los nodos de datos. Con el siguiente comando creamos el directorio:

mkdir /var/lib/mysql-cluster

Con el siguiente comando asignamos a root como propietario del directorio, y a mysql como el grupo del directorio:

chown root:mysql /var/lib/mysql-cluster

Para los nodos de datos también es necesario editar un archivo de configuraciones, en este caso, lo guardamos en /etc/mysql/my.cnf con el siguiente comando:

vim /etc/mysql/my.cnf

Este archivo está compuesto por secciones y propiedades, para nuestro caso, la configuración debe quedar de la siguiente forma:

!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mysql.conf.d/
[mysqld]
ndbcluster
[mysql_cluster]
ndb-connectstring=<IP_NODO_ADMINISTRADOR>

Esta configuración nos sirve por un lado para habilitar el soporte del motor de almacenamiento NDBCluster y por otro para saber como conectar con el MGM.

Ahora estamos listos para conectar el nodo de datos al cluster, con el siguiente comando:

ndbd

¡Nos hemos conectado al nodo administrador! Podemos ver el id que se nos ha asignado en el cluster. Debemos replicar esto en cada nodo de datos. Si volvemos al nodo administrador y desde el cliente del nodo administrador obtenemos la lista de nodos con “SHOW”, podremos comprobar la conexión establecida a los nodos de datos:

El único paso restante para tener un ambiente MySQL de alta disponibilidad es conectar un cliente que obtenga los datos almacenados.

Ahora probaremos el nodo SQL (aunque también podemos realizar los mismos pasos en los nodos NDB), por lo que debemos acceder a su línea de comandos. Nuevamente realizamos todos los pasos requeridos para descomprimir la lista de repositorios de MySQL, y actualizamos la lista de paquetes del contenedor. En este nodo SQL será necesario instalar el cliente de MySQL Cluster Community Server:

apt-get install mysql-cluster-community-server

Esta instalación nos pedirá una contraseña para el usuario root, junto con el método de autenticación que deseamos utilizar (el recomendado es la opción 1). Una vez que termine la instalación, no debemos olvidar configurar el archivo /etc/mysql/my.cnf de la misma forma que los nodos NDB.

Para conectar nuestro nodo y activar el API de MySQL en el cluster, debemos ejecutar el siguiente comando desde el nodo SQL.

mysqld — user=root

Si observamos la configuración del cluster desde el nodo de administración, observaremos que la conexión se ha establecido:

Si la línea de comandos del nodo SQL quedó pasmada después del comando mysqld, basta con ingresar la secuencia de escape y volver a ejecutar el contenedor del nodo SQL. Con el siguiente comando ingresamos al cliente SQL que está siendo compartido en todo el cluster:

mysql -u root -p

Se nos pedirá ingresar la clave del usuario root. Una vez ingresada la contraseña, habremos logrado tener un cliente de MySQL en un ambiente de alta disponibilidad ¡Excelente!

Es momento de balancear la carga de peticiones entre los nodos SQL. Necesitaremos una nueva máquina virtual que funcionará como proxy balanceador, no olvidemos preparar el contenedor para poder descargar paquetes actualizados y editar archivos. Descargamos el siguiente paquete:

apt-get install haproxy

Una vez terminada la instalación, editamos el siguiente archivo de configuración:

vim /etc/haproxy/haproxy.cfg

El archivo debe contar con las siguientes secciones y valores:

global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
maxconn 4096
user haproxy
group haproxy
daemon

# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private

# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
log global
#mode http
#option httplog
mode tcp
option tcplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
retries 3
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http

listen mysql-cluster
bind 0.0.0.0:3306
mode tcp
balance roundrobin
server nodo1 <IP_NODO_DATOS_1>:3306 check weight 30
server nodo2<IP_NODO_DATOS_2>:3306 check weight 30
server nodo3 <IP_NODO_DATOS_SQL>:3306 check weight 40

La parte importante se encuentra en el listen mysql-cluster, donde estamos indicando que el proxy escuche por el puerto 3307, y que las peticiones que le lleguen por ese puerto las reparta en base a un algoritmo round robin entre los servidores que se especifican. En este caso definimos un peso más alto para el nodo SQL, ya que este no estará realizando lecturas y escrituras al disco.

Con el siguiente comando reiniciamos el servicio y verificamos el archivo de configuración:

haproxy -f /etc/haproxy/haproxy.cfg

Lo primero que tenemos que hacer para probar el resultado es crear y dar permisos de conexión remota al mismo usuario en los distintos nodos SQL que están referenciados en la configuración del proxy.

Estos pasos los repetimos por cada uno de los nodos SQL. Primero nos logamos como root, después creamos el usuario y le damos los permisos para conectar remotamente:

CREATE USER prueba;
GRANT ALL PRIVILEGES ON *.* TO ‘prueba’@’%’;
FLUSH PRIVILEGES;

Abrimos el archivo de configuración /etc/mysql/my.cnf y en la sección [mysqld] ponemos a 0.0.0.0 el valor de la variable bind-address y el valor de la variable max-connections a 1000.

Reiniciamos todos los nodos y comprobamos que el cluster sigue funcionando.

Para probar el balanceo de la carga basta con situarnos en un nodo SQL y tratar de ejecutar una conexión remota a la dirección y puerto del proxy con el siguiente comando:

mysql -u prueba -h <IP_DEL_PROXY>

Tenemos que ver que el servidor conecta y permite la ejecución de consultas. Hay que recordar que en la máquina del proxy no está instalado MySQL, así que nuestro proxy está funcionando.

Referencias

--

--