ProxySQL supporta il clustering nativo dalla v1.4.2. Ciò significa che più istanze ProxySQL sono compatibili con il cluster; sono a conoscenza dello stato dell'altro e sono in grado di gestire automaticamente le modifiche alla configurazione sincronizzandosi con la configurazione più aggiornata in base alla versione della configurazione, al timestamp e al valore del checksum. Dai un'occhiata a questo post del blog che mostra come configurare il supporto del clustering per ProxySQL e come potresti aspettarti che si comporti.
ProxySQL è un proxy decentralizzato, consigliato per essere distribuito più vicino all'applicazione. Questo approccio scala abbastanza bene anche fino a centinaia di nodi, poiché è stato progettato per essere facilmente riconfigurabile in fase di esecuzione. Per gestire in modo efficiente più nodi ProxySQL, è necessario assicurarsi che tutte le modifiche eseguite su uno dei nodi vengano applicate a tutti i nodi della farm. Senza il clustering nativo, è necessario esportare manualmente le configurazioni e importarle negli altri nodi (anche se potresti automatizzarlo da solo).
Nel precedente post del blog, abbiamo trattato il clustering ProxySQL tramite Kubernetes ConfigMap. Questo approccio è più o meno efficiente con l'approccio di configurazione centralizzata in ConfigMap. Qualunque cosa sia caricata in ConfigMap verrà montata nei pod. L'aggiornamento della configurazione può essere eseguito tramite il controllo delle versioni (modifica il contenuto proxysql.cnf e caricalo in ConfigMap con un altro nome) e quindi invialo ai pod in base alla pianificazione del metodo di distribuzione e alla strategia di aggiornamento.
Tuttavia, in un ambiente in rapida evoluzione, questo approccio ConfigMap non è probabilmente il metodo migliore perché per caricare la nuova configurazione, è necessaria la riprogrammazione del pod per rimontare il volume ConfigMap e ciò potrebbe compromettere il servizio ProxySQL nel suo insieme. Ad esempio, diciamo nel nostro ambiente, la nostra rigorosa politica delle password richiede di forzare la scadenza della password utente MySQL ogni 7 giorni, che dovremmo continuare ad aggiornare ProxySQL ConfigMap per la nuova password su base settimanale. Come nota a margine, l'utente MySQL all'interno di ProxySQL richiede che l'utente e la password corrispondano a quelli dei server MySQL di back-end. È qui che dovremmo iniziare a utilizzare il supporto del clustering nativo ProxySQL in Kubernetes, per applicare automaticamente le modifiche alla configurazione senza il fastidio del controllo delle versioni di ConfigMap e della riprogrammazione del pod.
In questo post del blog, ti mostreremo come eseguire il clustering nativo ProxySQL con il servizio headless su Kubernetes. La nostra architettura di alto livello può essere illustrata come segue:

Abbiamo 3 nodi Galera in esecuzione su un'infrastruttura bare-metal distribuita e gestita da ClusterControl:
- 192.168.0.21
- 192.168.0.22
- 192.168.0.23
Le nostre applicazioni funzionano tutte come pod all'interno di Kubernetes. L'idea è di introdurre due istanze ProxySQL tra l'applicazione e il nostro cluster di database per fungere da proxy inverso. Le applicazioni si collegheranno quindi ai pod ProxySQL tramite il servizio Kubernetes che verrà bilanciato e verrà eseguito il failover su una serie di repliche ProxySQL.
Di seguito è riportato un riepilogo della nostra configurazione Kubernetes:
example@sqldat.com:~# 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
Configurazione proxySQL tramite ConfigMap
Per prima cosa prepariamo la nostra configurazione di base che verrà caricata in ConfigMap. Crea un file chiamato proxysql.cnf e aggiungi le seguenti righe:
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 }
)
Alcune delle righe di configurazione di cui sopra sono spiegate nella sezione seguente:
variabili_admin
Presta attenzione alle credenziali_admin variabile in cui abbiamo utilizzato un utente non predefinito che è "proxysql-admin". ProxySQL riserva l'utente "admin" predefinito solo per la connessione locale tramite localhost. Pertanto, dobbiamo utilizzare altri utenti per accedere all'istanza ProxySQL in remoto. In caso contrario, avresti il seguente errore:
ERROR 1040 (42000): User 'admin' can only connect locally
Abbiamo anche aggiunto il cluster_username e password_cluster valore nelle credenziali_admin linea, separata da un punto e virgola per consentire la sincronizzazione automatica. Tutte le variabili precedute da cluster_* sono correlati al clustering nativo di ProxySQL e sono autoesplicativi.
mysql_galera_hostgroups
Questa è una nuova direttiva introdotta per ProxySQL 2.x (la nostra immagine ProxySQL è in esecuzione su 2.0.5). Se si desidera eseguire su ProxySQL 1.x, rimuovere questa parte e utilizzare invece la tabella di pianificazione. Abbiamo già spiegato i dettagli di configurazione in questo post del blog, Come eseguire e configurare ProxySQL 2.0 per MySQL Galera Cluster su Docker in "Supporto ProxySQL 2.x per Galera Cluster".
server_mysql
Tutte le righe sono autoesplicative, che si basano su tre server di database in esecuzione in MySQL Galera Cluster, come riepilogato nella seguente schermata della topologia presa da ClusterControl:

