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.