MariaDB
 sql >> Database >  >> RDS >> MariaDB

Clustering nativo di ProxySQL con Kubernetes

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:

[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

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.