[TOC]
5 MySQL 主从
完整项目地址:https://github.com/As9530272755/k8sOfMysql
5.1 什么是 statefulset
我们之前也说过,对于我们的 docker 来说更适合的是运行我们的无状态服务。那有状态服务怎么办呢,docker 给我们的解决方案是一个以存储卷的方案去加载对应的数据,但是在 K8S 里它不仅有存储卷,还给了我们一个特殊的控制器叫 statefulset ,去完成我们的有状态服务的解决。其实现在还是有很多有状态的应用程序不方便去部署进我们的 statefulset 中,或是不方便部署进我们的 k8s 中。比如我们的 mysql ,其实至今没有一个太好的解决方案可以把 mysql 这么一个有状态服务部署进我们的 K8s。
statefulset 作为 controller 为 pod 提供唯一的标识。它可以保证部署和 scale(比例) 的顺序
statefulset 是为了解决有状态服务的问题 (对应 Deployments 和 ReplicaSets 是为无状态服务而设计),其应用场景包括:
- 稳定的持久化存储:即 pod 重新调度后还是能够访问到相同的持久化数据,基于 PVC 来实现。假设现在我有几个我们的容器,是被我们的 statefulset 去部署的,部署完成以后呢可能都用到了我们同一个存储卷,假如有一天这个 pod 死亡了,但我们的 statefulset 为了维持这个副本数就会重新创建对应的 pod ,这时候新创建的 pod 也会继续使用上一个 pod 退出时使用到的存储卷。也就这里的持久化数据并不会丢失,这就是我们所谓的稳定的持久化存储方案。
-
稳定的网络标识:即 pod 重新调度以后其 pod name 和 host name 不变(这个很重要,在我们的很多服务里我们去创建对应的一些服务的链接方案的时候,都会以我们的 pod name 为链接对象,或者 host name 为链接对象,如果我们的 pod 死亡被重新创建以后它的名称发生改变了这个对于我们后面的自动化流程来说时非常不友好的。但是对于我们的 statefulset 来说它可以确保每一个 pod 它的 pod name 和 host name 在 statefulset 生命周期里完全不变),基于 headless service 无头服务实现(也就是没有 cluster IP 的 service,可以理解为没有 IP 地址和端口 )来实现
-
有序部署,有序扩展:即 pod 是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行(即从 0 到 N1,在下一个 pod 运行之前所有的 pod 必须是 running 和 ready 状态),基于 init containers 来实现。好这里需要注意以下为啥是基于 init containers 而不是基于我们所谓的 start 或 stop ,原因是不管是 start 、stop 他都需要去更改我们 pod 内部的容器镜像,但是对于 init containers 初始化容器来说是不需要更改的,只需要在 statefulset 的 pod 里面的容器运行之前加一个 init C ,并不会对我们的原有 pod 结构发生改变。所以这是他的解决方案怎么去实现这里的 running 和 ready。
-
有序收缩,有序删除(即从 N1 到 0):会发现在扩展部署的时候是 0 到 N1 的,在删除的时候 N1 到 0 的,这是一个倒序关系。我们可以观察下图:
现在我有这么一个结构,底下是我们一个数据库 mysql ,上面运行了我们一些 php-fpm,再上面运行了我们的一个 nginx 实现了我们的一个反向代理,这样的一个结构我们会发现在部署的时候应该从底往上部署。也就意味着他是先部署我们的 mysql –> php-fpm –> nginx ,这是我们的部署顺序。那如果我们想把这个服务停止的话它的顺序正好是相反的,我们应该先停我们的 nginx –> php-fpm –> mysql ,这时候有可能有的朋友就会想为什么不是先停我们的 mysql , 假如我们先把 mysql 停了,那这里的 php-fpm 和 nginx 还是在运行状态。用户的请求有可能是会到我们的 nginx 那就有可能到我们的 php-fpm 就有可能报错。所以它的部署顺序和它的启动顺序恰好是相反的。
- 为了解决有状态服务问题,也就是服务本身是有集群关系,同步主从
-
他所管理的 pod 拥有固定的 pod 名称,主机名,启停顺序
-
创建一个 statefulset 类型的 pod , 并指定 serviceName ,创建 headless 类型的 svc
-
statefulset 类型启动 pod 一定会先起第一个然后再起后面的 pod ,他是有严格的启动顺序
statefulset 特点:
- 给每个 pod 分配固定且唯一的网络标识符(pod 名称不会改变)
- 比如是第启动了第一个 pod,那么这个 pod 的名称就是 podName-0 ,并且通过每个 pod 名字来访问 pod
- 给每个 pod 分配固定且持久化的外部存储
- 每个 pod 使用的 pv 和 pvc 是固定的绑定关系,并且存入到 ETCD 不管 pod 怎么删除都不会打乱这种绑定关系
- 由此每个 pod 的数据是唯一的
- 对 pod 进行有序的部署和扩展
- 假如定义了 3 pod 副本就,如果前面的 pod0 没有起来那么后面的 pod1、pod2 就不会启动
- 对 pod 进有序的删除和终止
- 删除 pod 也是进行有序的删除,并且是从后往前面删除,由 podN 向 pod0 进行删除
- 对 pod 进有序的自动滚动更新
5.2 如何实现 MySQL 主从
基于 statefulset 运行 MySQL 主从,服务的特殊性就在于我们的 mysql 是主从的那到底谁是主谁是从
- 是一个“主从复制”(Maser-Slave Replication)的 MySQL 集群;
- 有 1 个主节点(Master);
- 有多个从节点(Slave);
- 从节点需要能水平扩展;
- 所有的写操作,只能在主节点上执行;
- 读操作可以在所有节点上执行。
在常规环境里,部署这样一个主从模式的 MySQL 集群的主要难点在于:如何让从节点能够拥有主节点的数据,
即:如何配置主(Master)从(Slave)节点的复制与同步。
MySQL 主从注意事项:
我们将 mysql 做了读写分离的话就会让读操作通过从节点实现,而写操作就通过主节点实现
其他客户端也可以通过 svc 进行访问,但是我们要保证 svc 不能只有一个,而实每个 pod 有一个 svc 或者说 master 有对应的 svc ,slave 也有自己对应的 svc
如果一个 svc 的话后面就是绑定的所有 pod ,因为这些 pod 绑定的 svc labels 是一样的,但是这样的话就会导致数据不一致,读数据的话还好,如果是写入数据的本来是向主库写入,结果把这些请求转给了 slave 上。就会导致数据写入失败,即使 slave 写入成功也会出现数据的不一致
所以我们在写入数据的话直接都是写 pod 名称,直接通过 master 节点写入。而读取数据的话就是通过 salve 读取
因为我们都知道 statefulset + headless service,都是通过 DNS 解析,即使 pod 的 ip 发生变化但是依旧不会影响我们对 pod 的解析
- statefulset(绑定唯一标识): 是用来管理有状态应用的工作负载 API 对象。
StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。
和 Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。 尽管 StatefulSet 中的单个 Pod 仍可能出现故障, 但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。
-
headless service(通过 DNS 解析):有时不需要或不想要负载均衡,以及单独的 service IP 。遇到这种情况,可以通过指定 cluster IP(spec.cluster IP)的值为 none 来创建 headless service(无头服务)。这类的 service 并不会分配 cluster IP ,kube-proxy 不会处理他们,而且平台也不会为它们进行负载均衡和路由。而是使用 pod 的 ip
5.3 MySQL 结构图
- 一定要先启动一个 pod-0 为 master ,写数据就往该 pod 上写入
- 紧接着 pod-0 起来之后会启动 pod-1、pod-2 的 slave 节点,读数据的话就往 slave 节点上进行读取
而且我们需要两个镜像:
- 一个是 MySQL 用来做基础镜像
- 一个是 xtrabackup 用来做数据同步
5.4 操作流程
5.4.1 准备镜像
将下载下来的镜像上传至本地 harbor ,因为这样就不会让其他节点在到外网上下载
1.先准备 mysql:5.7 镜像
# 下载镜像
[16:32:11 root@k8s-master k8sOfMysql]#docker pull mysql:5.7
# 打 tag
[16:35:42 root@k8s-master k8sOfMysql]#docker tag docker.io/library/mysql:5.7 hub.zhangguiyuan.com/baseimage/mysql:5.7
# 上传镜像
[16:36:24 root@k8s-master k8sOfMysql]#docker push hub.zhangguiyuan.com/baseimage/mysql:5.7
2.下载 xtrabackup 镜像
# 下载镜像
[16:44:24 root@k8s-master k8sOfMysql]#docker pull registry.cn-hangzhou.aliyuncs.com/hxpdocker/xtrabackup:1.0
# 打 tag
[16:45:11 root@k8s-master k8sOfMysql]#docker tag registry.cn-hangzhou.aliyuncs.com/hxpdocker/xtrabackup:1.0 hub.zhangguiyuan.com/baseimage/xtrabackup:1.0
# 上传
[16:45:31 root@k8s-master k8sOfMysql]#docker push hub.zhangguiyuan.com/baseimage/xtrabackup:1.0
5.4.2 创建 NS
1.编写 yaml
[16:56:49 root@k8s-master mysqlYaml]#vim mysql-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
name: mysql
2.创建
[16:58:04 root@k8s-master mysqlYaml]#kubectl apply -f mysql-ns.yaml
5.4.2 创建 pv
每个 pv 都是给每个不同的 pod 来使用,因为 mysql 主从 pod 的数据都是一个 pod 对应一个 pv ,工作中计划跑几个 pod 就创建几个 pv
1.编写 yaml 文件
[16:52:45 root@k8s-master pv]#vim mysql-persistentvolume.yaml
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-datadir-1
namespace: mysql
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
nfs:
path: /data/k8sdata/mysql/mysql-datadir-1
server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-datadir-2
namespace: mysql
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
nfs:
path: /data/k8sdata/mysql/mysql-datadir-2
server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-datadir-3
namespace: mysql
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
nfs:
path: /data/k8sdata/mysql/mysql-datadir-3
server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-datadir-4
namespace: mysql
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
nfs:
path: /data/k8sdata/mysql/mysql-datadir-4
server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-datadir-5
namespace: mysql
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
nfs:
path: /data/k8sdata/mysql/mysql-datadir-5
server: 10.0.0.133
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-datadir-6
namespace: mysql
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
nfs:
path: /data/k8sdata/mysql/mysql-datadir-6
server: 10.0.0.133
2.在 NFS 上创建挂载目录
[16:51:36 root@harbor-nfs ~]#mkdir -p /data/k8sdata/mysql/mysql-datadir-{1..6}
3.创建 pv
[17:06:25 root@k8s-master mysqlYaml]#kubectl apply -f mysql-persistentvolume.yaml
5.4.3 创建 configmap
1.编写配置,这个 configmap 会挂到对应的 pod 上,后期我们想修改 mysql 的配置文件直接修改 configmap 即可
[16:58:08 root@k8s-master mysqlYaml]#vim mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
namespace: mysql
labels:
app: mysql
data:
# master 配置文件
master.cnf: |
# Apply this config only on the master.
[mysqld]
log-bin # 开启二进制日志
log_bin_trust_function_creators=1 # MySQL不会对创建存储实施限制。
lower_case_table_names=1 # 表名存储在磁盘是小写的,但是比较的时候是不区分大小写
# slave 节点配置文件
slave.cnf: |
# Apply this config only on slaves.
[mysqld]
super-read-only # 只读
log_bin_trust_function_creators=1
2.创建
[17:06:42 root@k8s-master mysqlYaml]#kubectl apply -f mysql-configmap.yaml
5.4.4 创建 svc
这里我创建的 svc 都是基于 headless svc ,因为通过 dns 来解析,
[17:07:23 root@k8s-master mysqlYaml]#vim mysql-services.yaml
# Headless service for stable DNS entries of StatefulSet members.
# 提供给 MySQL 的 master pod 用来实现写数据
apiVersion: v1
kind: Service
metadata:
namespace: mysql
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.
# 提供给 MySQL 的 slave pod 用来实现读取数据
apiVersion: v1
kind: Service
metadata:
name: mysql-read
namespace: mysql
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
2.创建
[17:10:54 root@k8s-master mysqlYaml]#kubectl apply -f mysql-services.yaml
service/mysql created
service/mysql-read created
5.4.5 创建 statefulset
通过这种方式,每次他的 slave pod 都会基于前面的一个 pod 来进行同步数据,除了 master pod 是自己生成的,如上图我在红框中标出的一样,他只会通过 pod-id -1 的 pod 来进行数据同步,这样的好处就是并不会给 master pod 增加太多的负载
- yaml 解析
[17:19:15 root@k8s-master mysqlYaml]#cat mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
# 定义初始化容器,因为 mysql 在启动之前需要进行环境初始化,生成一些库之类的比如 user 表 mysql 库等等
initContainers:
- name: init-mysql
image: hub.zhangguiyuan.com/baseimage/mysql:5.7
imagePullPolicy: IfNotPresent
# 在初始化容器中执行命令,给这个容器传递一些支持的命令,主要包含了下面操作:
command:
- bash
- "-c"
- |
set -ex
# 1.先判断 hostname 是否数字结尾
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
# 2.通过 BASH_REMATCH 取到第一个数,用下面对 pod 的角色判断
ordinal=${BASH_REMATCH[1]}
# 3.往 server-id.cnf 写入 [mysqld]
echo [mysqld] > /mnt/conf.d/server-id.cnf
# 4.添加偏移量以避免保留服务器 id=0 的值。因为 server-id 一定要唯一
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# 5.如果 ordinal 变量等于 0 就 cp master.cnf
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/master.cnf /mnt/conf.d/
else
# 6.如果 ordinal 不为 0 就表示这 pod 是 slavc 将 slave.cnf 到 conf.d
cp /mnt/config-map/slave.cnf /mnt/conf.d/
fi
# 将配置文件进行挂载
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
# 接着初始化 xtrabackup 容器,这个是用于克隆前一个 pod 的数据
- name: clone-mysql
image: hub.zhangguiyuan.com/baseimage/xtrabackup:1.0
imagePullPolicy: IfNotPresent
command:
- bash
- "-c"
- |
set -ex
# 判断 /var/lib/mysql/mysql 是否存在
[[ -d /var/lib/mysql/mysql ]] && exit 0
# 判断主机名的最后以为是不是数字
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
# 拿到第一个数
ordinal=${BASH_REMATCH[1]}
# 判断 ordinal 是否等于 0
[[ $ordinal -eq 0 ]] && exit 0
# mysql-$(($ordinal-1)).mysql 是一个 pod 的 DSN 解析
# 接收 mysql-$(($ordinal-1)).mysql pod 的数据并备份,就是只基于前一个 mysqp pod 进行数据同步
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# 备份数据目录
xtrabackup --prepare --target-dir=/var/lib/mysql
volumeMounts:
# 挂载 pvc
- name: data
mountPath: /var/lib/mysql
# 1 个 pod 中可以拉起多个容器,有时候希望将不同容器的路径挂载在存储卷volume 的子路径
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
# 运行容器信息
containers:
- name: mysql
image: hub.zhangguiyuan.com/baseimage/mysql:5.7
imagePullPolicy: IfNotPresent
env:
- name: MYSQL_ALLOW_EMPTY_PASSWORD
value: "1" # 值为 1 不用传递密码,改为 0 就需要传递密码
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 500m
memory: 1Gi
# 存活检测
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
# 就绪检测
readinessProbe:
exec:
# Check we can execute queries over TCP (skip-networking is off).
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
# 创建 xtrabackup 容器
- name: xtrabackup
image: hub.zhangguiyuan.com/baseimage/xtrabackup:1.0
imagePullPolicy: IfNotPresent
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# 保存主日志文件以及偏移
if [[ -f xtrabackup_slave_info ]]; then
# XtraBackup already generated a partial "CHANGE MASTER TO" query
# because we're cloning from an existing slave.
mv xtrabackup_slave_info change_master_to.sql.in
# Ignore xtrabackup_binlog_info in this case (it's useless).
rm -f xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# We're cloning directly from master. Parse binlog position.
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm xtrabackup_binlog_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
# Check if we need to complete a clone by starting replication.
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
# In case of container restart, attempt this at-most-once.
mv change_master_to.sql.in change_master_to.sql.orig
mysql -h 127.0.0.1 <<EOF
$(<change_master_to.sql.orig),
MASTER_HOST='mysql-0.mysql',
MASTER_USER='root',
MASTER_PASSWORD='',
MASTER_CONNECT_RETRY=10;
START SLAVE;
EOF
fi
# Start a server to send backups when requested by peers.
exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 100m
memory: 100Mi
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql
# 调用 pv 进行存储挂载。并自动创建 pvc
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
2.完整的 yaml 文件
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: hub.zhangguiyuan.com/baseimage/mysql:5.7
imagePullPolicy: IfNotPresent
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: hub.zhangguiyuan.com/baseimage/xtrabackup:1.0
imagePullPolicy: IfNotPresent
command:
- bash
- "-c"
- |
set -ex
# Skip the clone if data already exists.
[[ -d /var/lib/mysql/mysql ]] && exit 0
# Skip the clone on master (ordinal index 0).
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# Clone data from previous peer.
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# Prepare the backup.
xtrabackup --prepare --target-dir=/var/lib/mysql
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
containers:
- name: mysql
image: hub.zhangguiyuan.com/baseimage/mysql:5.7
imagePullPolicy: IfNotPresent
env:
- name: MYSQL_ALLOW_EMPTY_PASSWORD
value: "1"
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 500m
memory: 1Gi
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
# Check we can execute queries over TCP (skip-networking is off).
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
- name: xtrabackup
image: hub.zhangguiyuan.com/baseimage/xtrabackup:1.0
imagePullPolicy: IfNotPresent
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# Determine binlog position of cloned data, if any.
if [[ -f xtrabackup_slave_info ]]; then
# XtraBackup already generated a partial "CHANGE MASTER TO" query
# because we're cloning from an existing slave.
mv xtrabackup_slave_info change_master_to.sql.in
# Ignore xtrabackup_binlog_info in this case (it's useless).
rm -f xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# We're cloning directly from master. Parse binlog position.
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm xtrabackup_binlog_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
# Check if we need to complete a clone by starting replication.
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
# In case of container restart, attempt this at-most-once.
mv change_master_to.sql.in change_master_to.sql.orig
mysql -h 127.0.0.1 <<EOF
$(<change_master_to.sql.orig),
MASTER_HOST='mysql-0.mysql',
MASTER_USER='root',
MASTER_PASSWORD='',
MASTER_CONNECT_RETRY=10;
START SLAVE;
EOF
fi
# Start a server to send backups when requested by peers.
exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 100m
memory: 100Mi
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
3.创建
我们可以看到通过 statefuleset 的方式创建的 pod ,他都是按照顺序启动,如果前一个没有起来下面的 pod 也不会起来
[18:04:52 root@k8s-master mysqlYaml]#kubectl get pod -n mysql -w
NAME READY STATUS RESTARTS AGE
mysql-0 0/2 Init:0/2 0 7s
mysql-0 0/2 Init:1/2 0 12s
mysql-0 0/2 PodInitializing 0 19s
mysql-0 1/2 Running 0 20s
mysql-0 2/2 Running 0 34s
mysql-1 0/2 Pending 0 0s
mysql-1 0/2 Pending 0 0s
mysql-1 0/2 Pending 0 2s
mysql-1 0/2 Init:0/2 0 2s
mysql-1 0/2 Init:1/2 0 12s
mysql-1 0/2 Init:1/2 0 19s
mysql-1 0/2 PodInitializing 0 27s
mysql-1 1/2 Error 0 28s
mysql-1 1/2 Running 1 29s
mysql-1 2/2 Running 1 34s
mysql-2 0/2 Pending 0 0s
mysql-2 0/2 Pending 0 0s
mysql-2 0/2 Pending 0 2s
mysql-2 0/2 Init:0/2 0 2s
mysql-2 0/2 Init:1/2 0 4s
mysql-2 0/2 Init:1/2 0 5s
mysql-2 0/2 PodInitializing 0 13s
mysql-2 1/2 Error 0 14s
mysql-2 1/2 Running 1 15s
mysql-2 2/2 Running 1 20s
4.验证 pvc 已经自动绑定
[18:12:01 root@k8s-master mysqlYaml]#kubectl get pvc -n mysql
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-mysql-0 Bound mysql-datadir-5 50Gi RWO 7m17s
data-mysql-1 Bound mysql-datadir-2 50Gi RWO 6m43s
data-mysql-2 Bound mysql-datadir-3 50Gi RWO 6m9s
5.pod 已经启动
[18:12:03 root@k8s-master mysqlYaml]#kubectl get pod -n mysql
NAME READY STATUS RESTARTS AGE
mysql-0 2/2 Running 0 7m44s
mysql-1 2/2 Running 1 7m10s
mysql-2 2/2 Running 1 6m36s
5.5 验证
1.进入到 mysql master pod 里面,进行状态验证
[18:12:30 root@k8s-master mysqlYaml]#kubectl exec -it -n mysql mysql-0 bash
root@mysql-0:/# mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 363
Server version: 5.7.36-log MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
# 查看当前 master 状态
mysql> show master status\G;
*************************** 1. row ***************************
File: mysql-0-bin.000003
Position: 154
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set:
1 row in set (0.00 sec)
# 并创建一个 test 数据库
mysql> create database test_db;
Query OK, 1 row affected (0.01 sec)
2.进行到 slave 节点进行验证
[18:18:40 root@k8s-master mysqlYaml]#kubectl exec -it -n mysql mysql-1 bash
root@mysql-1:/# mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 469
Server version: 5.7.36 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
# 查看数据库 test_db 存在
mysql> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test_db |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.02 sec)
# 查看 slave 状态
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: mysql-0.mysql # master 节点就是通过主机名解析
Master_User: root
Master_Port: 3306
Connect_Retry: 10
Master_Log_File: mysql-0-bin.000003
Read_Master_Log_Pos: 322
Relay_Log_File: mysql-1-relay-bin.000002
Relay_Log_Pos: 490
Relay_Master_Log_File: mysql-0-bin.000003
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 322
Relay_Log_Space: 699
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 100
Master_UUID: 347f9607-318d-11ec-a413-5aa1e107c549
Master_Info_File: /var/lib/mysql/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
3.查看 svc
# 我们可以看到 mysql 是 none 无头服务,但是 mysql-read 就有对应的 CLUSTER-IP
[18:20:42 root@k8s-master mysqlYaml]#kubectl get svc -n mysql
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mysql ClusterIP None <none> 3306/TCP 65m
mysql-read ClusterIP 172.30.241.13 <none> 3306/TCP 65m
4.进入其他的 pod 中通过主机头 ping 通
# 进入 pod
[18:27:47 root@k8s-master mysqlYaml]#kubectl exec -it -n redis deploy-devops-redis /bin/bash
# 刚在我们看到 mysql 才是 svc ,而我这里直接通过 ping mysql-0 的主机头进行解析
[root@deploy-devops-redis /]# ping mysql-0.mysql.mysql.svc.linux.local
PING mysql-0.mysql.mysql.svc.linux.local (10.10.169.140) 56(84) bytes of data.
64 bytes from mysql-0.mysql.mysql.svc.linux.local (10.10.169.140): icmp_seq=1 ttl=63 time=0.048 ms
64 bytes from mysql-0.mysql.mysql.svc.linux.local (10.10.169.140): icmp_seq=2 ttl=63 time=0.082 ms
5.先获取 coredns pod 对应的 ip
[18:33:14 root@k8s-master mysqlYaml]#kubectl get pod -n kube-system -o wide | grep coredns
coredns-6f6b8cc4f6-gnqwh 1/1 Running 2 26h 10.10.169.137 k8s-node2 <none> <none>
coredns-6f6b8cc4f6-qp8n4 1/1 Running 2 26h 10.10.113.132 k8s-node <none> <none>
6.在获取 mysql pod 的详细信息
[18:33:44 root@k8s-master mysqlYaml]#kubectl get pod -n mysql -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mysql-0 2/2 Running 0 29m 10.10.169.140 k8s-node2 <none> <none>
mysql-1 2/2 Running 1 28m 10.10.113.135 k8s-node <none> <none>
mysql-2 2/2 Running 1 27m 10.10.113.136 k8s-node <none> <none>
7.通过 dig
命令进行解析,这里 @ 后面跟的是任意一个 coredns ip
也就意味着在我们无头服务中虽然没有自己的 SVC 了,但是可以通过访问域名的方案依然可以访问至这几个不同的 pod 上去,这个就是我们无头服务的含义
[18:33:51 root@k8s-master mysqlYaml]#dig -t -A mysql.mysql.svc.linux.local. @10.10.169.137
;; Warning, ignoring invalid type -A
; <<>> DiG 9.11.3-1ubuntu1.12-Ubuntu <<>> -t -A mysql.mysql.svc.linux.local. @10.10.169.137
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27126
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 9019399f0301aa0a (echoed)
;; QUESTION SECTION:
;mysql.mysql.svc.linux.local. IN A
# 我们会惊奇得发现每条 DNS 解析的就是对应的 mysql pod ip
;; ANSWER SECTION:
mysql.mysql.svc.linux.local. 30 IN A 10.10.113.136
mysql.mysql.svc.linux.local. 30 IN A 10.10.169.140
mysql.mysql.svc.linux.local. 30 IN A 10.10.113.135
;; Query time: 1 msec
;; SERVER: 10.10.169.137#53(10.10.169.137)
;; WHEN: Wed Oct 20 18:35:06 CST 2021
;; MSG SIZE rcvd: 197
8.并且在 NFS 服务器上也有挂载卷,从而实现了数据的持久化
[16:54:12 root@harbor-nfs ~]#ll /data/k8sdata/mysql/mysql-datadir-{1..6}
/data/k8sdata/mysql/mysql-datadir-1:
total 8
drwxr-xr-x 2 root root 4096 Oct 20 16:54 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
/data/k8sdata/mysql/mysql-datadir-2:
total 12
drwxr-xr-x 3 root root 4096 Oct 20 18:05 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
drwxr-xr-x 7 999 root 4096 Oct 20 18:18 mysql/
/data/k8sdata/mysql/mysql-datadir-3:
total 12
drwxr-xr-x 3 root root 4096 Oct 20 18:05 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
drwxr-xr-x 7 999 root 4096 Oct 20 18:18 mysql/
/data/k8sdata/mysql/mysql-datadir-4:
total 8
drwxr-xr-x 2 root root 4096 Oct 20 16:54 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
/data/k8sdata/mysql/mysql-datadir-5:
total 12
drwxr-xr-x 3 root root 4096 Oct 20 18:05 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../
drwxr-xr-x 7 999 root 4096 Oct 20 18:18 mysql/
/data/k8sdata/mysql/mysql-datadir-6:
total 8
drwxr-xr-x 2 root root 4096 Oct 20 16:54 ./
drwxr-xr-x 8 root root 4096 Oct 20 16:54 ../