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.