MariaDB
 sql >> Base de données >  >> RDS >> MariaDB

Clustering ProxySQL natif avec Kubernetes

ProxySQL prend en charge le clustering natif depuis la v1.4.2. Cela signifie que plusieurs instances de ProxySQL sont compatibles avec les clusters ; ils sont conscients de l'état de chacun et capables de gérer automatiquement les changements de configuration en se synchronisant avec la configuration la plus récente en fonction de la version de la configuration, de l'horodatage et de la valeur de la somme de contrôle. Consultez cet article de blog qui montre comment configurer la prise en charge du clustering pour ProxySQL et comment vous pouvez vous attendre à ce qu'il se comporte.

ProxySQL est un proxy décentralisé, recommandé pour être déployé au plus près de l'application. Cette approche s'adapte assez bien même à des centaines de nœuds, car elle a été conçue pour être facilement reconfigurable lors de l'exécution. Pour gérer efficacement plusieurs nœuds ProxySQL, il faut s'assurer que toutes les modifications effectuées sur l'un des nœuds doivent être appliquées à tous les nœuds de la batterie. Sans clustering natif, il faut exporter manuellement les configurations et les importer vers les autres nœuds (bien que vous puissiez automatiser cela par vous-même).

Dans le précédent article de blog, nous avons couvert le clustering ProxySQL via Kubernetes ConfigMap. Cette approche est plus ou moins assez efficace avec l'approche de configuration centralisée dans ConfigMap. Tout ce qui est chargé dans ConfigMap sera monté dans des pods. La mise à jour de la configuration peut être effectuée via la gestion des versions (modifiez le contenu proxysql.cnf et chargez-le dans ConfigMap avec un autre nom), puis poussez vers les pods en fonction de la planification de la méthode de déploiement et de la stratégie de mise à jour.

Cependant, dans un environnement en évolution rapide, cette approche ConfigMap n'est probablement pas la meilleure méthode car pour charger la nouvelle configuration, la replanification des pods est nécessaire pour remonter le volume ConfigMap et cela pourrait compromettre le service ProxySQL dans son ensemble. Par exemple, disons que dans notre environnement, notre politique de mot de passe stricte nécessite de forcer l'expiration du mot de passe de l'utilisateur MySQL tous les 7 jours, ce que nous devrions continuer à mettre à jour le ProxySQL ConfigMap pour le nouveau mot de passe sur une base hebdomadaire. En remarque, l'utilisateur MySQL dans ProxySQL nécessite que l'utilisateur et le mot de passe correspondent à ceux des serveurs MySQL principaux. C'est là que nous devrions commencer à utiliser la prise en charge du clustering natif ProxySQL dans Kubernetes, pour appliquer automatiquement les modifications de configuration sans les tracas de la gestion des versions de ConfigMap et de la reprogrammation des pods.

Dans cet article de blog, nous allons vous montrer comment exécuter le clustering natif ProxySQL avec un service sans tête sur Kubernetes. Notre architecture de haut niveau peut être illustrée ci-dessous :

Nous avons 3 nœuds Galera fonctionnant sur une infrastructure bare-metal déployée et gérée par ClusterControl :

  • 192.168.0.21
  • 192.168.0.22
  • 192.168.0.23

Nos applications s'exécutent toutes en tant que pods dans Kubernetes. L'idée est d'introduire deux instances ProxySQL entre l'application et notre cluster de base de données pour servir de proxy inverse. Les applications se connecteront ensuite aux pods ProxySQL via le service Kubernetes qui sera équilibré en charge et basculera sur un certain nombre de répliques ProxySQL.

Voici un résumé de notre configuration 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

Configuration ProxySQL via ConfigMap

Préparons d'abord notre configuration de base qui sera chargée dans ConfigMap. Créez un fichier appelé proxysql.cnf et ajoutez les lignes suivantes :

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 }
)

Certaines des lignes de configuration ci-dessus sont expliquées par section ci-dessous :

variables_admin

Faites attention aux admin_credentials variable où nous avons utilisé l'utilisateur non par défaut qui est "proxysql-admin". ProxySQL réserve l'utilisateur "admin" par défaut pour la connexion locale via localhost uniquement. Par conséquent, nous devons utiliser d'autres utilisateurs pour accéder à distance à l'instance ProxySQL. Sinon, vous obtiendrez l'erreur suivante :

ERROR 1040 (42000): User 'admin' can only connect locally

Nous avons également ajouté le cluster_username et cluster_password valeur dans les admin_credentials ligne, séparés par un point-virgule pour permettre la synchronisation automatique. Toutes les variables préfixées par cluster_* sont liés au clustering natif ProxySQL et sont explicites.

