內容目錄
MySQL的高可用方案
MySQL高可用方案採用主從複製+讀寫分離,即由單一的master和多個slave所構成。其中,客戶端通過master對資料庫進行寫操作,通過slave端進行讀操作。master出現問題後,可以將應用切換到slave端。 此方案是MySQL官方提供的一種高可用解決方案,節點間的資料同步採用MySQL Replication技術。MySQL Replication從一個MySQL資料庫伺服器(master)的資料複製到一個或多個MySQL資料庫伺服器(slave)。在預設情況下,複製是非同步的;slave不需要一直接收來自主機的更新。根據配置,可以複製資料庫中的所有資料庫、選定的資料庫,或者特定的表。
架構說明
通過mysql+xtrabackup的模式來組成資料庫master+slave的模式,具體的架構圖如下所示:
Staefulset搭建Mysql叢集
vi mysql-statefulset.yaml
apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql spec: # selector 表示,这个 StatefulSet 要管理的 Pod 必须携带 app=mysql 标签 selector: matchLabels: app: mysql # serviceName 表示 它声明要使用的 Headless Service 的名字是:mysql serviceName: mysql # StatefulSet 的 replicas 值是 3,表示它定义的 MySQL 集群有三个节点:一个 Master 节点,两个 Slave 节点。 replicas: 3 template: metadata: labels: app: mysql spec: # 需要进行一个初始化操作,根据节点的角色是 Master 还是 Slave 节点,为 Pod 分配对应的配置文件。此外,MySQL 还要求集群里的每个节点都有一个唯一的 ID 文件,名叫 server-id.cnf initContainers: - name: init-mysql image: mysql:5.7 command: - bash - "-c" - | set -ex # 从 Pod 的 hostname 里,读取到了 Pod 的序号,以此作为 MySQL 节点的 server-id [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 ordinal=${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/目录; # 否则,拷贝Slave的配置文件 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 # init-mysql 在声明了挂载 config-map 这个 Volume 之后,ConfigMap 里保存的内容,就会以文件的方式出现在它的 /mnt/config-map 目录当中 volumeMounts: - name: conf mountPath: /mnt/conf.d - name: config-map mountPath: /mnt/config-map - name: clone-mysql # 使用的是 xtrabackup 镜像(它里面安装了 xtrabackup 工具) image: gcr.io/google-samples/xtrabackup:1.0 command: - bash - "-c" - | set -ex # 拷贝操作只需要在第一次启动时进行,所以如果数据已经存在,跳过 [[ -d /var/lib/mysql/mysql ]] && exit 0 # Master节点(序号为0)不需要做这个操作 [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 ordinal=${BASH_REMATCH[1]} [[ $ordinal -eq 0 ]] && exit 0 # 使用ncat指令,远程地向 DNS 记录为“mysql-< 当前序号减一 >.mysql”的 Pod,也就是当前 Pod 的前一个 Pod,发起数据传输请求,并且直接用 xbstream 指令将收到的备份数据保存在 /var/lib/mysql 目录下; # 3307 是一个特殊端口,运行着一个专门负责备份 MySQL 数据的辅助进程 # 这一步还可以用其他方法来传输数据。比如,用 scp 或者 rsync,都没问题 ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql # 执行--prepare,这样拷贝来的数据就可以用作恢复了 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: mysql:5.7 env: - name: MYSQL_ALLOW_EMPTY_PASSWORD value: "1" ports: - name: mysql containerPort: 3306 volumeMounts: - name: data # 数据目录是 /var/lib/mysql mountPath: /var/lib/mysql subPath: mysql - name: conf # 配置文件目录是 /etc/mysql/conf.d mountPath: /etc/mysql/conf.d resources: requests: cpu: 500m cpu: 200m memory: 1Gi memory: 500m # 定义了一个 livenessProbe,通过 mysqladmin ping 命令来检查它是否健康 livenessProbe: exec: command: ["mysqladmin", "ping"] initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 # 定义了一个 readinessProbe,通过查询 SQL(select 1)来检查 MySQL 服务是否可用,凡是 readinessProbe 检查失败的 MySQL Pod,都会从 Service 里被摘除掉。 readinessProbe: exec: # 通过TCP连接的方式进行健康检查 command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"] initialDelaySeconds: 5 periodSeconds: 2 timeoutSeconds: 1 - name: xtrabackup image: gcr.io/google-samples/xtrabackup:1.0 ports: - name: xtrabackup containerPort: 3307 command: - bash - "-c" - | set -ex cd /var/lib/mysql #第一部分工作,是 MySQL 节点的初始化工作。这个初始化需要使用的 SQL,是 sidecar 容器拼装出来、保存在一个名为 change_master_to.sql.in 的文件里的,具体过程如下所示 # 从备份信息文件里读取MASTER_LOG_FILEM和MASTER_LOG_POS这两个字段的值,用来拼装集群初始化SQL if [[ -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_info elif [[ -f xtrabackup_binlog_info ]]; then # 如果只存在xtrabackup_binlog_inf文件,那说明备份来自于Master节点,我们就需要解析这个备份信息文件,读取所需的两个字段的值 [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1 # 同上,这里也就用不着xtrabackup_binlog_info了,否则,下次这个容器重启时,就会发现这些文件存在,所以又会重新执行一次数据恢复和集群初始化的操作,这是不对的 rm 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.in fi # 接下来,sidecar 容器就可以执行初始化了 # 如果这个 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 -e "SELECT 1"; do sleep 1; done echo "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 <<EOF $(<change_master_to.sql.orig), MASTER_HOST='mysql-0.mysql', MASTER_USER='root', MASTER_PASSWORD='', MASTER_CONNECT_RETRY=10; START SLAVE; EOF fi #第二部分工作,则是启动一个数据传输服务,具体过程如下所示 # 使用ncat监听3307端口。它的作用是,在收到传输请求的时候,直接执行"xtrabackup --backup"命令,备份MySQL的数据并发送给请求者 # 由于 sidecar 容器和 MySQL 容器同处于一个 Pod 里,所以它是直接通过 Localhost 来访问和备份 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" volumeMounts: - name: data mountPath: /var/lib/mysql subPath: mysql - name: conf mountPath: /etc/mysql/conf.d volumes: - name: conf emptyDir: {} - name: config-map configMap: name: mysql # 通过 volumeClaimTemplate(PVC 模板)来为每个 Pod 定义 PVC,将来,这个 PV 对应的的 Volume 就会充当 MySQL Pod 的存储数据目录 volumeClaimTemplates: - metadata: name: data spec: # 指定了该存储的属性为可读写,并且一个 PV 只允许挂载在一个宿主机上 accessModes: ["ReadWriteOnce"] storageClassName: standard resources: requests: # 指定了存储的大小为 10 GiB storage: 10Gi
vi mysql-service.yaml
apiVersion: v1 kind: Service metadata: name: mysql labels: app: mysql spec: ports: - name: mysql port: 3306 clusterIP: None selector: app: mysql --- apiVersion: v1 kind: Service metadata: name: mysql-read labels: app: mysql spec: ports: - name: mysql port: 3306 selector: app: mysql
vi mysql-configmap.yaml
apiVersion: v1 kind: ConfigMap metadata: name: mysql labels: app: mysql data: master.cnf: | # 主节点MySQL的配置文件 [mysqld] log-bin slave.cnf: | # 从节点MySQL的配置文件 [mysqld] super-read-only
佈署資源
kubectl apply -f mysql-configmap.yaml
kubectl apply -f mysql-services.yaml
kubectl apply -f mysql-statefulset.yaml
觀察服務啟動狀態
或使用kubectl查詢
kubectl get pods -l app=mysql --watch
kubectl get pod -l app=mysql
測試mysql是否正常
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\ mysql -h mysql-read -e "CREATE DATABASE k8smysql;" kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\ mysql -h mysql-read -e "show databases;"
擴容一下sts資源
kubectl scale statefulset mysql --replicas=4
可以看到新建出來的複本mysql slave一樣有一開始建立的資料庫
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\ mysql -h mysql-3.mysql -e "show databases;"