server_proxysql
Qui definiamo un elenco di peer ProxySQL:
- nome host - Nome host/indirizzo IP del peer
- porta - Porta di amministrazione del peer
- peso - Attualmente non utilizzato, ma nella tabella di marcia per miglioramenti futuri
- commento - Campo di commento in formato libero
Nell'ambiente Docker/Kubernetes, esistono diversi modi per scoprire e collegare i nomi host o gli indirizzi IP dei container e inserirli in questa tabella, utilizzando ConfigMap, l'inserimento manuale, tramite lo scripting entrypoint.sh, le variabili di ambiente o altri mezzi. In Kubernetes, a seconda del metodo ReplicationController o Deployment utilizzato, indovinare in anticipo il nome host risolvibile del pod è alquanto complicato a meno che non si stia eseguendo StatefulSet.
Dai un'occhiata a questo tutorial sull'indice ordinale del pod StatefulState che fornisce un nome host risolvibile stabile per i pod creati. Combina questo con il servizio senza testa (spiegato più in basso), il formato del nome host risolvibile sarebbe:
{nome_app}-{numero_indice}.{servizio}
Dove {service} è un servizio senza testa, che spiega da dove provengono "proxysql-0.proxysqlcluster" e "proxysql-1.proxysqlcluster". Se desideri avere più di 2 repliche, aggiungi più voci di conseguenza aggiungendo un numero di indice crescente rispetto al nome dell'applicazione StatefulSet.
Ora siamo pronti per eseguire il push del file di configurazione in ConfigMap, che verrà montato in ogni pod ProxySQL durante la distribuzione:
$ kubectl create configmap proxysql-configmap --from-file=proxysql.cnf
Verifica se la nostra ConfigMap è caricata correttamente:
$ kubectl get configmap
NAME DATA AGE
proxysql-configmap 1 7h57m
Creazione dell'utente di monitoraggio ProxySQL
Il passaggio successivo prima di iniziare la distribuzione consiste nel creare un utente di monitoraggio ProxySQL nel nostro cluster di database. Poiché siamo in esecuzione sul cluster Galera, esegui le seguenti istruzioni su uno dei nodi Galera:
mysql> CREATE USER 'proxysql'@'%' IDENTIFIED BY 'proxysqlpassw0rd';
mysql> GRANT USAGE ON *.* TO 'proxysql'@'%';
Se non hai creato gli utenti MySQL (come specificato nella sezione mysql_users sopra), dobbiamo creare anche loro:
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'@'%';
Questo è tutto. Ora siamo pronti per iniziare la distribuzione.
Distribuzione di uno StatefulSet
Inizieremo creando due istanze ProxySQL o repliche per scopi di ridondanza utilizzando StatefulSet.
Iniziamo creando un file di testo chiamato proxysql-ss-svc.yml e aggiungiamo le seguenti righe:
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
Esistono due sezioni della definizione precedente:StatefulSet e Service. StatefulSet è la definizione dei nostri pod o repliche e il punto di montaggio per il nostro volume ConfigMap, caricato da proxysql-configmap. La sezione successiva è la definizione del servizio, in cui definiamo come i pod devono essere esposti e instradati per la rete interna o esterna.
Crea il set e il servizio con stato ProxySQL:
$ kubectl create -f proxysql-ss-svc.yml
Verifica gli stati del pod e del servizio:
$ 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
Se guardi il registro del pod, noterai che siamo stati inondati da questo avviso:
$ 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)
Quanto sopra significa semplicemente che proxysql-0 non è stato in grado di risolvere "proxysql-1.proxysqlcluster" e connettersi ad esso, il che è previsto poiché non abbiamo creato il nostro servizio headless per i record DNS che saranno necessari per la comunicazione tra ProxySQL.
Servizio senza testa Kubernetes
Affinché i pod ProxySQL possano risolvere l'FQDN previsto e connettersi ad esso direttamente, il processo di risoluzione deve essere in grado di cercare l'indirizzo IP del pod di destinazione assegnato e non l'indirizzo IP virtuale. È qui che entra in gioco il servizio senza testa. Quando si crea un servizio headless impostando "clusterIP=None", non viene configurato alcun bilanciamento del carico e non viene allocato alcun IP cluster (IP virtuale) per questo servizio. Solo il DNS viene configurato automaticamente. Quando esegui una query DNS per il servizio headless, otterrai l'elenco degli indirizzi IP dei pod.
Ecco come appare se cerchiamo i record DNS del servizio senza testa per "proxysqlcluster" (in questo esempio abbiamo 3 istanze 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
Mentre l'output seguente mostra il record DNS per il servizio standard chiamato "proxysql" che si risolve nel clusterIP:
$ host proxysql
proxysql.default.svc.cluster.local has address 10.110.38.154
Per creare un servizio senza testa e collegarlo ai pod, è necessario definire ServiceName all'interno della dichiarazione StatefulSet e la definizione del servizio deve avere "clusterIP=None" come mostrato di seguito. Crea un file di testo chiamato proxysql-headless-svc.yml e aggiungi le seguenti righe:
apiVersion: v1
kind: Service
metadata:
name: proxysqlcluster
labels:
app: proxysql
spec:
clusterIP: None
ports:
- port: 6032
name: proxysql-admin
selector:
app: proxysql
Crea il servizio senza testa:
$ kubectl create -f proxysql-headless-svc.yml
Solo per verifica, a questo punto, abbiamo i seguenti servizi in esecuzione:
$ 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
Ora, controlla uno dei registri del nostro 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.
Si noterà che il componente Cluster è in grado di risolvere, connettere e rilevare un nuovo checksum dall'altro peer, proxysql-1.proxysqlcluster sulla porta 6032 tramite il servizio headless chiamato "proxysqlcluster". Tieni presente che questo servizio espone la porta 6032 solo all'interno della rete Kubernetes, quindi non è raggiungibile esternamente.
A questo punto, la nostra distribuzione è ora completa.
Connessione a ProxySQL
Esistono diversi modi per connettersi ai servizi ProxySQL. Le connessioni MySQL con bilanciamento del carico devono essere inviate alla porta 6033 dall'interno della rete Kubernetes e utilizzare la porta 30033 se il client si connette da una rete esterna.
Per connetterci all'interfaccia di amministrazione di ProxySQL da una rete esterna, possiamo connetterci alla porta definita nella sezione NodePort, 30032 (192.168.100.203 è l'indirizzo IP principale dell'host kube3.local):
$ mysql -uproxysql-admin -padminpassw0rd -h192.168.100.203 -P30032
Utilizza clusterIP 10.110.38.154 (definito nel servizio "proxysql") sulla porta 6032 se desideri accedervi da altri pod nella rete Kubernetes.
Quindi esegui le modifiche alla configurazione di ProxySQL come desideri e caricale in runtime:
mysql> INSERT INTO mysql_users (username,password,default_hostgroup) VALUES ('newuser','passw0rd',10);
mysql> LOAD MYSQL USERS TO RUNTIME;
Noterai le seguenti righe in uno dei pod che indicano che la sincronizzazione della configurazione è stata completata:
$ 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
Tieni presente che la sincronizzazione automatica avviene solo se è presente una modifica della configurazione nel runtime di ProxySQL. Pertanto, è fondamentale eseguire l'istruzione "LOAD ... TO RUNTIME" prima di poter vedere l'azione. Non dimenticare di salvare le modifiche ProxySQL nel disco per la persistenza:
mysql> SAVE MYSQL USERS TO DISK;
Limitazione
Si noti che esiste una limitazione a questa configurazione a causa del fatto che ProxySQL non supporta il salvataggio/esportazione della configurazione attiva in un file di configurazione di testo che potremmo utilizzare in seguito per caricare in ConfigMap per la persistenza. C'è una richiesta di funzionalità per questo. Nel frattempo, puoi inviare manualmente le modifiche a ConfigMap. In caso contrario, se i pod venissero eliminati accidentalmente, perderesti la configurazione corrente perché i nuovi pod verrebbero avviati da qualsiasi cosa definita in ConfigMap.
Un ringraziamento speciale a Sampath Kamineni, che ha dato vita all'idea di questo post sul blog e ha fornito approfondimenti sui casi d'uso e sull'implementazione.