ProxySQL admite la agrupación en clústeres nativa desde la v1.4.2. Esto significa que varias instancias de ProxySQL son compatibles con clústeres; son conscientes del estado de los demás y pueden manejar los cambios de configuración automáticamente mediante la sincronización con la configuración más actualizada en función de la versión de configuración, la marca de tiempo y el valor de la suma de comprobación. Consulte esta publicación de blog que demuestra cómo configurar la compatibilidad con clústeres para ProxySQL y cómo puede esperar que se comporte.
ProxySQL es un proxy descentralizado, recomendado para implementarse más cerca de la aplicación. Este enfoque escala bastante bien incluso hasta cientos de nodos, ya que fue diseñado para ser fácilmente reconfigurable en tiempo de ejecución. Para administrar de manera eficiente múltiples nodos de ProxySQL, uno debe asegurarse de que los cambios realizados en uno de los nodos se apliquen en todos los nodos de la granja. Sin el agrupamiento nativo, uno tiene que exportar manualmente las configuraciones e importarlas a los otros nodos (aunque podría automatizar esto usted mismo).
En la publicación de blog anterior, hemos cubierto la agrupación en clústeres de ProxySQL a través de Kubernetes ConfigMap. Este enfoque es más o menos bastante eficiente con el enfoque de configuración centralizada en ConfigMap. Todo lo que se cargue en ConfigMap se montará en pods. La actualización de la configuración se puede realizar a través del control de versiones (modifique el contenido de proxysql.cnf y cárguelo en ConfigMap con otro nombre) y luego envíelo a los pods según la programación del método de implementación y la estrategia de actualización.
Sin embargo, en un entorno que cambia rápidamente, este enfoque de ConfigMap probablemente no sea el mejor método porque para cargar la nueva configuración, se requiere la reprogramación del pod para volver a montar el volumen de ConfigMap y esto podría poner en peligro el servicio ProxySQL en su totalidad. Por ejemplo, digamos que en nuestro entorno, nuestra estricta política de contraseñas requiere forzar la caducidad de la contraseña de usuario de MySQL cada 7 días, por lo que tendríamos que seguir actualizando ProxySQL ConfigMap para la nueva contraseña semanalmente. Como nota al margen, el usuario de MySQL dentro de ProxySQL requiere que el usuario y la contraseña coincidan con los de los servidores backend de MySQL. Ahí es donde deberíamos comenzar a hacer uso de la compatibilidad con la agrupación en clústeres nativa de ProxySQL en Kubernetes, para aplicar automáticamente los cambios de configuración sin la molestia de las versiones de ConfigMap y la reprogramación de pods.
En esta publicación de blog, le mostraremos cómo ejecutar la agrupación en clústeres nativa de ProxySQL con un servicio sin cabeza en Kubernetes. Nuestra arquitectura de alto nivel se puede ilustrar de la siguiente manera:
Tenemos 3 nodos de Galera que se ejecutan en una infraestructura completa implementada y administrada por ClusterControl:
- 192.168.0.21
- 192.168.0.22
- 192.168.0.23
Todas nuestras aplicaciones se ejecutan como pods dentro de Kubernetes. La idea es introducir dos instancias de ProxySQL entre la aplicación y nuestro clúster de base de datos para que sirvan como un proxy inverso. Luego, las aplicaciones se conectarán a los pods de ProxySQL a través del servicio de Kubernetes, cuya carga se equilibrará y conmutará por error en varias réplicas de ProxySQL.
El siguiente es un resumen de nuestra configuración de Kubernetes:
[email protected]:~# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
kube1 Ready master 5m v1.15.1 192.168.100.201 <none> Ubuntu 18.04.1 LTS 4.15.0-39-generic docker://18.9.7
kube2 Ready <none> 4m1s v1.15.1 192.168.100.202 <none> Ubuntu 18.04.1 LTS 4.15.0-39-generic docker://18.9.7
kube3 Ready <none> 3m42s v1.15.1 192.168.100.203 <none> Ubuntu 18.04.1 LTS 4.15.0-39-generic docker://18.9.7
Configuración de ProxySQL a través de ConfigMap
Primero preparemos nuestra configuración base que se cargará en ConfigMap. Cree un archivo llamado proxysql.cnf y agregue las siguientes líneas:
datadir="/var/lib/proxysql"
admin_variables=
{
admin_credentials="proxysql-admin:adminpassw0rd;cluster1:secret1pass"
mysql_ifaces="0.0.0.0:6032"
refresh_interval=2000
cluster_username="cluster1"
cluster_password="secret1pass"
cluster_check_interval_ms=200
cluster_check_status_frequency=100
cluster_mysql_query_rules_save_to_disk=true
cluster_mysql_servers_save_to_disk=true
cluster_mysql_users_save_to_disk=true
cluster_proxysql_servers_save_to_disk=true
cluster_mysql_query_rules_diffs_before_sync=3
cluster_mysql_servers_diffs_before_sync=3
cluster_mysql_users_diffs_before_sync=3
cluster_proxysql_servers_diffs_before_sync=3
}
mysql_variables=
{
threads=4
max_connections=2048
default_query_delay=0
default_query_timeout=36000000
have_compress=true
poll_timeout=2000
interfaces="0.0.0.0:6033;/tmp/proxysql.sock"
default_schema="information_schema"
stacksize=1048576
server_version="5.1.30"
connect_timeout_server=10000
monitor_history=60000
monitor_connect_interval=200000
monitor_ping_interval=200000
ping_interval_server_msec=10000
ping_timeout_server=200
commands_stats=true
sessions_sort=true
monitor_username="proxysql"
monitor_password="proxysqlpassw0rd"
monitor_galera_healthcheck_interval=2000
monitor_galera_healthcheck_timeout=800
}
mysql_galera_hostgroups =
(
{
writer_hostgroup=10
backup_writer_hostgroup=20
reader_hostgroup=30
offline_hostgroup=9999
max_writers=1
writer_is_also_reader=1
max_transactions_behind=30
active=1
}
)
mysql_servers =
(
{ address="192.168.0.21" , port=3306 , hostgroup=10, max_connections=100 },
{ address="192.168.0.22" , port=3306 , hostgroup=10, max_connections=100 },
{ address="192.168.0.23" , port=3306 , hostgroup=10, max_connections=100 }
)
mysql_query_rules =
(
{
rule_id=100
active=1
match_pattern="^SELECT .* FOR UPDATE"
destination_hostgroup=10
apply=1
},
{
rule_id=200
active=1
match_pattern="^SELECT .*"
destination_hostgroup=20
apply=1
},
{
rule_id=300
active=1
match_pattern=".*"
destination_hostgroup=10
apply=1
}
)
mysql_users =
(
{ username = "wordpress", password = "passw0rd", default_hostgroup = 10, transaction_persistent = 0, active = 1 },
{ username = "sbtest", password = "passw0rd", default_hostgroup = 10, transaction_persistent = 0, active = 1 }
)
proxysql_servers =
(
{ hostname = "proxysql-0.proxysqlcluster", port = 6032, weight = 1 },
{ hostname = "proxysql-1.proxysqlcluster", port = 6032, weight = 1 }
)
Algunas de las líneas de configuración anteriores se explican por sección a continuación:
variables_admin
Preste atención a las admin_credentials variable donde usamos un usuario no predeterminado que es "proxysql-admin". ProxySQL reserva el usuario "admin" predeterminado para la conexión local solo a través de localhost. Por lo tanto, tenemos que usar otros usuarios para acceder a la instancia de ProxySQL de forma remota. De lo contrario, obtendrías el siguiente error:
ERROR 1040 (42000): User 'admin' can only connect locally
También agregamos el cluster_username y contraseña_de_clúster valor en admin_credentials línea, separados por un punto y coma para permitir que ocurra la sincronización automática. Todas las variables con el prefijo cluster_* están relacionados con la agrupación en clústeres nativos de ProxySQL y se explican por sí mismos.
mysql_galera_hostgroups
Esta es una nueva directiva introducida para ProxySQL 2.x (nuestra imagen de ProxySQL se ejecuta en 2.0.5). Si desea ejecutar ProxySQL 1.x, elimine esta parte y use la tabla del programador en su lugar. Ya explicamos los detalles de configuración en esta publicación de blog, Cómo ejecutar y configurar ProxySQL 2.0 para MySQL Galera Cluster en Docker en "ProxySQL 2.x Support for Galera Cluster".
servidores_mysql
Todas las líneas se explican por sí mismas, que se basan en tres servidores de bases de datos que se ejecutan en MySQL Galera Cluster, como se resume en la siguiente captura de pantalla de topología tomada de ClusterControl:
servidores_proxysql
Aquí definimos una lista de pares de ProxySQL:
- hostname:nombre de host/dirección IP del compañero
- port:puerto de administración del compañero
- peso:actualmente no se usa, pero está en la hoja de ruta para futuras mejoras
- comentario:campo de comentario de forma libre
En el entorno de Docker/Kubernetes, hay varias formas de descubrir y vincular nombres de host de contenedores o direcciones IP e insertarlos en esta tabla, ya sea mediante ConfigMap, inserción manual, secuencias de comandos de entrypoint.sh, variables de entorno o algún otro medio. En Kubernetes, según el ReplicationController o el método de implementación utilizado, adivinar el nombre de host que se puede resolver del pod de antemano es algo complicado, a menos que se esté ejecutando en StatefulSet.
Consulte este tutorial sobre el índice ordinal de pod StatefulState que proporciona un nombre de host estable y resoluble para los pods creados. Combine esto con el servicio sin cabeza (explicado más abajo), el formato de nombre de host que se puede resolver sería:
{app_name}-{index_number}.{service}
Donde {servicio} es un servicio sin encabezado, lo que explica de dónde provienen "proxysql-0.proxysqlcluster" y "proxysql-1.proxysqlcluster". Si desea tener más de 2 réplicas, agregue más entradas en consecuencia agregando un número de índice ascendente relativo al nombre de la aplicación StatefulSet.
Ahora estamos listos para insertar el archivo de configuración en ConfigMap, que se montará en cada pod de ProxySQL durante la implementación:
$ kubectl create configmap proxysql-configmap --from-file=proxysql.cnf
Verifica si nuestro ConfigMap está cargado correctamente:
$ kubectl get configmap
NAME DATA AGE
proxysql-configmap 1 7h57m
Creación de un usuario de supervisión de ProxySQL
El siguiente paso antes de comenzar la implementación es crear un usuario de monitoreo de ProxySQL en nuestro clúster de base de datos. Dado que estamos ejecutando en el clúster de Galera, ejecute las siguientes declaraciones en uno de los nodos de Galera:
mysql> CREATE USER 'proxysql'@'%' IDENTIFIED BY 'proxysqlpassw0rd';
mysql> GRANT USAGE ON *.* TO 'proxysql'@'%';
Si no ha creado los usuarios de MySQL (como se especifica en la sección mysql_users anterior), también tenemos que crearlos:
mysql> CREATE USER 'wordpress'@'%' IDENTIFIED BY 'passw0rd';
mysql> GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'%';
mysql> CREATE USER 'sbtest'@'%' IDENTIFIED BY 'passw0rd';
mysql> GRANT ALL PRIVILEGES ON sbtest.* TO 'proxysql'@'%';
Eso es todo. Ahora estamos listos para comenzar la implementación.
Implementación de un StatefulSet
Comenzaremos creando dos instancias de ProxySQL o réplicas con fines de redundancia utilizando StatefulSet.
Comencemos creando un archivo de texto llamado proxysql-ss-svc.yml y agregue las siguientes líneas:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: proxysql
labels:
app: proxysql
spec:
replicas: 2
serviceName: proxysqlcluster
selector:
matchLabels:
app: proxysql
tier: frontend
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
app: proxysql
tier: frontend
spec:
restartPolicy: Always
containers:
- image: severalnines/proxysql:2.0.4
name: proxysql
volumeMounts:
- name: proxysql-config
mountPath: /etc/proxysql.cnf
subPath: proxysql.cnf
ports:
- containerPort: 6033
name: proxysql-mysql
- containerPort: 6032
name: proxysql-admin
volumes:
- name: proxysql-config
configMap:
name: proxysql-configmap
---
apiVersion: v1
kind: Service
metadata:
annotations:
labels:
app: proxysql
tier: frontend
name: proxysql
spec:
ports:
- name: proxysql-mysql
nodePort: 30033
port: 6033
protocol: TCP
targetPort: 6033
- name: proxysql-admin
nodePort: 30032
port: 6032
protocol: TCP
targetPort: 6032
selector:
app: proxysql
tier: frontend
type: NodePort
Hay dos secciones de la definición anterior:StatefulSet y Service. StatefulSet es la definición de nuestros pods o réplicas y el punto de montaje para nuestro volumen ConfigMap, cargado desde proxysql-configmap. La siguiente sección es la definición del servicio, donde definimos cómo se deben exponer y enrutar los pods para la red interna o externa.
Cree el conjunto y el servicio con estado de ProxySQL:
$ kubectl create -f proxysql-ss-svc.yml
Verifique los estados del pod y del servicio:
$ kubectl get pods,svc
NAME READY STATUS RESTARTS AGE
pod/proxysql-0 1/1 Running 0 4m46s
pod/proxysql-1 1/1 Running 0 2m59s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10h
service/proxysql NodePort 10.111.240.193 <none> 6033:30033/TCP,6032:30032/TCP 5m28s
Si observa el registro del pod, notará que nos inundaron con esta advertencia:
$ kubectl logs -f proxysql-0
...
2019-08-01 19:06:18 ProxySQL_Cluster.cpp:215:ProxySQL_Cluster_Monitor_thread(): [WARNING] Cluster: unable to connect to peer proxysql-1.proxysqlcluster:6032 . Error: Unknown MySQL server host 'proxysql-1.proxysqlcluster' (0)
Lo anterior simplemente significa que proxysql-0 no pudo resolver "proxysql-1.proxysqlcluster" y conectarse a él, lo cual se esperaba ya que no hemos creado nuestro servicio sin cabeza para los registros DNS que serán necesarios para la comunicación entre ProxySQL.
Servicio autónomo de Kubernetes
Para que los pods de ProxySQL puedan resolver el FQDN anticipado y conectarse a él directamente, el proceso de resolución debe poder buscar la dirección IP del pod de destino asignada y no la dirección IP virtual. Aquí es donde el servicio sin cabeza entra en escena. Al crear un servicio sin encabezado configurando "clusterIP=None", no se configura el equilibrio de carga y no se asigna ninguna IP de clúster (IP virtual) para este servicio. Solo el DNS se configura automáticamente. Cuando ejecuta una consulta de DNS para el servicio sin cabeza, obtendrá la lista de las direcciones IP de los pods.
Así es como se ve si buscamos los registros DNS del servicio sin cabeza para "proxysqlcluster" (en este ejemplo teníamos 3 instancias de ProxySQL):
$ host proxysqlcluster
proxysqlcluster.default.svc.cluster.local has address 10.40.0.2
proxysqlcluster.default.svc.cluster.local has address 10.40.0.3
proxysqlcluster.default.svc.cluster.local has address 10.32.0.2
Mientras que el siguiente resultado muestra el registro DNS para el servicio estándar llamado "proxysql" que se resuelve en la IP del clúster:
$ host proxysql
proxysql.default.svc.cluster.local has address 10.110.38.154
Para crear un servicio sin encabezado y adjuntarlo a los pods, se debe definir ServiceName dentro de la declaración StatefulSet, y la definición de servicio debe tener "clusterIP=None", como se muestra a continuación. Cree un archivo de texto llamado proxysql-headless-svc.yml y agregue las siguientes líneas:
apiVersion: v1
kind: Service
metadata:
name: proxysqlcluster
labels:
app: proxysql
spec:
clusterIP: None
ports:
- port: 6032
name: proxysql-admin
selector:
app: proxysql
Cree el servicio sin cabeza:
$ kubectl create -f proxysql-headless-svc.yml
Solo para verificación, en este momento, tenemos los siguientes servicios en ejecución:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 8h
proxysql NodePort 10.110.38.154 <none> 6033:30033/TCP,6032:30032/TCP 23m
proxysqlcluster ClusterIP None <none> 6032/TCP 4s
Ahora, echa un vistazo a uno de los registros de nuestro pod:
$ kubectl logs -f proxysql-0
...
2019-08-01 19:06:19 ProxySQL_Cluster.cpp:215:ProxySQL_Cluster_Monitor_thread(): [WARNING] Cluster: unable to connect to peer proxysql-1.proxysqlcluster:6032 . Error: Unknown MySQL server host 'proxysql-1.proxysqlcluster' (0)
2019-08-01 19:06:19 [INFO] Cluster: detected a new checksum for mysql_query_rules from peer proxysql-1.proxysqlcluster:6032, version 1, epoch 1564686376, checksum 0x3FEC69A5C9D96848 . Not syncing yet ...
2019-08-01 19:06:19 [INFO] Cluster: checksum for mysql_query_rules from peer proxysql-1.proxysqlcluster:6032 matches with local checksum 0x3FEC69A5C9D96848 , we won't sync.
Notará que el componente de clúster puede resolver, conectar y detectar una nueva suma de verificación del otro par, proxysql-1.proxysqlcluster en el puerto 6032 a través del servicio sin cabeza llamado "proxysqlcluster". Tenga en cuenta que este servicio expone el puerto 6032 solo dentro de la red de Kubernetes, por lo que no se puede acceder a él desde el exterior.
En este punto, nuestra implementación ya está completa.
Conexión a ProxySQL
Hay varias formas de conectarse a los servicios de ProxySQL. Las conexiones de MySQL con equilibrio de carga deben enviarse al puerto 6033 desde dentro de la red de Kubernetes y usar el puerto 30033 si el cliente se conecta desde una red externa.
Para conectarnos a la interfaz de administración de ProxySQL desde una red externa, podemos conectarnos al puerto definido en la sección NodePort, 30032 (192.168.100.203 es la dirección IP principal del host kube3.local):
$ mysql -uproxysql-admin -padminpassw0rd -h192.168.100.203 -P30032
Use el clusterIP 10.110.38.154 (definido en el servicio "proxysql") en el puerto 6032 si desea acceder desde otros pods en la red de Kubernetes.
Luego realice los cambios de configuración de ProxySQL como desee y cárguelos en tiempo de ejecución:
mysql> INSERT INTO mysql_users (username,password,default_hostgroup) VALUES ('newuser','passw0rd',10);
mysql> LOAD MYSQL USERS TO RUNTIME;
Notará las siguientes líneas en uno de los pods que indican que se completó la sincronización de la configuración:
$ kubectl logs -f proxysql-0
...
2019-08-02 03:53:48 [INFO] Cluster: detected a peer proxysql-1.proxysqlcluster:6032 with mysql_users version 2, epoch 1564718027, diff_check 4. Own version: 1, epoch: 1564714803. Proceeding with remote sync
2019-08-02 03:53:48 [INFO] Cluster: detected peer proxysql-1.proxysqlcluster:6032 with mysql_users version 2, epoch 1564718027
2019-08-02 03:53:48 [INFO] Cluster: Fetching MySQL Users from peer proxysql-1.proxysqlcluster:6032 started
2019-08-02 03:53:48 [INFO] Cluster: Fetching MySQL Users from peer proxysql-1.proxysqlcluster:6032 completed
Tenga en cuenta que la sincronización automática solo ocurre si hay un cambio de configuración en el tiempo de ejecución de ProxySQL. Por lo tanto, es fundamental ejecutar la declaración "LOAD ... TO RUNTIME" antes de poder ver la acción. No olvide guardar los cambios de ProxySQL en el disco para persistencia:
mysql> SAVE MYSQL USERS TO DISK;
Limitación
Tenga en cuenta que existe una limitación para esta configuración debido a que ProxySQL no admite guardar/exportar la configuración activa en un archivo de configuración de texto que podríamos usar más adelante para cargar en ConfigMap para la persistencia. Hay una solicitud de función para esto. Mientras tanto, puede enviar las modificaciones a ConfigMap manualmente. De lo contrario, si los pods se eliminaran accidentalmente, perdería su configuración actual porque los nuevos pods se iniciarían según lo definido en el ConfigMap.
Un agradecimiento especial a Sampath Kamineni, quien generó la idea de esta publicación de blog y brindó información sobre los casos de uso y la implementación.