运维实战案例 容器部分 使用Statefulset部署MySQL主从集群
需求分析
一主多从
模式只能有一个写入端, 多个查询端需要能负载均衡和健康检查- 如何保证
MASTER
端恒定且连接方式不飘逸 - 解决主从机配置文件不同的问题
- 每台机器的配置文件中
server-id
不同, 如何用同一个配置文件模板为所有从机分配各自的配置文件 - 如何进行数据的持久化
解决思路
-
根据
StatefulSet
控制器的特点, 上一个Pod
报告就绪后, 才会开始生成下一个Pod
, 因此我们可以断定, 第一个就绪的Pod
一定是MASTER
端, 且如果MASTER
端初始化出现问题, 整个后续都不会继续. -
根据
Headless Service
的特点, 首先启动的Pod
一定对应的是mysql-0.mysql
, 后续会被Pod
的DNS
自行补全, 因此MASTER
端的唯一性和确定性也可以保证 -
所有
Pod
又可以放入一个新的svc
中管理, 通过ClusterIP
实现负载均衡和健康检查 -
之前我们学过, 在容器创建前依旧会有
Init 容器
存在, 且顺序执行, 将配置文件的放入和处理操作在Init 容器
中执行, 既可以保证容器创建时有符合要求的配置文件,Init 容器
本身顺序执行且必须执行完毕的特性也保证了只要逻辑得当, 生成的配置文件一定是符合要求的 -
两个
Service
实现读写分离 -
借助
Init 容器
实现对配置文件的操作 -
StatefulSet
控制器实现有序建立和定位 -
PVC
动态生成PV
来实现数据持久化
参考教程
注意事项
- 实验采用的设置都不是生产环境下的设置, 并不具备生产环境的安全性
- 重点在于构建的思路
ConfigMap部分
- 通过
ConfigMap
实现将配置模板挂入Pod
中的目的
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
master.cnf: |
# Apply this config only on the master.
[mysqld]
log-bin
slave.cnf: |
# Apply this config only on slaves.
[mysqld]
super-read-only
可以看到master.cnf
和slave.cnf
被分别存放, slave.cnf
中包括super-read-only
参数来实现只读, 这一步是为了提供my.cnf
的内容
Service部分
- 创建读写分离所需要的两个服务
- 写服务为
Headless Service
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the master: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
name: mysql-read
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
无头服务为每个Pod
提供一个DNS
解析, 因为服务名为 mysql
, 所以可以通过在同一 Kubernetes 集群和名字中的任何其他 Pod 内解析 <Pod 名称>.mysql
来访问 Pod
, 又因为MASTER
端永远第一个被建立, 因此其解析地址永远为mysql-0.mysql
Read
相关的服务就是普通的服务, 通过ClusterIP
提供负载均衡和健康检查
StatefulSet控制器部分
也是本实验最重要的部分, 可以说以下内容都是通过StatefulSet
的特性来实现的
- 配置文件中
server-id
的有序递增 Pod
创建顺序的有序化- 新生成的
Pod
从上一个Pod
复制数据的实现
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: mysql:5.7
command:
- bash
- "-c"
- |
set -ex
# Generate mysql server-id from pod ordinal index.
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# Add an offset to avoid reserved server-id=0 value.
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# Copy appropriate conf.d files from config-map to emptyDir.
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/master.cnf /mnt/conf.d/
else
cp /mnt/config-map/slave.cnf /mnt/conf.d/
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
- name: clone-mysql
image: gcr.io/google-samples/xtrabackup:1.0
command:
- bash
- "-c"
- |
set -ex
# Skip the clone if data already exists.
[[ -d /var/lib/mysql/mysql ]] &&