mysql_galera_hostgroups

Il s'agit d'une nouvelle directive introduite pour ProxySQL 2.x (notre image ProxySQL s'exécute sur 2.0.5). Si vous souhaitez exécuter ProxySQL 1.x, supprimez cette partie et utilisez la table du planificateur à la place. Nous avons déjà expliqué les détails de configuration dans cet article de blog, Comment exécuter et configurer ProxySQL 2.0 pour MySQL Galera Cluster sur Docker sous "Prise en charge de ProxySQL 2.x pour Galera Cluster".

mysql_servers

Toutes les lignes sont explicites, basées sur trois serveurs de base de données exécutés dans MySQL Galera Cluster, comme résumé dans la capture d'écran Topology suivante tirée de ClusterControl :

proxysql_servers

Ici, nous définissons une liste de pairs ProxySQL :

  • nom d'hôte :nom d'hôte/adresse IP du pair
  • port - Port d'administration du pair
  • pondération - Actuellement inutilisé, mais dans la feuille de route pour de futures améliorations
  • commentaire - Champ de commentaire de forme libre

Dans l'environnement Docker/Kubernetes, il existe plusieurs façons de découvrir et de lier les noms d'hôte ou les adresses IP des conteneurs et de les insérer dans cette table, soit en utilisant ConfigMap, une insertion manuelle, via des scripts entrypoint.sh, des variables d'environnement ou d'autres moyens. Dans Kubernetes, selon la méthode ReplicationController ou Deployment utilisée, deviner à l'avance le nom d'hôte résoluble du pod est quelque peu délicat, sauf si vous utilisez StatefulSet.

Consultez ce didacticiel sur l'index ordinal de pod StatefulState qui fournit un nom d'hôte résoluble stable pour les pods créés. Combinez cela avec le service sans tête (expliqué plus bas), le format de nom d'hôte résoluble serait :

{app_name}-{index_number}.{service}

Où {service} est un service sans tête, ce qui explique d'où viennent "proxysql-0.proxysqlcluster" et "proxysql-1.proxysqlcluster". Si vous souhaitez avoir plus de 2 répliques, ajoutez plus d'entrées en conséquence en ajoutant un numéro d'index croissant relatif au nom de l'application StatefulSet.

Nous sommes maintenant prêts à envoyer le fichier de configuration dans ConfigMap, qui sera monté dans chaque pod ProxySQL lors du déploiement :

$ kubectl create configmap proxysql-configmap --from-file=proxysql.cnf

Vérifiez si notre ConfigMap est chargé correctement :

$ kubectl get configmap
NAME                 DATA   AGE
proxysql-configmap   1      7h57m

Création d'un utilisateur de surveillance ProxySQL

La prochaine étape avant de commencer le déploiement consiste à créer un utilisateur de surveillance ProxySQL dans notre cluster de base de données. Puisque nous exécutons sur le cluster Galera, exécutez les instructions suivantes sur l'un des nœuds Galera :

mysql> CREATE USER 'proxysql'@'%' IDENTIFIED BY 'proxysqlpassw0rd';
mysql> GRANT USAGE ON *.* TO 'proxysql'@'%';

Si vous n'avez pas créé les utilisateurs MySQL (comme spécifié dans la section mysql_users ci-dessus), nous devons également les créer :

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'@'%';

C'est ça. Nous sommes maintenant prêts à démarrer le déploiement.

Déployer un StatefulSet

Nous allons commencer par créer deux instances ProxySQL, ou répliques à des fins de redondance à l'aide de StatefulSet.

Commençons par créer un fichier texte appelé proxysql-ss-svc.yml et ajoutons les lignes suivantes :

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

Il y a deux sections de la définition ci-dessus - StatefulSet et Service. Le StatefulSet est la définition de nos pods ou répliques et le point de montage de notre volume ConfigMap, chargé à partir de proxysql-configmap. La section suivante est la définition du service, où nous définissons comment les pods doivent être exposés et acheminés pour le réseau interne ou externe.

Créez l'ensemble et le service avec état ProxySQL :

$ kubectl create -f proxysql-ss-svc.yml

Vérifiez les états du pod et du service :

$ 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 vous regardez le journal du pod, vous remarquerez que nous avons été inondés de cet avertissement :

$ 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)

Ce qui précède signifie simplement que proxysql-0 n'a pas pu résoudre "proxysql-1.proxysqlcluster" et s'y connecter, ce qui est attendu puisque nous n'avons pas créé notre service sans tête pour les enregistrements DNS qui seront nécessaires pour la communication inter-ProxySQL.

Service sans tête Kubernetes

