k8s部署一个带有mysql应用_Kubernetes之有状态应用实践-搭建MySQL集群


零、前情纪要
上一遍文章
一、知识储备
Pod: Kubernetes的基本调度单元称为“pod”。通过该种抽象类别可以把更级别的抽象内容增加到容器化组件。一个pod一般包含一个或多个容器,这样可以保证它们一直位于主机上,并且可以共享资源。Kubernetes中的每个pod都被分配一个唯一的(在集群内的)IP地址这样久可以运行应用程序使用同一端口,而避免了发生冲突的问题。Pod可以定义一个卷,例如本地磁盘目录或网格磁盘,并将其暴露在pod中的一个容器之中。pod可以通过Kubernetes API手动管理,也可以委托给控制器来实现自动管理。
标签和选择器:Kubernetes是客户端(用户或内部组件)将称为“标签”的键值对附加到系统中的任何API对象,如pod和节点。相应地,“标签选择器”是针对匹配对象的标签的查询方法。标签和选择器是Kubernetes中的主要分组机制,用户确定操作适用的组件。
控制器:控制器是通过管理一组pod来实现来将实际集群状态转移到所需集群状态的对账循环机制。目前kubernetes官网提到的控制器有“ReplicaSet”、“ReplicationController”、“Deployments”、“StatefulSets”、“DaemonSet”、“Garbage Collection”、“TTL Controller for Finished Resources”、“Jobs - Run to Completion”和“CronJjo”。
ReplicationController:ReplicationController确保在任何时候都有特定数量的pod副本处于运行状态。换句话说,ReplicationController确保一个pod或一组同类的pod总是可用的。
ReplicaSet: ReplicaSet是下一代的Replication Controller。ReplicaSet和Replication Controller的唯一区别是选择器的支持。ReplicaSet支持新的基于集合的选择器需求,这在标签用户指南中有描述。而Replication Controller 仅支持基于相等选择器的需求。虽然ReplicaSets可以独立使用,但今天它主要被Deployments用作协调Pod创建、删除和更新的机制。当您使用Deployment时,您不必担心还要管理它们创建的ReplicaSet。Deployment会拥有并管理它们的ReplicaSet。
Deployments:一个Deployment控制器为Pods和ReplicaSets提供描述性的更新方式。描述Deployment中的desired state,并且Deployment控制器以受控速率更改实际状态,以达到期望状态。可以定义Deployments以创建新的ReplicaSets,或删除现有Deployments,并通过新的Deployments使用其所有资源。
StatefulSets:本文需要重点理解的概念,StatefulSet是用来管理有状态应用的工作负载API对象。StatefulSet用来管理Deployment和扩展一组Pod,并且能为这些Pod提供序号和唯一性保证。和Deployment相同的是,StatefulSet管理了基于相同容器定义的一组Pod。但和deployment不同的是,StatefulSet为它们的每个Pod维护了一个固定的ID。这些Pod是基于相同的声明来创建的,但是不能相互替换:无论怎么调度,每个Pod都有一个永久不变的ID。StatefulSet和其他控制器使用相同的工作模式。你在StatefulSet对象中定义你期待的状态,然后StatefulSet的控制器久会通过这种更新来达到那种你想要的状态。
有状态应用:实例之间存在不对等关系,比如:主从关系、主备关系的,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”。
Volumes:容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些问题。首先,当容器崩溃时,kubelet将重新启动容器,容器中的文件将会丢失--因为容器会以干净的状态重建。其次,当在一个Pod中同时运行多个容器时,常常需要在这些容器之间共享文件。Kubernetes抽象出Volume对象来解决这两个问题。
PersistentVolume (PV):PV时集群中的一块存储,由管理员提供或者使用存储类动态提供。它是集群中的资源,就像节点是集群资源一样。PV是类型Volumes的卷的插件,但是其生命周期与使用PV的任何单个Pod无关。
A PersistentVolumeClaim (PVC):PVC是用户存储请求。它类似于豆荚。容器消耗节点资源,PVC消耗PV资源。Pod可以请求特定大小的CPU和内存的资源。PVC可以请求特定的大小和访问模式(例如,一次读写或多次读写)的磁盘资源。
ConfigMap:ConfigMap是一种API对象,用来将非机密性的数据保存到健值对中。使用时可以用作环境变量、命令行参数或者存储卷中的配置文件。ConfigMap将您的环境配置和容器镜像解耦,便于应用配置的修改。当您需要存储机密信息时可以使用Secret对象。
Services:Kubernetes服务本质是一组协同工作的pod,类同多层架构应用中的一层。构成服务的pod组通过标签选择器来定义。Kubernetes通过给服务分配静态IP地址和域名来提供服务发现机制,并且以轮询调度的方式将流量负载均衡到能于选择器匹配的pod的IP地址的网络连接上。
Headless Services:有时不需要或不想要负载均衡,以及单独的 Service IP。遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为“None”来创建 Headless Service。可以用 headless Service 与其他服务发现机制进行接口,而不需要与Kubernetes的实现捆绑在一起。对于 headless Service 并不会分配 Cluster IP,kube-proxy不会处理它们,而且平台也不会为它们进行负载均衡和路由。DNS如何实现自动配置,依赖于 Service 是否定义了selector
ps:概念比较多,建议大概浏览一次,下面浏览搭建过程的时候,遇到再回顾加深记忆即可。
二、MySQL集群搭建
2.1、集群要求搭建一个一主 N 从的 MySQL 集群;
从节点可以水平扩展;
所有的写操作,都只能在主节点(Master)上执行;
所有的读操作可以在所有节点上执行;
2.2、物理机环境搭建集群
下面,我们看一张MySQL主从复制的原理图:
上面的原理图告诉我们,部署这样一个一主多从的 MySQL 集群,关键在于配置 Master 节点和 Slave 节点的复制和同步。
下面我们用自然语言描述在物理机上部署这样的集群的关键步骤;1、配置并安装好 Master 节点的 MySQL;
2、通过XtraBackup将Master节点的数据备份到指定目录;
3、将第2步备份出来的目录,连同备份信息文件,拷贝到Slave的/var/lib/mysql下,然后执行CHANGE MASTER TO指令;
4、启动 Slave 节点,执行START SLAVE指令;
5、在这个集群中添加更多的Slave节点;
2.3 Kubernetes 上搭建一主多从的 MySQL 集群
2.3.1 难点
不同于物理机,在 Kubernetes 上搭建一主多从的 MySQL 集群,结合容器的技术特色,我们思考以下需要解决的困难点:Master 节点和 Slave 节点需要有不同的配置文件;
Master 节点和 Slave 节点需要能够传输备份信息文件;
第一次启动 Slave 节点,需要执行一些初始化SQL操作;
2.3.2 使用NFS做持久化存储
2.3.2.1 搭建NFS服务器
我们这里为了演示方便,决定使用相对简单的 NFS 这种存储资源。
nfs 卷能将NFS(网络文件系统)挂载到您的Pod中。不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,Volume 只是会被卸载掉。这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间“传递”
下面我们在 sz-fengyin-3 服务器上搭建NFS服务:
### 关闭防火墙
[root@sz-fengyin-3 ~]# systemctl stop firewalld.service
[root@sz-fengyin-3 ~]# systemctl disable firewalld.service
### 安装
[root@sz-fengyin-3 ~]# yum -y install nfs-utils rpcbind
### 启动 rpcbind
[root@sz-fengyin-3 ~]# systemctl start rpcbind
[root@sz-fengyin-3 ~]# systemctl enable rpcbind
systemctl start nfs-server
[root@sz-fengyin-3 ~]# systemctl enable nfs-server
[root@sz-fengyin-3 ~]# mkdir -p /data0/dbData/nfs/k8s/mysql
### 配置说明:
### /data0/dbData/nfs/k8s/mysql:是共享的数据目录
### 172.18.144.0/24:表示任何人都有权限连接,当然也可以是一个网段,一个 IP,也可以是域名
### rw:读写的权限
### sync:表示文件同时写入硬盘和内存
### no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份
[root@sz-fengyin-3 ~]# more /etc/exports
/data0/dbData/nfs/k8s/mysql 172.18.144.0/24(rw,sync,no_root_squash)
### 启动 nfs 服务
[root@sz-fengyin-3 ~]# systemctl restart nfs.service
[root@sz-fengyin-3 ~]# systemctl enable nfs
[root@sz-fengyin-3 ~]# systemctl status nfs
● nfs-server.service - NFS server and services
Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; vendor preset: disabled)
Drop-In: /run/systemd/generator/nfs-server.service.d
└─order-with-mounts.conf
Active: active (exited) since 二 2020-06-09 09:35:55 CST; 4h 9min ago
Process: 20003 ExecStopPost=/usr/sbin/exportfs -f (code=exited, status=0/SUCCESS)
Process: 20002 ExecStopPost=/usr/sbin/exportfs -au (code=exited, status=0/SUCCESS)
Process: 19999 ExecStop=/usr/sbin/rpc.nfsd 0 (code=exited, status=0/SUCCESS)
Process: 20033 ExecStartPost=/bin/sh -c if systemctl -q is-active gssproxy; then systemctl reload gssproxy ; fi (code=exited, status=0/SUCCESS)
Process: 20016 ExecStart=/usr/sbin/rpc.nfsd $RPCNFSDARGS (code=exited, status=0/SUCCESS)
Process: 20014 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
Main PID: 20016 (code=exited, status=0/SUCCESS)
Tasks: 0
Memory: 0B
CGroup: /system.slice/nfs-server.service
6月 09 09:35:55 sz-fengyin-3 systemd[1]: Starting NFS server and services...
6月 09 09:35:55 sz-fengyin-3 systemd[1]: Started NFS server and services.
### 确认 NFS Server 启动成功
[root@sz-fengyin-3 ~]# rpcinfo -p|grep nfs
100003 3 tcp 2049 nfs
100003 4 tcp 2049 nfs
100227 3 tcp 2049 nfs_acl
100003 3 udp 2049 nfs
100003 4 udp 2049 nfs
100227 3 udp 2049 nfs_acl
### 查看具体目录挂载权限
[root@sz-fengyin-3 ~]# showmount -e 172.18.144.45
Export list for 172.18.144.45:
/data0/dbData/nfs/k8s/mysql 172.18.144.0/24
接下来我们在 sz-fengyin-[1:3] 服务器上安装nfs的客户端,由此验证nfs服务器,主要三个服务器都需要配置免密登陆。下面是在 sz-fengyin-1 上的安装步骤
### 关闭防火墙
[root@sz-fengyin-1 ~]# systemctl stop firewalld.service
[root@sz-fengyin-1 ~]# systemctl disable firewalld.service
### 安装
[root@sz-fengyin-1 ~]# yum -y install nfs-utils rpcbind
### 启动rpc
[root@sz-fengyin-1 ~]# systemctl start rpcbind.service
[root@sz-fengyin-1 ~]# systemctl enable rpcbind.service
### 启动 nfs 服务
[root@sz-fengyin-1 ~]# systemctl start nfs.service
[root@sz-fengyin-1 ~]# systemctl enable nfs.service
### 磁盘挂载
[root@sz-fengyin-1 ~]# mkdir -p /data0/dbData/k8s/mysql
[root@sz-fengyin-1 ~]# mount -t nfs 172.18.144.45:/data0/dbData/nfs/k8s/mysql /data0/dbData/k8s/mysql
### 再挂载磁盘上创建文件来验证 nfs 挂载是否成功
[root@sz-fengyin-1 ~]# cd /data0/dbData/k8s/mysql
[root@sz-fengyin-1 mysql]# touch test.txt
[root@sz-fengyin-1 mysql]# ls
test.txt
[root@sz-fengyin-3 ~]# cd /data0/dbData/nfs/k8s/mysql
[root@sz-fengyin-3 mysql]# ls
test.txt
2.3.2.2 NFS动态提供Kubernetes存储卷
因为NFS没有内部分配器,但是可以使用第三方存储供应商提供的外部分配器。下面我们使用
nfs-client Provisioner 是一个自动预配置程序,它使用您现有的已经配置的NFS服务器来支持通过PVC动态配置 Kubernetes PV。PV以 $ {namespace}-$ {pvcName}-$ {pvName} 的命令格式提供在NFS服务器上。
具体的实验配置步骤如下:配置nfs-client Provisioner 的Deployment
下面配置主要修改了环境变量的 NFS_SERVER 和 NFS_PATH 和 nfs配置。
[root@sz-fengyin-1 nfs-client-provisioner]# more nfs-deployment.yamlkind:DeploymentapiVersion:apps/v1metadata:name:nfs-client-provisionerspec:replicas:1selector:matchLabels:app:nfs-client-provisionerstrategy:type:Recreatetemplate:metadata:labels:app:nfs-client-provisionerspec:serviceAccountName:nfs-client-provisionercontainers:- name:nfs-client-provisionerimage:quay.io/external_storage/nfs-client-provisioner:latestvolumeMounts:- name:nfs-client-rootmountPath:/persistentvolumesenv:- name:PROVISIONER_NAMEvalue:fuseim.pri/ifs- name:NFS_SERVERvalue:172.18.144.45- name:NFS_PATHvalue:/data0/dbData/nfs/k8s/mysqlvolumes:- name:nfs-client-rootnfs:server:172.18.144.45path:/data0/dbData/nfs/k8s/mysql配置 serviceAccount
看到上面配置我们这里需要一个名为 nfs-client-provisioner 的 serviceAccount,下面是 serviceAccount 的配置文件
[root@sz-fengyin-1 nfs-client-provisioner]# more nfs-deployment-service-accout.yamlapiVersion:v1kind:ServiceAccountmetadata:name:nfs-client-provisioner---kind:ClusterRoleapiVersion:rbac.authorization.k8s.io/v1metadata:name:nfs-client-provisioner-runnerrules:- apiGroups:[""]resources:["persistentvolumes"]verbs:["get","list","watch","create","delete"]- apiGroups:[""]resources:["persistentvolumeclaims"]verbs:["get","list","watch","update"]- apiGroups:["storage.k8s.io"]resources:["storageclasses"]verbs:["get","list","watch"]- apiGroups:[""]resources:["events"]verbs:["list","watch","create","update","patch"]- apiGroups:[""]resources:["endpoints"]verbs:["create","delete","get","list","watch","patch","update"]---kind:ClusterRoleBindingapiVersion:rbac.authorization.k8s.io/v1metadata:name:run-nfs-client-provisionersubjects:- kind:ServiceAccountname:nfs-client-provisionernamespace:defaultroleRef:kind:ClusterRolename:nfs-client-provisioner-runnerapiGroup:rbac.authorization.k8s.io创建 StorageClass 对象
nfs-client 的 Deployment 声明完成后,我们就可以来创建一个StorageClass对象了。
[root@sz-fengyin-1 nfs-client-provisioner]# more nfs-deployment-storage-class.yamlapiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:course-nfs-storageprovisioner:fuseim.pri/ifs创建并查看资源对象
[root@sz-fengyin-1 nfs-client-provisioner]# kubectl create -f nfs-deployment.yaml
deployment.apps/nfs-client-provisioner created
[root@sz-fengyin-1 nfs-client-provisioner]# kubectl create -f nfs-deployment-service-accout.yaml
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
[root@sz-fengyin-1 nfs-client-provisioner]# kubectl create -f nfs-deployment-storage-class.yaml
storageclass.storage.k8s.io/course-nfs-storage created
[root@sz-fengyin-1 nfs-client-provisioner]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-dc5c55797-hnck6 1/1 Running 0 12s
nginx-deployment-5cc7f59bbc-w9mrj 0/1 Pending 0 18d
[root@sz-fengyin-1 nfs-client-provisioner]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
course-nfs-storage fuseim.pri/ifs Delete Immediate false 48s创建PVC对象
上面把StorageClass资源对象创建成功了,接下来我们来通过一个示例测试下动态 PV,这里需要创建一个 PVC 对象。
### 配置
### accessModes:支持三种类型
### ReadWriteMany 多路读写,卷能被集群多个节点挂载并读写
### ReadWriteOnce 单路读写,卷只能被单一集群节点挂载读写
### ReadOnlyMany 多路只读,卷能被多个集群节点挂载且只能读
###
####persistentVolumeReclaimPolicy:也有三种策略,这个策略是当与之关联的PVC被删除以后,这个PV中的数据如何被处理
### Retain 当删除与之绑定的PVC时候,这个PV被标记为released(PVC与PV解绑但还没有执行回收策略)且之前的数据依然保存在该PV上,但是该PV不可用,需要手动来处理这些数据并删除该PV。
### Delete 当删除与之绑定的PVC时候
### Recycle 这个在1.14版本中以及被废弃,取而代之的是推荐使用动态存储供给策略,它的功能是当删除与该PV关联的PVC时,自动删除该PV中的所有数据
[root@sz-fengyin-1 nfs-client-provisioner]# more test-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
annotations:
volume.beta.kubernetes.io/storage-class: "course-nfs-storage"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
### 创建pvc对象
[root@sz-fengyin-1 nfs-client-provisioner]# kubectl create -f test-pvc.yaml
persistentvolumeclaim/test-pvc created
### 查看绑定情况
[root@sz-fengyin-1 nfs-client-provisioner]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-pvc Bound pvc-4f173f15-5601-4f03-92f2-cc4a7dc71025 1Mi RWX course-nfs-storage 5s
### nfs服务器上对应的变化
[root@sz-fengyin-3 ~]# cd /data0/dbData/nfs/k8s/mysql
[root@sz-fengyin-3 mysql]# ll
总用量 4
drwxrwxrwx 2 root root 4096 6月 10 07:51 default-test-pvc-pvc-4f173f15-5601-4f03-92f2-cc4a7dc71025
查看Kubenetes管理页面:
2.3.3 ConfigMap 为 Master/Slave 节点分配不同的配置文件
根据开头介绍的ConfigMap概念可知,使用 ConfigMap 可以很好解决 MySQL集群中 Master 和 Slave 节点配置文件不一致的问题。ConfigMap 的配置信息如下:
[root@sz-fengyin-1 mysql]# more mysql-configmap.yamlapiVersion:v1kind:ConfigMapmetadata:name:mysqllabels:app:mysqldata:master.cnf:|# 主节点MySQL的配置文件[mysqld]log-binslave.cnf: |# 从节点MySQL的配置文件[mysqld]super-read-only
这里我们定义了 master.cnf 和 slave.cnf 两个MySQL的配置文件。master.cnf 开启了 log-bin,即: 使用二进制日志文件的方式进行主从复制。
slave.cnf 开启了 super-read-only,表示从节点拒绝除了主节点的数据同步操作之外的所有写操作,即它对用户是只读的。
创建ConfigMap
[root@sz-fengyin-1 mysql]# kubectl create -f mysql-configmap.yaml
configmap/mysql created
2.3.4 Secret 为 集群配置密码
[root@sz-fengyin-1 mysql]# more mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
labels:
app: mysql
type: Opaque
data:
password: ZmVuZ3lpbg== # echo -n "fengyin" | base64
创建secret:
[root@sz-fengyin-1 mysql]# kubectl create -f mysql-secret.yaml
secret/mysql-secret created
2.3.5 Service 为 StatefulSet和用户提供服务发现
这里我们需要创建两个service,两个Service的配置如下:
[root@sz-fengyin-1 mysql]# more mysql-service.yamlapiVersion:v1kind:Servicemetadata:name:mysqllabels:app:mysqlspec:ports:- name:mysqlport:3306clusterIP:Noneselector:app:mysql---apiVersion:v1kind:Servicemetadata:name:mysql-readlabels:app:mysqlspec:ports:- name:mysqlport:3306selector:app:mysql
配置介绍:两个Service 都代理了所有携带 app=mysql 标签的 Pod,也就是所有的 MySQL Pod。端口映射都是用 Service 的3306端口对应 Pod 的 3306 端口
一个名叫 “mysql” Service 是 Headless Service (即:clusterIP=None)。它的作用是通过为 Pod 分配 DNS 记录来固定 Pod 集群,比如 “mysql-0.mysql” 和 “mysql-1.mysql” 这样的 DNS 名字。其中, “mysql-0.mysql” 的节点就是我们的主节点;名叫 “mysql-read” 的 Service 是一个常规的 Service
规定,所有用户的读请求必须访问名叫 “mysql-read” 的Service被分配的DNS记录上,这样,读请求就可以转发到任意一个 MySQL 的主节点或者从节点上;所有用户的写请求,则必须访问到 MySQL 的主节点,即 “mysql-0.mysql” 这条 DNS 记录
创建 Services:
[root@sz-fengyin-1 mysql]# kubectl apply -f mysql-service.yaml
service/mysql created
service/mysql-read created
2.3.6 使用 StatefulSet 搭建 MySQL 集群
[root@sz-fengyin-1 mysql]# more mysql-statefulset.yamlapiVersion:apps/v1kind:StatefulSetmetadata:name:mysqllabels:app:mysqlspec:selector:matchLabels:app:mysqlserviceName:mysqlreplicas:2template:metadata:labels:app:mysqlspec:initContainers:- image:busyboxname:mysql-volume-cleanerargs:[/bin/sh, -c, 'rm -rf /var/lib/mysql/* || true']volumeMounts:- mountPath:/var/lib/mysqlname:datasubPath:mysql- name:init-mysqlimage:mysql:5.7env:- name:MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name:mysql-secretkey:passwordcommand:- bash- "-c"- |set -ex# 从 Pod 的序号,生成 server-id[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1ordinal=${BASH_REMATCH[1]}echo [mysqld] > /mnt/conf.d/server-id.cnf# 由于 server-id 不能为 0,因此给 ID 加 100 来避开它echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf# 如果 Pod 的序号为 0,说明它是 Master 节点,从 ConfigMap 里把 Master 的配置文件拷贝到 /mnt/conf.d 目录下# 否则,拷贝 ConfigMap 里的 Slave 的配置文件if [[ ${ordinal} -eq 0 ]]; thencp /mnt/config-map/master.cnf /mnt/conf.delsecp /mnt/config-map/slave.cnf /mnt/conf.dfivolumeMounts:- name: confmountPath: /mnt/conf.d- name: config-mapmountPath: /mnt/config-map- name: clone-mysqlimage: gcr.io/google-samples/xtrabackup:1.0imagePullPolicy: IfNotPresentenv:- name: MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name: mysql-secretkey: passwordcommand:- bash- "-c"- |set -ex# 拷贝操作只需要在第一次启动时进行,所以数据已经存在则跳过[[ -d /var/lib/mysql/mysql ]] && exit 0# Master 节点(序号为 0)不需要这个操作[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1ordinal=${BASH_REMATCH[1]}[[ $ordinal == 0 ]] && exit 0# 使用 ncat 指令,远程地从前一个节点拷贝数据到本地ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql# 执行 --prepare,这样拷贝来的数据就可以用作恢复了xtrabackup --prepare --target-dir=/var/lib/mysqlvolumeMounts:- name: datamountPath: /var/lib/mysqlsubPath: mysql- name: confmountPath: /etc/mysql/conf.dcontainers:- name: mysqlimage: mysql:5.7env:- name: MYSQL_ALLOW_EMPTY_PASSWORDvalue: "1"- name: MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name: mysql-secretkey: passwordports:- name: mysqlcontainerPort: 3306volumeMounts:- name: datamountPath: /var/lib/mysqlsubPath: mysql- name: confmountPath: /etc/mysql/conf.dresources:requests:cpu: 500mmemory: 1GilivenessProbe:# exec:# command: ["mysqladmin", "ping", "-h127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]tcpSocket:port: 3306initialDelaySeconds: 30periodSeconds: 10timeoutSeconds: 5readinessProbe:# exec:# 通过TCP连接的方式进行健康检查# command: ["mysql", "-h127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD}", "-e" "SELECT 1"]tcpSocket:port: 3306initialDelaySeconds: 5periodSeconds: 2timeoutSeconds: 1- name: xtrabackupimage: gcr.io/google-samples/xtrabackup:1.0imagePullPolicy: IfNotPresentports:- name: xtrabackupcontainerPort: 3307env:- name: MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name: mysql-secretkey: passwordcommand:- bash- "-c"- |set -excd /var/lib/mysql# 从备份信息文件里读取 MASTER_LOG_FILE 和 MASTER_LOG_POS 这 2 个字段的值,用来拼装集群初始化 SQLif [[ -f xtrabackup_slave_info ]]; then# 如果 xtrabackup_slave_info 文件存在,说明这个备份数据来自于另一个 Slave 节点# 这种情况下,XtraBackup 工具在备份的时候,就已经在这个文件里自动生成了 "CHANGE MASTER TO" SQL 语句# 所以,只需要把这个文件重命名为 change_master_to.sql.in,后面直接使用即可mv xtrabackup_slave_info change_master_to.sql.in# 所以,也就用不着 xtrabackup_binlog_info 了rm -f xtrabackup_binlog_infoelif [[ -f xtrabackup_binlog_info ]]; then# 如果只是存在 xtrabackup_binlog_info 文件,说明备份来自于 Master 节点,就需要解析这个备份信息文件,读取所需的两个字段的值[[ $(cat xtrabackup_binlog_info) =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1rm xtrabackup_binlog_info# 把两个字段的值拼装成 SQL,写入 change_master_to.sql.in 文件echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.infi# 如果存在 change_master_to.sql.in,就意味着需要做集群初始化工作if [[ -f change_master_to.sql.in ]]; then# 但一定要先等 MySQL 容器启动之后才能进行下一步连接 MySQL 的操作echo "Waiting for mysqld to be ready(accepting connections)"until mysql -h 127.0.0.1 -uroot -p${MYSQL_ROOT_PASSWORD} -e "SELECT 1"; do sleep 1; doneecho "Initializing replication from clone position"# 将文件 change_master_to.sql.in 改个名字# 防止这个 Container 重启的时候,因为又找到了 change_master_to.sql.in,从而重复执行一遍初始化流程mv change_master_to.sql.in change_master_to.sql.orig# 使用 change_master_to.sql.orig 的内容,也就是前面拼装的 SQL,组成一个完整的初始化和启动 Slave 的 SQL 语句mysql -h 127.0.0.1 -uroot -p${MYSQL_ROOT_PASSWORD} << EOF$(< change_master_to.sql.orig),MASTER_HOST='mysql-0.mysql',MASTER_USER='root',MASTER_PASSWORD='${MYSQL_ROOT_PASSWORD}',MASTER_CONNECT_RETRY=10; START SLAVE; EOFfi# 使用 ncat 监听 3307 端口。# 它的作用是,在收到传输请求的时候,直接执行 xtrabackup --backup 命令,备份 MySQL 的数据并发送给请求者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 --password=${MYSQL_ROOT_PASSWORD}"volumeMounts:- name: datamountPath: /var/lib/mysqlsubPath: mysql- name: confmountPath: /etc/mysql/conf.dvolumes:- name: confemptyDir: {}- name: config-mapconfigMap:name: mysqlvolumeClaimTemplates:- metadata:name: dataspec:accessModes:- "ReadWriteOnce"storageClassName: course-nfs-storageresources:requests:storage: 3Gi
创建StatefulSet
[root@sz-fengyin-1 mysql]# kubectl apply -f mysql-statefulset.yaml
如出现以下报错:
2019-08-25T15:36:470279Z 0 [ERROR] --initialize specified but the data directory has files in it. Aborting
2019-08-09T15:36:470367Z 0 [ERROR] Aborting
需要添加一个 initContainers 做初始化
- image:busyboxname:mysql-volume-cleanerargs:[/bin/sh, -c, 'rm -rf /var/lib/mysql/* || true']volumeMounts:- mountPath:/var/lib/mysqlname:datasubPath:mysql
2.3.7 验证查看容器状态
[root@sz-fengyin-1 mysql]# kubectl get pod -A -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
default mysql-0 2/2 Running 0 116m 10.244.1.161 sz-fengyin-2
default mysql-1 2/2 Running 1 115m 10.244.2.110 sz-fengyin-3 查看主从配置
[root@sz-fengyin-1 mysql]# kubectl -n default exec mysql-1 -c mysql -- bash -c "mysql -uroot -pfengyin -e 'show slave status \G'"
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: mysql-0.mysql
Master_User: root
Master_Port: 3306
Connect_Retry: 10
Master_Log_File: mysql-0-bin.000003
Read_Master_Log_Pos: 154
Relay_Log_File: mysql-1-relay-bin.000002
Relay_Log_Pos: 322
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: 154
Relay_Log_Space: 531
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: 44ff2fa6-bf89-11ea-b7b2-96040b41e265
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:验证主从数据是否同步
### 现在主库创建测试的数据库、表和数据
[root@sz-fengyin-1 mysql]# kubectl exec -it mysql-0 -c mysql -n default -- /bin/bash
root@mysql-0:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4208
Server version: 5.7.30-log MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
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.
mysql> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| xtrabackup_backupfiles |
+------------------------+
5 rows in set (0.01 sec)
mysql> create database test
-> ;
Query OK, 1 row affected (0.00 sec)
mysql> use test
Database changed
mysql> create table counter(id int);
Query OK, 0 rows affected (0.03 sec)
mysql> insert into counter values(123);
Query OK, 1 row affected (0.02 sec)
mysql> exit
Bye
root@mysql-0:/# exit
exit
### 去从库查看数据是否存在
[root@sz-fengyin-1 mysql]# kubectl exec -it mysql-1 -c mysql -n default -- /bin/bash
root@mysql-1:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4228
Server version: 5.7.30 MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
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.
mysql> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.01 sec)
mysql> use test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select * from counter;
+------+
| id |
+------+
| 123 |
+------+
1 row in set (0.00 sec)
2.3.8 扩展 MySQL 集群
在有了 StatefulSet 以后,你就可以像 Deployment 那样,非常方便地扩展这个 MySQL 集群
### 扩展节点命令
[root@sz-fengyin-1 mysql]# kubectl scale statefulset mysql --replicas=3
statefulset.apps/mysql scaled
### 查看容器状态
[root@sz-fengyin-1 mysql]# kubectl get pod -A -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
default mysql-0 2/2 Running 0 121m 10.244.1.161 sz-fengyin-2
default mysql-1 2/2 Running 1 121m 10.244.2.110 sz-fengyin-3
default mysql-2 2/2 Running 1 50s 10.244.1.162 sz-fengyin-2
### 查看新扩展的节点是否主动同步数据
[root@sz-fengyin-1 mysql]# kubectl exec -it mysql-2 -c mysql -n default -- /bin/bash
root@mysql-2:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 48
Server version: 5.7.30 MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
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.
mysql> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.00 sec)
mysql> use test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select * from counter;
+------+
| id |
+------+
| 123 |
+------+
1 row in set (0.00 sec)
mysql>
2.3.9查看版本信息
[root@sz-fengyin-1 ~]# kubectl get controllerrevision
NAME CONTROLLER REVISION AGE
mysql-646f855877 statefulset.apps/mysql 1 10h
web-6bc849cb6b statefulset.apps/web 1 45d
?
[root@sz-fengyin-1 ~]# kubectl describe controllerrevision mysql-646f855877
2.3.10 删除
[root@sz-fengyin-1 mysql]# kubectl delete -f mysql-statefulset.yaml
statefulset.apps "mysql" deleted
三、 待解决问题
使用如下配置会出现 Access denied for user 'root'@'localhost' (using password: YES) 问题,还没有找到解决方案,先用TCP探测替代:
livenessProbe:exec:command:["mysqladmin","ping","-h127.0.0.1","-uroot","-p${MYSQL_ROOT_PASSWORD}"]readinessProbe:exec:# 通过TCP连接的方式进行健康检查command:["mysql","-h127.0.0.1","-uroot","-p${MYSQL_ROOT_PASSWORD}","-e""SELECT 1"]
[root@sz-fengyin-1 mysql]# kubectl logs -f mysql-0 -c mysql
2020-06-30T05:08:53.460538Z 12318 [Note] Access denied for user 'root'@'localhost' (using password: YES)
2020-06-30T05:08:55.466126Z 12319 [Note] Access denied for user 'root'@'localhost' (using password: YES)
2020-06-30T05:08:57.467856Z 12320 [Note] Access denied for user 'root'@'localhost' (using password: YES)
2020-06-30T05:08:58.203240Z 12321 [Note] Access denied for user 'root'@'localhost' (using password: YES)
2020-06-30T05:08:59.464400Z 12322 [Note] Access denied for user 'root'@'localhost' (using password: YES)
2020-06-30T05:09:01.462945Z 12323 [Note] Access denied for user 'root'@'localhost' (using password: YES)
2020-06-30T05:09:03.465287Z 12324 [Note] Access denied for user 'root'@'localhost' (using password: YES)
2020-06-30T05:09:05.463037Z 12325 [Note] Access denied for user 'root'@'localhost' (using password: YES)
【k8s部署一个带有mysql应用_Kubernetes之有状态应用实践-搭建MySQL集群】2020-06-30T05:09:07.494706Z 12326 [Note] Access denied for user 'root'@'localhost' (using password: YES)

    推荐阅读