MariaDB
 sql >> Datenbank >  >> RDS >> MariaDB

Natives ProxySQL-Clustering mit Kubernetes

ProxySQL unterstützt natives Clustering seit v1.4.2. Das bedeutet, dass mehrere ProxySQL-Instanzen clusterfähig sind; Sie kennen den Status des jeweils anderen und sind in der Lage, die Konfigurationsänderungen automatisch zu handhaben, indem sie die aktuellste Konfiguration basierend auf Konfigurationsversion, Zeitstempel und Prüfsummenwert synchronisieren. Sehen Sie sich diesen Blogbeitrag an, der zeigt, wie Sie die Clustering-Unterstützung für ProxySQL konfigurieren und wie Sie sich davon erwarten können.

ProxySQL ist ein dezentraler Proxy, der empfohlen wird, näher an der Anwendung bereitgestellt zu werden. Dieser Ansatz lässt sich ziemlich gut skalieren, sogar bis zu Hunderten von Knoten, da er so konzipiert wurde, dass er zur Laufzeit einfach neu konfiguriert werden kann. Um mehrere ProxySQL-Knoten effizient zu verwalten, muss sichergestellt werden, dass alle Änderungen, die an einem der Knoten vorgenommen werden, auf alle Knoten in der Farm angewendet werden. Ohne natives Clustering muss man die Konfigurationen manuell exportieren und sie in die anderen Knoten importieren (obwohl Sie dies selbst automatisieren könnten).

Im vorherigen Blogbeitrag haben wir ProxySQL-Clustering über Kubernetes ConfigMap behandelt. Dieser Ansatz ist mit dem zentralisierten Konfigurationsansatz in ConfigMap mehr oder weniger effizient. Was auch immer in ConfigMap geladen wird, wird in Pods gemountet. Die Aktualisierung der Konfiguration kann über die Versionierung erfolgen (ändern Sie den Inhalt der Datei „proxysql.cnf“ und laden Sie ihn unter einem anderen Namen in die ConfigMap) und übertragen Sie ihn dann je nach Planung der Bereitstellungsmethode und Aktualisierungsstrategie auf die Pods.

In einer sich schnell ändernden Umgebung ist dieser ConfigMap-Ansatz jedoch wahrscheinlich nicht die beste Methode, da zum Laden der neuen Konfiguration eine Pod-Neuplanung erforderlich ist, um das ConfigMap-Volume erneut bereitzustellen, und dies könnte den ProxySQL-Dienst als Ganzes gefährden. Angenommen, in unserer Umgebung erfordert unsere strenge Kennwortrichtlinie, dass das MySQL-Benutzerkennwort alle 7 Tage abläuft, wodurch wir die ProxySQL-Konfigurationskarte für das neue Kennwort wöchentlich aktualisieren müssten. Als Nebenbemerkung benötigt der MySQL-Benutzer in ProxySQL Benutzer und Passwort, die mit denen auf den Backend-MySQL-Servern übereinstimmen. Hier sollten wir damit beginnen, die native ProxySQL-Clustering-Unterstützung in Kubernetes zu nutzen, um die Konfigurationsänderungen automatisch anzuwenden, ohne den Aufwand der ConfigMap-Versionierung und Pod-Neuplanung.

In diesem Blogbeitrag zeigen wir Ihnen, wie Sie natives ProxySQL-Clustering mit Headless-Service auf Kubernetes ausführen. Unsere High-Level-Architektur kann wie folgt dargestellt werden:

Wir haben 3 Galera-Knoten, die auf einer Bare-Metal-Infrastruktur laufen, die von ClusterControl bereitgestellt und verwaltet wird:

  • 192.168.0.21
  • 192.168.0.22
  • 192.168.0.23

Unsere Anwendungen werden alle als Pods in Kubernetes ausgeführt. Die Idee ist, zwei ProxySQL-Instanzen zwischen der Anwendung und unserem Datenbank-Cluster einzuführen, die als Reverse-Proxy dienen. Anwendungen werden dann über den Kubernetes-Dienst mit ProxySQL-Pods verbunden, der Lastenausgleich und Failover über eine Reihe von ProxySQL-Replikaten hinweg ausführt.

Im Folgenden finden Sie eine Zusammenfassung unseres Kubernetes-Setups:

[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

ProxySQL-Konfiguration über ConfigMap

Lassen Sie uns zuerst unsere Basiskonfiguration vorbereiten, die in ConfigMap geladen wird. Erstellen Sie eine Datei namens proxysql.cnf und fügen Sie die folgenden Zeilen hinzu:

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

Einige der obigen Konfigurationszeilen werden im folgenden Abschnitt erklärt:

admin_variables

Achten Sie auf die admin_credentials Variable, bei der wir den nicht standardmäßigen Benutzer "proxysql-admin" verwendet haben. ProxySQL reserviert den Standardbenutzer „admin“ nur für die lokale Verbindung über localhost. Daher müssen wir andere Benutzer verwenden, um remote auf die ProxySQL-Instanz zuzugreifen. Andernfalls erhalten Sie die folgende Fehlermeldung:

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

Wir haben auch den cluster_username angehängt und cluster_password Wert in den admin_credentials Zeile, getrennt durch ein Semikolon, um eine automatische Synchronisierung zu ermöglichen. Alle Variablen mit dem Präfix cluster_* beziehen sich auf das native ProxySQL-Clustering und sind selbsterklärend.

mysql_galera_hostgroups

Dies ist eine neue Direktive, die für ProxySQL 2.x eingeführt wurde (unser ProxySQL-Image läuft auf 2.0.5). Wenn Sie ProxySQL 1.x ausführen möchten, entfernen Sie diesen Teil und verwenden Sie stattdessen die Scheduler-Tabelle. Die Konfigurationsdetails haben wir bereits in diesem Blog-Beitrag How to Run and Configure ProxySQL 2.0 for MySQL Galera Cluster on Docker unter „ProxySQL 2.x Support for Galera Cluster“ erklärt.

mysql_server

Alle Zeilen sind selbsterklärend, was auf drei Datenbankservern basiert, die in MySQL Galera Cluster ausgeführt werden, wie im folgenden Topologie-Screenshot von ClusterControl zusammengefasst:

proxysql_server

Hier definieren wir eine Liste von ProxySQL-Peers:

  • hostname - Hostname/IP-Adresse des Peers
  • port - Admin-Port des Peers
  • Gewicht - Derzeit ungenutzt, aber in der Roadmap für zukünftige Verbesserungen
  • comment - Kommentarfeld in freier Form

In der Docker/Kubernetes-Umgebung gibt es mehrere Möglichkeiten, Container-Hostnamen oder IP-Adressen zu erkennen und zu verknüpfen und sie in diese Tabelle einzufügen, entweder mithilfe von ConfigMap, manueller Einfügung, über entrypoint.sh-Skripting, Umgebungsvariablen oder auf andere Weise. In Kubernetes ist es je nach verwendeter ReplicationController- oder Deployment-Methode etwas schwierig, den auflösbaren Hostnamen des Pods im Voraus zu erraten, es sei denn, Sie arbeiten mit StatefulSet.

Sehen Sie sich dieses Tutorial zum StatefulState-Pod-Ordinalindex an, der einen stabilen auflösbaren Hostnamen für die erstellten Pods bereitstellt. Kombinieren Sie dies mit dem Headless-Service (weiter unten erklärt), das Format des auflösbaren Hostnamens wäre:

{app_name}-{index_number}.{service}

Wobei {service} ein Headless-Service ist, was erklärt, woher „proxysql-0.proxysqlcluster“ und „proxysql-1.proxysqlcluster“ kommen. Wenn Sie mehr als zwei Replikate haben möchten, fügen Sie entsprechend weitere Einträge hinzu, indem Sie eine aufsteigende Indexnummer relativ zum StatefulSet-Anwendungsnamen anhängen.

Jetzt sind wir bereit, die Konfigurationsdatei in ConfigMap zu pushen, die während der Bereitstellung in jeden ProxySQL-Pod gemountet wird:

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

Überprüfen Sie, ob unsere ConfigMap korrekt geladen wurde:

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

ProxySQL-Überwachungsbenutzer erstellen

Der nächste Schritt, bevor wir mit der Bereitstellung beginnen, besteht darin, einen ProxySQL-Überwachungsbenutzer in unserem Datenbankcluster zu erstellen. Da wir auf dem Galera-Cluster laufen, führen Sie die folgenden Anweisungen auf einem der Galera-Knoten aus:

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

Wenn Sie die MySQL-Benutzer nicht erstellt haben (wie oben im Abschnitt mysql_users angegeben), müssen wir sie ebenfalls erstellen:

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

Das ist es. Wir können jetzt mit der Bereitstellung beginnen.

Bereitstellen eines StatefulSet

Wir beginnen mit der Erstellung von zwei ProxySQL-Instanzen oder Replikaten für Redundanzzwecke mit StatefulSet.

Beginnen wir damit, eine Textdatei mit dem Namen „proxysql-ss-svc.yml“ zu erstellen, und fügen Sie die folgenden Zeilen hinzu:

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

Es gibt zwei Abschnitte der obigen Definition – StatefulSet und Service. Das StatefulSet ist die Definition unserer Pods oder Replicas und der Einhängepunkt für unser ConfigMap-Volume, geladen von proxysql-configmap. Der nächste Abschnitt ist die Dienstdefinition, in der wir definieren, wie die Pods verfügbar gemacht und für das interne oder externe Netzwerk weitergeleitet werden sollen.

Erstellen Sie das zustandsbehaftete ProxySQL-Set und den Dienst:

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

Überprüfen Sie den Pod- und Dienststatus:

$ 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

Wenn Sie sich das Protokoll des Pods ansehen, werden Sie bemerken, dass wir mit dieser Warnung überflutet wurden:

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

Das Obige bedeutet einfach, dass „proxysql-0“ „proxysql-1.proxysqlcluster“ nicht auflösen und keine Verbindung herstellen konnte, was zu erwarten ist, da wir unseren Headless-Dienst für DNS-Einträge, die für die Kommunikation zwischen ProxySQL benötigt werden, nicht erstellt haben.

Kopfloser Kubernetes-Dienst

Damit ProxySQL-Pods den erwarteten FQDN auflösen und sich direkt damit verbinden können, muss der Auflösungsprozess in der Lage sein, die zugewiesene Ziel-Pod-IP-Adresse und nicht die virtuelle IP-Adresse nachzuschlagen. Hier kommt der Headless-Service ins Spiel. Beim Erstellen eines Headless-Dienstes durch die Einstellung „clusterIP=None“ wird kein Load-Balancing konfiguriert und diesem Dienst keine Cluster-IP (virtuelle IP) zugewiesen. Nur DNS wird automatisch konfiguriert. Wenn Sie eine DNS-Abfrage für den Headless-Service ausführen, erhalten Sie die Liste der IP-Adressen der Pods.

So sieht es aus, wenn wir die Headless-Service-DNS-Einträge für „proxysqlcluster“ nachschlagen (in diesem Beispiel hatten wir 3 ProxySQL-Instanzen):

$ 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

Während die folgende Ausgabe den DNS-Eintrag für den Standarddienst namens „proxysql“ zeigt, der in die Cluster-IP aufgelöst wird:

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

Um einen kopflosen Dienst zu erstellen und ihn an die Pods anzuhängen, muss der ServiceName in der StatefulSet-Deklaration definiert werden, und die Dienstdefinition muss „clusterIP=None“ haben, wie unten gezeigt. Erstellen Sie eine Textdatei mit dem Namen „proxysql-headless-svc.yml“ und fügen Sie die folgenden Zeilen hinzu:

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

Erstellen Sie den kopflosen Dienst:

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

Nur zur Überprüfung haben wir an dieser Stelle die folgenden Dienste ausgeführt:

$ 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

Sehen Sie sich jetzt eines unserer Pod-Protokolle an:

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

Sie würden feststellen, dass die Cluster-Komponente in der Lage ist, eine neue Prüfsumme vom anderen Peer, proxysql-1.proxysqlcluster auf Port 6032, über den kopflosen Dienst namens „proxysqlcluster“ aufzulösen, zu verbinden und zu erkennen. Beachten Sie, dass dieser Dienst Port 6032 nur innerhalb des Kubernetes-Netzwerks verfügbar macht und daher extern nicht erreichbar ist.

An diesem Punkt ist unsere Bereitstellung abgeschlossen.

Verbinden mit ProxySQL

Es gibt mehrere Möglichkeiten, eine Verbindung zu ProxySQL-Diensten herzustellen. Die MySQL-Verbindungen mit Lastenausgleich sollten innerhalb des Kubernetes-Netzwerks an Port 6033 gesendet werden und Port 30033 verwenden, wenn der Client eine Verbindung von einem externen Netzwerk aus herstellt.

Um von einem externen Netzwerk aus eine Verbindung zur ProxySQL-Verwaltungsschnittstelle herzustellen, können wir eine Verbindung zu dem im NodePort-Abschnitt definierten Port 30032 herstellen (192.168.100.203 ist die primäre IP-Adresse von Host kube3.local):

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

Verwenden Sie die clusterIP 10.110.38.154 (definiert unter „proxysql“-Dienst) auf Port 6032, wenn Sie von anderen Pods im Kubernetes-Netzwerk darauf zugreifen möchten.

Nehmen Sie dann die ProxySQL-Konfigurationsänderungen wie gewünscht vor und laden Sie sie zur Laufzeit:

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

Sie werden die folgenden Zeilen in einem der Pods bemerken, die darauf hinweisen, dass die Konfigurationssynchronisierung abgeschlossen ist:

$ 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

Beachten Sie, dass die automatische Synchronisierung nur dann erfolgt, wenn es eine Konfigurationsänderung in der ProxySQL-Laufzeit gibt. Daher ist es wichtig, die Anweisung "LOAD ... TO RUNTIME" auszuführen, bevor Sie die Aktion sehen können. Vergessen Sie nicht, die ProxySQL-Änderungen aus Gründen der Persistenz auf der Festplatte zu speichern:

mysql> SAVE MYSQL USERS TO DISK;

Einschränkung

Beachten Sie, dass dieses Setup eingeschränkt ist, da ProxySQL das Speichern/Exportieren der aktiven Konfiguration in eine Textkonfigurationsdatei nicht unterstützt, die wir später zum Laden in ConfigMap für die Persistenz verwenden könnten. Dazu gibt es einen Feature Request. In der Zwischenzeit könnten Sie die Änderungen manuell in ConfigMap übertragen. Andernfalls, wenn die Pods versehentlich gelöscht würden, würden Sie Ihre aktuelle Konfiguration verlieren, da die neuen Pods von dem, was in der ConfigMap definiert ist, gebootstrapped würden.

Besonderer Dank geht an Sampath Kamineni, der die Idee zu diesem Blogbeitrag ausgelöst und Einblicke in die Anwendungsfälle und die Implementierung gegeben hat.