Pour que les pods ProxySQL puissent résoudre le FQDN anticipé et s'y connecter directement, le processus de résolution doit pouvoir rechercher l'adresse IP du pod cible attribuée et non l'adresse IP virtuelle. C'est là que le service sans tête entre en scène. Lors de la création d'un service sans tête en définissant "clusterIP=None", aucun équilibrage de charge n'est configuré et aucune adresse IP de cluster (adresse IP virtuelle) n'est allouée pour ce service. Seul le DNS est configuré automatiquement. Lorsque vous exécutez une requête DNS pour un service sans tête, vous obtenez la liste des adresses IP des pods.

Voici à quoi cela ressemble si nous recherchons les enregistrements DNS du service sans tête pour "proxysqlcluster" (dans cet exemple, nous avions 3 instances 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

Alors que la sortie suivante montre l'enregistrement DNS pour le service standard appelé "proxysql" qui se résout en clusterIP :

$ host proxysql
proxysql.default.svc.cluster.local has address 10.110.38.154

Pour créer un service sans tête et l'attacher aux pods, il faut définir le ServiceName dans la déclaration StatefulSet, et la définition du service doit avoir "clusterIP=None" comme indiqué ci-dessous. Créez un fichier texte appelé proxysql-headless-svc.yml et ajoutez les lignes suivantes :

apiVersion: v1
kind: Service
metadata:
  name: proxysqlcluster
  labels:
    app: proxysql
spec:
  clusterIP: None
  ports:
  - port: 6032
    name: proxysql-admin
  selector:
    app: proxysql

Créez le service sans tête :

$ kubectl create -f proxysql-headless-svc.yml

Juste pour vérification, à ce stade, nous avons les services suivants en cours d'exécution :

$ 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

Maintenant, consultez le journal de l'un de nos modules :

$ 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.

Vous remarquerez que le composant Cluster est capable de résoudre, connecter et détecter une nouvelle somme de contrôle de l'autre pair, proxysql-1.proxysqlcluster sur le port 6032 via le service sans tête appelé "proxysqlcluster". Notez que ce service expose le port 6032 au sein du réseau Kubernetes uniquement, il est donc inaccessible de l'extérieur.

À ce stade, notre déploiement est maintenant terminé.

Connexion à ProxySQL

Il existe plusieurs façons de se connecter aux services ProxySQL. Les connexions MySQL à charge équilibrée doivent être envoyées au port 6033 depuis le réseau Kubernetes et utiliser le port 30033 si le client se connecte depuis un réseau externe.

Pour se connecter à l'interface d'administration ProxySQL à partir d'un réseau externe, nous pouvons nous connecter au port défini dans la section NodePort, 30032 (192.168.100.203 est l'adresse IP principale de l'hôte kube3.local) :

$ mysql -uproxysql-admin -padminpassw0rd -h192.168.100.203 -P30032

Utilisez le clusterIP 10.110.38.154 (défini sous le service "proxysql") sur le port 6032 si vous souhaitez y accéder à partir d'autres pods du réseau Kubernetes.

Effectuez ensuite les modifications de configuration de ProxySQL comme vous le souhaitez et chargez-les dans le runtime :

mysql> INSERT INTO mysql_users (username,password,default_hostgroup) VALUES ('newuser','passw0rd',10);
mysql> LOAD MYSQL USERS TO RUNTIME;

Vous remarquerez les lignes suivantes dans l'un des modules indiquant que la synchronisation de la configuration est terminée :

$ 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

Gardez à l'esprit que la synchronisation automatique ne se produit que s'il y a un changement de configuration dans le runtime ProxySQL. Par conséquent, il est essentiel d'exécuter l'instruction "LOAD ... TO RUNTIME" avant de pouvoir voir l'action. N'oubliez pas d'enregistrer les modifications ProxySQL sur le disque pour la persistance :

mysql> SAVE MYSQL USERS TO DISK;

Limites

Notez qu'il existe une limitation à cette configuration car ProxySQL ne prend pas en charge l'enregistrement/l'exportation de la configuration active dans un fichier de configuration texte que nous pourrions utiliser ultérieurement pour charger dans ConfigMap pour la persistance. Il y a une demande de fonctionnalité pour cela. Pendant ce temps, vous pouvez pousser manuellement les modifications vers ConfigMap. Sinon, si les pods étaient accidentellement supprimés, vous perdriez votre configuration actuelle car les nouveaux pods seraient amorcés par tout ce qui est défini dans le ConfigMap.

Un merci spécial à Sampath Kamineni, qui a suscité l'idée de ce billet de blog et fourni des informations sur les cas d'utilisation et la mise en œuvre.