自v1.4.2起,ProxySQL支持本机群集。这意味着多个ProxySQL实例可识别群集; 他们了解彼此的状态,并能够通过根据配置版本,时间戳和校验和值同步最新的配置来自动处理配置更改。
ProxySQL是一个分散的代理,建议更靠近应用程序部署。这种方法甚至可以扩展到数百个节点,因为它可以在运行时轻松重新配置。为了有效地管理多个ProxySQL节点,必须确保在其中一个节点上执行的任何更改都应该应用于服务器场中的所有节点。如果没有本机群集,则必须手动导出配置并将其导入其他节点(尽管您可以自行进行自动化)。
使用ConfigMap中的集中配置方法,这种方法或多或少非常有效。无论加载到ConfigMap中的是什么都将被安装到pod中。更新配置可以通过版本控制(修改proxysql.cnf内容并将其加载到另一个名称的ConfigMap),然后根据部署方法调度和更新策略推送到pod。
但是,在快速变化的环境中,这种ConfigMap方法可能不是最好的方法,因为为了加载新配置,需要重新安排pod重新安排ConfigMap卷,这可能会危及整个ProxySQL服务。例如,假设在我们的环境中,我们的严格密码策略要求每7天强制MySQL用户密码过期,我们必须每周更新ProxySQL ConfigMap以获取新密码。另外,ProxySQL中的MySQL用户需要用户和密码才能匹配后端MySQL服务器上的用户和密码。这就是我们应该开始在Kubernetes中使用ProxySQL本机群集支持的地方,自动应用配置更改,而无需ConfigMap版本控制和pod重新安排的麻烦。
在这篇博文中,我将向您展示如何在Kubernetes上运行具有无头服务的ProxySQL本机群集。我们的高级架构可以说明如下:
我们在ClusterControl部署和管理的裸机基础设施上运行了3个Galera节点:
- 192.168.0.21
- 192.168.0.22
- 192.168.0.23
我们的应用程序都在Kubernetes中作为pod运行。我们的想法是在应用程序和数据库集群之间引入两个ProxySQL实例作为反向代理。然后,应用程序将通过Kubernetes服务连接到ProxySQL pod,这将在多个ProxySQL副本之间进行负载平衡和故障转移。
以下是我们的Kubernetes设置的摘要:
1
2
3
4
5
|
root@kube1:~
# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME<
/font
>
kube1 Ready master 5m v1.15.1 192.168.100.201
//18
.9.7<
/font
>
kube2 Ready
//18
.9.7<
/font
>
kube3 Ready
//18
.9.7
|
通过ConfigMap进行ProxySQL配置
让我们首先准备我们的基本配置,它将被加载到ConfigMap中。创建一个名为proxysql.cnf的文件并添加以下行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
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 }
)
|
以下部分解释了上面的一些配置行:
admin_variables
注意admin_credentials变量,我们使用非默认用户“proxysql-admin”。ProxySQL仅通过localhost为本地连接保留默认的“admin”用户。因此,我们必须使用其他用户远程访问ProxySQL实例。否则,您将收到以下错误:
1
|
ERROR 1040 (42000): User
'admin'
can only connect locally
|
我们还在admin_credentials行中附加了cluster_username和cluster_password值,以分号分隔以允许自动同步发生。所有以cluster_ *为前缀的变量都与ProxySQL本机群集相关,并且不言自明。
mysql_galera_hostgroups
这是为ProxySQL 2.x引入的新指令(我们的ProxySQL映像在2.0.5上运行)。如果您想在ProxySQL 1.x上运行,请删除此部分并改为使用调度程序表。我们已经在这篇博文中解释了配置细节,如何在Docker上运行和配置针对MySQL Galera Cluster的ProxySQL 2.0“对Galera集群的 ProxySQL 2.x支持”。
mysql_servers
所有行都是不言自明的,它基于在MySQL Galera Cluster中运行的三个数据库服务器,如以下从ClusterControl获取的拓扑截图中所概述:
proxysql_servers
这里我们定义一个ProxySQL对等列表:
- hostname - 对等方的主机名/ IP地址
- port - Peer的管理端口
- 重量 - 目前未使用,但在未来增强的路线图中
- 评论 - 免费表单评论字段
在Docker / Kubernetes环境中,有多种方法可以发现和链接容器主机名或IP地址,并将它们插入到此表中,方法是使用ConfigMap,手动插入,通过entrypoint.sh脚本,环境变量或其他方法。在Kubernetes中,根据所使用的ReplicationController或Deployment方法,在高级中猜测pod的可解析主机名有点棘手,除非您在StatefulSet上运行。
查看有关StatefulState pod序数索引的本教程,该索引为创建的pod提供稳定的可解析主机名。将此与无头服务相结合(进一步解释),可解析的主机名格式为:
{APP_NAME} - {index_number} {}服务。
其中{service}是无头服务,它解释了“proxysql-0.proxysqlcluster”和“proxysql-1.proxysqlcluster”的来源。如果要包含2个以上的副本,请通过附加相对于StatefulSet应用程序名称的升序索引号来相应地添加更多条目。
现在我们准备将配置文件推送到ConfigMap,它将在部署期间安装到每个ProxySQL pod中:
1
|
$ kubectl create configmap proxysql-configmap --from-
file
=proxysql.cnf
|
验证我们的ConfigMap是否正确加载:
1
2
3
|
$ kubectl get configmap<
/font
>
NAME DATA AGE<
/font
>
proxysql-configmap 1 7h57m
|
创建ProxySQL监控用户
开始部署之前的下一步是在数据库集群中创建ProxySQL监控用户。由于我们在Galera集群上运行,因此在其中一个Galera节点上运行以下语句:
1
2
|
mysql>
CREATE
USER
'proxysql'
@
'%'
IDENTIFIED
BY
'proxysqlpassw0rd'
;
mysql>
GRANT
USAGE
ON
*.*
TO
'proxysql'
@
'%'
;
|
如果您还没有创建MySQL用户(如上面mysql_users部分所述),我们也必须创建它们:
1
2
3
4
|
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'
@
'%'
;
|
而已。我们现在准备开始部署。
部署StatefulSet
我们将首先使用StatefulSet为冗余目的创建两个ProxySQL实例或副本。
让我们首先创建一个名为proxysql-ss-svc.yml的文本文件,并添加以下行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
apiVersion: apps
/v1
<
/font
>
kind: StatefulSet<
/font
>
metadata:<
/font
>
name: proxysql<
/font
>
labels:<
/font
>
app: proxysql<
/font
>
spec:<
/font
>
replicas: 2<
/font
>
serviceName: proxysqlcluster<
/font
>
selector:<
/font
>
matchLabels:<
/font
>
app: proxysql<
/font
>
tier: frontend<
/font
>
updateStrategy:<
/font
>
type
: RollingUpdate<
/font
>
template:<
/font
>
metadata:<
/font
>
labels:<
/font
>
app: proxysql<
/font
>
tier: frontend<
/font
>
spec:<
/font
>
restartPolicy: Always<
/font
>
containers:<
/font
>
- image: severalnines
/proxysql
:2.0.4<
/font
>
name: proxysql<
/font
>
volumeMounts:<
/font
>
- name: proxysql-config<
/font
>
mountPath:
/etc/proxysql
.cnf<
/font
>
subPath: proxysql.cnf<
/font
>
ports:<
/font
>
- containerPort: 6033<
/font
>
name: proxysql-mysql<
/font
>
- containerPort: 6032<
/font
>
name: proxysql-admin<
/font
>
volumes:<
/font
>
- name: proxysql-config<
/font
>
configMap:<
/font
>
name: proxysql-configmap<
/font
>
---<
/font
>
apiVersion: v1<
/font
>
kind: Service<
/font
>
metadata:<
/font
>
annotations:<
/font
>
labels:<
/font
>
app: proxysql<
/font
>
tier: frontend<
/font
>
name: proxysql<
/font
>
spec:<
/font
>
ports:<
/font
>
- name: proxysql-mysql<
/font
>
port: 6033<
/font
>
protocol: TCP<
/font
>
targetPort: 6033<
/font
>
- name: proxysql-admin<
/font
>
nodePort: 30032<
/font
>
port: 6032<
/font
>
protocol: TCP<
/font
>
targetPort: 6032<
/font
>
selector:<
/font
>
app: proxysql<
/font
>
tier: frontend<
/font
>
type
: NodePort
|
上面定义有两个部分 - StatefulSet和Service。StatefulSet是从proxysql-configmap加载的pods或副本以及ConfigMap卷的挂载点的定义。下一节是服务定义,我们在其中定义如何为内部或外部网络公开和路由pod。
验证pod和服务状态:
1
2
3
4
5
6
7
8
|
$ kubectl get pods,svc<
/font
>
NAME READY STATUS RESTARTS AGE<
/font
>
pod
/proxysql-0
1
/1
Running 0 4m46s<
/font
>
pod
/proxysql-1
1
/1
Running 0 2m59s<
/font
>
<
/font
>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE<
/font
>
service
/kubernetes
ClusterIP 10.96.0.1
/TCP
10h<
/font
>
service
/proxysql
NodePort 10.111.240.193
/TCP
,6032:30032
/TCP
5m28s
|
如果你看一下pod的日志,你会发现我们被这个警告淹没了:
1
2
3
|
$ kubectl logs -f proxysql-0<
/font
>
...<
/font
>
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)
|
以上只是意味着proxysql-0无法解析“proxysql-1.proxysqlcluster”并连接到它,这是预料之中的,因为我们还没有为代理SQL间通信所需的DNS记录创建无头服务。
Kubernetes无头服务
为了使ProxySQL pod能够解析预期的FQDN并直接连接到它,解析过程必须能够查找分配的目标pod IP地址而不是虚拟IP地址。这是无头服务进入画面的地方。通过设置“ clusterIP = None ” 创建无头服务时,不会配置负载平衡,也不会为此服务分配集群IP(虚拟IP)。仅自动配置DNS。当您为无头服务运行DNS查询时,您将获得pod IP地址的列表。
如果我们查找“proxysqlcluster”的无头服务DNS记录(在此示例中我们有3个ProxySQL实例),这就是它的样子:
1
2
3
4
|
$ host proxysqlcluster<
/font
>
proxysqlcluster.default.svc.cluster.
local
has address 10.40.0.2<
/font
>
proxysqlcluster.default.svc.cluster.
local
has address 10.40.0.3<
/font
>
proxysqlcluster.default.svc.cluster.
local
has address 10.32.0.2
|
同时,以下输出显示名为“proxysql”的标准服务的DNS记录,该记录解析为clusterIP:
1
2
|
$ host proxysql<
/font
>
proxysql.default.svc.cluster.
local
has address 10.110.38.154
|
要创建无头服务并将其附加到pod,必须在StatefulSet声明中定义ServiceName,并且Service定义必须具有“ clusterIP = None ”,如下所示。创建一个名为proxysql-headless-svc.yml的文本文件,并添加以下行:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
apiVersion: v1<
/font
>
kind: Service<
/font
>
metadata:<
/font
>
name: proxysqlcluster<
/font
>
labels:<
/font
>
app: proxysql<
/font
>
spec:<
/font
>
clusterIP: None<
/font
>
ports:<
/font
>
- port: 6032<
/font
>
name: proxysql-admin<
/font
>
selector:<
/font
>
app: proxysql
|
创建无头服务:
1
|
$ kubectl create -f proxysql-headless-svc.yml
|
仅用于验证,此时,我们运行以下服务:
1
2
3
4
5
|
$ kubectl get svc<
/font
>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE<
/font
>
kubernetes ClusterIP 10.96.0.1
/TCP
8h<
/font
>
proxysql NodePort 10.110.38.154
/TCP
,6032:30032
/TCP
23m<
/font
>
proxysqlcluster ClusterIP None
/TCP
4s
|
现在,查看我们的pod日志之一:
1
2
3
4
5
|
$ kubectl logs -f proxysql-0<
/font
>
...<
/font
>
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)<
/font
>
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 ...<
/font
>
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
.
|
您会注意到Cluster组件能够通过名为“proxysqlcluster”的无头服务从端口6032上的其他对等端proxysql-1.proxysqlcluster解析,连接和检测新的校验和。请注意,此服务仅在Kubernetes网络中公开端口6032,因此无法从外部访问。
此时,我们的部署现已完成。
连接到ProxySQL
有几种方法可以连接到ProxySQL服务。负载均衡的MySQL连接应从Kubernetes网络内发送到端口6033,如果客户端从外部网络连接,则使用端口30033。
要从外部网络连接到ProxySQL管理界面,我们可以连接到NodePort部分30032下定义的端口(192.168.100.203是主机kube3.local的主IP地址):
1
|
$ mysql -uproxysql-admin -padminpassw0rd -h192.168.100.203 -P30032
|
如果要从Kubernetes网络中的其他pod访问,请在端口6032上使用clusterIP 10.110.38.154(在“proxysql”服务下定义)。
然后根据需要执行ProxySQL配置更改并将其加载到运行时:
1
2
|
mysql>
INSERT
INTO
mysql_users (username,
password
,default_hostgroup)
VALUES
(
'newuser'
,
'passw0rd'
,10);
mysql>
LOAD
MYSQL USERS
TO
RUNTIME;
|
您将注意到其中一个窗格中的以下行指示配置同步完成:
1
2
3
4
5
6
|
$ kubectl logs -f proxysql-0<
/font
>
...<
/font
>
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
<
/font
>
2019-08-02 03:53:48 [INFO] Cluster: detected peer proxysql-1.proxysqlcluster:6032 with mysql_users version 2, epoch 1564718027<
/font
>
2019-08-02 03:53:48 [INFO] Cluster: Fetching MySQL Users from peer proxysql-1.proxysqlcluster:6032 started<
/font
>
2019-08-02 03:53:48 [INFO] Cluster: Fetching MySQL Users from peer proxysql-1.proxysqlcluster:6032 completed
|
请记住,只有在ProxySQL运行时中发生配置更改时才会发生自动同步。因此,在看到动作之前运行“ LOAD ... TO RUNTIME ”语句至关重要。不要忘记将ProxySQL更改保存到磁盘中以保持持久性:
1
|
mysql> SAVE MYSQL USERS
TO
DISK;
|
局限性
请注意,此设置存在限制,因为ProxySQL不支持将活动配置保存/导出到文本配置文件中,以后我们可以将其加载到ConfigMap中以保持持久性。有一个功能要求。同时,您可以手动将修改推送到ConfigMap。否则,如果意外删除了pod,则会丢失当前配置,因为新的pod将由ConfigMap中定义的任何内容引导。
特别感谢Sampath Kamineni,他引发了这篇博客文章的想法,并提供了有关用例和实现的见解。
欢迎关注公众号:“Java架构师学习”
你会喜欢的!