sql >> Base de Datos >  >> RDS >> MariaDB

Clúster nativo de ProxySQL con Kubernetes

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.