ProxySQL obsługuje natywne klastrowanie od wersji 1.4.2. Oznacza to, że wiele instancji ProxySQL obsługuje klastry; są świadomi wzajemnego stanu i są w stanie automatycznie obsługiwać zmiany konfiguracji, synchronizując się z najbardziej aktualną konfiguracją na podstawie wersji konfiguracji, znacznika czasu i wartości sumy kontrolnej. Sprawdź ten wpis na blogu, który pokazuje, jak skonfigurować obsługę klastrowania dla ProxySQL i jak możesz się tego spodziewać.
ProxySQL to zdecentralizowany serwer proxy, zalecany do wdrożenia bliżej aplikacji. Takie podejście skaluje się całkiem dobrze nawet do setek węzłów, ponieważ zostało zaprojektowane tak, aby można je było łatwo rekonfigurować w czasie wykonywania. Aby efektywnie zarządzać wieloma węzłami ProxySQL, należy upewnić się, że wszelkie zmiany wykonane na jednym z węzłów powinny zostać zastosowane we wszystkich węzłach w farmie. Bez natywnego klastrowania trzeba ręcznie wyeksportować konfiguracje i zaimportować je do innych węzłów (chociaż można to zautomatyzować samodzielnie).
W poprzednim wpisie w blogu omówiliśmy klastrowanie ProxySQL za pośrednictwem Kubernetes ConfigMap. To podejście jest mniej lub bardziej wydajne dzięki scentralizowanemu podejściu do konfiguracji w programie ConfigMap. Cokolwiek załadowane do ConfigMap zostanie zamontowane w podach. Aktualizację konfiguracji można wykonać za pomocą wersjonowania (zmodyfikuj zawartość proxysql.cnf i załaduj ją do ConfigMap pod inną nazwą), a następnie wypchnij do podów w zależności od planowania metody wdrażania i strategii aktualizacji.
Jednak w szybko zmieniającym się środowisku to podejście ConfigMap prawdopodobnie nie jest najlepszą metodą, ponieważ w celu załadowania nowej konfiguracji wymagane jest ponowne planowanie pod, aby ponownie zainstalować wolumin ConfigMap, co może zagrozić usłudze ProxySQL jako całości. Załóżmy na przykład, że w naszym środowisku nasza ścisła polityka dotycząca haseł wymaga wymuszania wygaśnięcia hasła użytkownika MySQL co 7 dni, co oznacza, że musielibyśmy aktualizować ProxySQL ConfigMap dla nowego hasła co tydzień. Na marginesie, użytkownik MySQL wewnątrz ProxySQL wymaga użytkownika i hasła, aby pasowały do tego na serwerach zaplecza MySQL. Od tego momentu powinniśmy zacząć korzystać z natywnej obsługi klastrów ProxySQL w Kubernetes, aby automatycznie zastosować zmiany konfiguracji bez kłopotów z wersjonowaniem ConfigMap i zmianą harmonogramu pod.
W tym poście na blogu pokażemy, jak uruchomić natywne klastry ProxySQL z usługą headless na Kubernetes. Naszą architekturę wysokiego poziomu można zilustrować poniżej:
Mamy 3 węzły Galera działające na infrastrukturze bare-metal wdrożonej i zarządzanej przez ClusterControl:
- 192.168.0.21
- 192.168.0.22
- 192.168.0.23
Wszystkie nasze aplikacje działają jako pody w Kubernetes. Pomysł polega na wprowadzeniu dwóch instancji ProxySQL pomiędzy aplikacją a naszym klastrem baz danych, aby służyły jako odwrotny serwer proxy. Aplikacje będą następnie łączyć się z podami ProxySQL za pośrednictwem usługi Kubernetes, która będzie równoważyć obciążenie i przełączać awaryjnie w wielu replikach ProxySQL.
Poniżej znajduje się podsumowanie naszej konfiguracji 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
Konfiguracja ProxySQL przez ConfigMap
Najpierw przygotujmy naszą podstawową konfigurację, która zostanie załadowana do ConfigMap. Utwórz plik o nazwie proxysql.cnf i dodaj następujące wiersze:
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 }
)
Niektóre z powyższych linii konfiguracyjnych są wyjaśnione w sekcji poniżej:
admin_variables
Zwróć uwagę na admin_credentials zmienna, w której użyliśmy użytkownika innego niż domyślny, którym jest „proxysql-admin”. ProxySQL rezerwuje domyślnego użytkownika „admin” tylko do połączenia lokalnego przez localhost. Dlatego musimy użyć innych użytkowników, aby uzyskać zdalny dostęp do instancji ProxySQL. W przeciwnym razie otrzymasz następujący błąd:
ERROR 1040 (42000): User 'admin' can only connect locally
Dodaliśmy również cluster_username i cluster_password wartość w admin_credentials linii oddzielonej średnikiem, aby umożliwić automatyczną synchronizację. Wszystkie zmienne z prefiksem cluster_* są związane z natywnym klastrowaniem ProxySQL i są oczywiste.
mysql_galera_hostgroups
Jest to nowa dyrektywa wprowadzona dla ProxySQL 2.x (nasz obraz ProxySQL działa na 2.0.5). Jeśli chcesz uruchomić na ProxySQL 1.x, usuń tę część i zamiast tego użyj tabeli harmonogramu. Wyjaśniliśmy już szczegóły konfiguracji w tym poście na blogu, Jak uruchomić i skonfigurować ProxySQL 2.0 dla MySQL Galera Cluster w Docker w sekcji „Obsługa ProxySQL 2.x dla Galera Cluster”.
mysql_servers
Wszystkie wiersze są oczywiste, co jest oparte na trzech serwerach baz danych działających w MySQL Galera Cluster, jak podsumowano na poniższym zrzucie ekranu topologii zaczerpniętym z ClusterControl:
serwery_proxysql
Tutaj definiujemy listę peerów ProxySQL:
- nazwa hosta — nazwa hosta/adres IP peera
- port — port administratora peera
- waga — obecnie nieużywana, ale w planie przyszłych ulepszeń
- komentarz — pole komentarza w dowolnym formularzu
W środowisku Docker/Kubernetes istnieje wiele sposobów odnajdywania i łączenia nazw hostów lub adresów IP kontenerów i wstawiania ich do tej tabeli za pomocą programu ConfigMap, ręcznego wstawiania, za pomocą skryptu entrypoint.sh, zmiennych środowiskowych lub w inny sposób. W Kubernetes, w zależności od użytej metody ReplicationController lub Deployment, odgadnięcie z góry możliwej do rozwiązania nazwy hosta poda jest nieco trudne, chyba że używasz StatefulSet.
Zapoznaj się z tym samouczkiem dotyczącym indeksu porządkowego pod StatefulState, który zapewnia stabilną, rozpoznawalną nazwę hosta dla utworzonych podów. W połączeniu z usługą bezgłową (wyjaśnioną poniżej), możliwym do rozwiązania formatem nazwy hosta będzie:
{app_name}-{index_number}.{service}
Gdzie {service} jest usługą bezgłową, która wyjaśnia, skąd pochodzą „proxysql-0.proxysqlcluster” i „proxysql-1.proxysqlcluster”. Jeśli chcesz mieć więcej niż 2 repliki, dodaj odpowiednio więcej wpisów, dodając rosnący numer indeksu względem nazwy aplikacji StatefulSet.
Teraz jesteśmy gotowi do wypchnięcia pliku konfiguracyjnego do ConfigMap, który zostanie zamontowany w każdym pod ProxySQL podczas wdrażania:
$ kubectl create configmap proxysql-configmap --from-file=proxysql.cnf
Sprawdź, czy nasza ConfigMap jest poprawnie załadowana:
$ kubectl get configmap
NAME DATA AGE
proxysql-configmap 1 7h57m
Tworzenie użytkownika monitorującego ProxySQL
Kolejnym krokiem przed rozpoczęciem wdrożenia jest utworzenie użytkownika monitorującego ProxySQL w naszym klastrze baz danych. Ponieważ działamy w klastrze Galera, uruchom następujące instrukcje na jednym z węzłów Galera:
mysql> CREATE USER 'proxysql'@'%' IDENTIFIED BY 'proxysqlpassw0rd';
mysql> GRANT USAGE ON *.* TO 'proxysql'@'%';
Jeśli nie utworzyłeś użytkowników MySQL (jak określono w sekcji mysql_users powyżej), musimy ich również utworzyć:
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'@'%';
Otóż to. Jesteśmy teraz gotowi do rozpoczęcia wdrażania.
Wdrażanie StatefulSet
Zaczniemy od utworzenia dwóch instancji ProxySQL lub replik dla celów redundancji przy użyciu StatefulSet.
Zacznijmy od utworzenia pliku tekstowego o nazwie proxysql-ss-svc.yml i dodaj następujące wiersze:
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
W powyższej definicji istnieją dwie sekcje — StatefulSet i Service. StatefulSet to definicja naszych podów lub replik oraz punkt instalacji dla naszego woluminu ConfigMap, ładowany z proxysql-configmap. Następna sekcja to definicja usługi, w której definiujemy, w jaki sposób pody powinny być eksponowane i kierowane dla sieci wewnętrznej lub zewnętrznej.
Utwórz zestaw stanowy i usługę ProxySQL:
$ kubectl create -f proxysql-ss-svc.yml
Sprawdź stan poda i usługi:
$ 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
Jeśli spojrzysz na dziennik pod, zauważysz, że zostaliśmy zalani tym ostrzeżeniem:
$ 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)
Powyższe oznacza po prostu, że proxysql-0 nie było w stanie rozwiązać „proxysql-1.proxysqlcluster” i połączyć się z nim, czego można się spodziewać, ponieważ nie stworzyliśmy naszej usługi headless dla rekordów DNS, które będą potrzebne do komunikacji między serwerami ProxySQL.
Usługa bezgłowa Kubernetes
Aby pody ProxySQL mogły rozpoznać oczekiwaną nazwę FQDN i połączyć się z nią bezpośrednio, proces rozpoznawania musi mieć możliwość wyszukania przypisanego docelowego adresu IP poda, a nie wirtualnego adresu IP. I tu pojawia się bezgłowa służba. Podczas tworzenia usługi bezgłowej przez ustawienie „clusterIP=Brak”, nie jest konfigurowane równoważenie obciążenia i żaden adres IP klastra (wirtualny adres IP) nie jest przydzielany dla tej usługi. Tylko DNS jest konfigurowany automatycznie. Po uruchomieniu zapytania DNS dla usługi bezgłowej otrzymasz listę adresów IP podów.
Oto, jak to wygląda, jeśli wyszukamy rekordy DNS usługi bezgłowej dla „proxysqlcluster” (w tym przykładzie mieliśmy 3 instancje 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
Podczas gdy poniższe dane wyjściowe pokazują rekord DNS dla standardowej usługi o nazwie „proxysql”, który jest tłumaczony na adres IP klastra:
$ host proxysql
proxysql.default.svc.cluster.local has address 10.110.38.154
Aby utworzyć usługę bezgłową i dołączyć ją do podów, należy zdefiniować ServiceName wewnątrz deklaracji StatefulSet, a definicja usługi musi mieć "clusterIP=None", jak pokazano poniżej. Utwórz plik tekstowy o nazwie proxysql-headless-svc.yml i dodaj następujące wiersze:
apiVersion: v1
kind: Service
metadata:
name: proxysqlcluster
labels:
app: proxysql
spec:
clusterIP: None
ports:
- port: 6032
name: proxysql-admin
selector:
app: proxysql
Utwórz usługę bezgłową:
$ kubectl create -f proxysql-headless-svc.yml
Tylko dla weryfikacji, w tym momencie mamy uruchomione następujące usługi:
$ 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
Teraz sprawdź jeden z naszych dzienników 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.
Można zauważyć, że komponent Cluster jest w stanie rozwiązać, połączyć i wykryć nową sumę kontrolną z drugiego peera, proxysql-1.proxysqlcluster na porcie 6032 za pośrednictwem usługi bezgłowej o nazwie „proxysqlcluster”. Pamiętaj, że ta usługa ujawnia port 6032 tylko w sieci Kubernetes, dlatego jest nieosiągalny zewnętrznie.
W tym momencie nasze wdrożenie jest już zakończone.
Łączenie z ProxySQL
Istnieje kilka sposobów łączenia się z usługami ProxySQL. Połączenia MySQL ze zrównoważonym obciążeniem powinny być wysyłane na port 6033 z sieci Kubernetes i używać portu 30033, jeśli klient łączy się z sieci zewnętrznej.
Aby połączyć się z interfejsem administratora ProxySQL z sieci zewnętrznej, możemy połączyć się z portem zdefiniowanym w sekcji NodePort, 30032 (192.168.100.203 to podstawowy adres IP hosta kube3.local):
$ mysql -uproxysql-admin -padminpassw0rd -h192.168.100.203 -P30032
Użyj klastraIP 10.110.38.154 (zdefiniowanego w usłudze "proxysql") na porcie 6032, jeśli chcesz uzyskać do niego dostęp z innych podów w sieci Kubernetes.
Następnie dokonaj zmian w konfiguracji ProxySQL, jak chcesz i załaduj je do środowiska wykonawczego:
mysql> INSERT INTO mysql_users (username,password,default_hostgroup) VALUES ('newuser','passw0rd',10);
mysql> LOAD MYSQL USERS TO RUNTIME;
W jednym z zasobników zauważysz następujące wiersze wskazujące, że synchronizacja konfiguracji została zakończona:
$ 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
Należy pamiętać, że automatyczna synchronizacja ma miejsce tylko w przypadku zmiany konfiguracji w środowisku wykonawczym ProxySQL. Dlatego ważne jest, aby uruchomić instrukcję „LOAD ... TO RUNTIME”, zanim zobaczysz akcję. Nie zapomnij zapisać zmian ProxySQL na dysku w celu zachowania trwałości:
mysql> SAVE MYSQL USERS TO DISK;
Ograniczenia
Należy zauważyć, że istnieje ograniczenie tej konfiguracji, ponieważ ProxySQL nie obsługuje zapisywania/eksportowania aktywnej konfiguracji do tekstowego pliku konfiguracyjnego, którego moglibyśmy później użyć do załadowania do programu ConfigMap w celu zachowania trwałości. Jest prośba o nową funkcję. Tymczasem możesz ręcznie przekazać modyfikacje do ConfigMap. W przeciwnym razie, jeśli zasobniki zostałyby przypadkowo usunięte, utracisz bieżącą konfigurację, ponieważ nowe zasobniki zostaną załadowane przez wszystko, co zostało zdefiniowane w ConfigMap.
Specjalne podziękowania dla Sampath Kamineni, który wpadł na pomysł tego wpisu na blogu i zapewnił wgląd w przypadki użycia i implementację.