侧边栏壁纸
博主头像
张种恩的技术小栈博主等级

行动起来,活在当下

  • 累计撰写 748 篇文章
  • 累计创建 65 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

在Kubernetes中部署MySQL主从集群

zze
zze
2020-10-19 / 1 评论 / 0 点赞 / 683 阅读 / 7793 字

不定期更新相关视频,抖音点击左上角加号后扫一扫右方侧边栏二维码关注我~正在更新《Shell其实很简单》系列

额。。。你没看错,就是在 Kubernetes 中部署 MySQL 主从,虽然我感觉 MySQL 丢 Kubernetes 有点蛋疼吧,但没办法,公司要求我只能照做了。。

思路

就直接说一下编写下面 YAML 的思路了:

  1. 首先,创建了一个 ConfigMap,其中包含了 MySQL 配置文件的模板,用于动态为各个 MySQL Pod 生成配置文件,这个功能由 StatefulSet 中的 initContainers 中的 mysql-sidecar-init 容器提供,在模板中可以看到已经开启了 binlogrelaylog 了;
  2. 然后创建了一个 StatefulSet,就是用它来管理 MySQL Pod,它有一个特点就是 Pod 名称是有编号的,比如下面的 StatefulSet 名称为 mysql-server,那么它管理的 Pod 名称就为 mysql-server-0mysql-server-1mysql-server-2 ...;
  3. 所以这里我就约定第一个创建的 Pod 为主库,后续所有创建的 Pod 都是第一个 Pod 的从库,所以这里就可以确定主库的名字就一直是 mysql-server-0 了;
  4. 现在唯一的一个问题就是如何构建主从关系了,这里就要用到 Kubernetes 内部的 DNS 了,在 Kubernetes 中可以通过 <pod_name>.<service_name>.<namespace>.svc.cluster.local 的方式来访问名为 <pod_name>Pod,所以在下面的示例中,主库的访问地址就固定为 mysql-server-0.mysql-server.test.svc.cluster.local 了,<server_name> 通过环境变量 SERVICE_NAME 传入,<pod_name>namespace 直接通过 DownwardAPI 获取;
  5. 主库的地址已经确定了,最后就要考虑的是如何去执行 SQL 了,因为构建 MySQL 主从关系首先需要在主库创建复制用户,然后在从库执行 change master ...,这里就用到了 Pod 的生命周期钩子了,如下通过 lifecycle.postStart 指定了 Pod 处于 running 状态之前执行的命令,这个命令指定了运行一个 /opt/run 二进制文件,它是我用 python 的并打包好的二进制文件(需要源码的可以留言邮箱我发,绝对无毒。),可以自动根据上述逻辑完成主从关系的维护。当然,在这里你完全可以在钩子这写一大片 Shell 来完成我上述所说的功能,但是实在是太不优雅了?
  6. 最后就可以运行这个 YAML 了,当然前提是你也准备好了 StorageClassName 提供 PV 的动态创建供给,我这里底层存储用的 Ceph,当然你也可以用 NFS 或其它的;

效果

$ kubectl apply -f mysql-cluster.yml
configmap/mysql-config-template created
statefulset.apps/mysql-server created
service/mysql-server created

# 因为 replicas 指定为 2,所以只会创建两个 Pod
$ kubectl get pod -n test 
NAME             READY   STATUS    RESTARTS   AGE
mysql-server-0   1/1     Running   0          45m
mysql-server-1   1/1     Running   0          42m

# 检查主库的状态,可以看到 binlog 成功开启了
kubectl -n test exec mysql-server-0 -- mysql -uroot -p123456 -e 'show master status;'
mysql: [Warning] Using a password on the command line interface can be insecure.
File    Position        Binlog_Do_DB    Binlog_Ignore_DB        Executed_Gtid_Set
mysql-bin.000001        441                     62347151-11e9-11eb-8bb2-0615ac65ca6c:1-10

# 检查从库的状态,可以看到 IO 线程和 SQL 线程也正常了
$ kubectl -n test exec mysql-server-1 -- mysql -uroot -pspeakin_root -e 'show slave status\G;' | grep -i 'running'
mysql: [Warning] Using a password on the command line interface can be insecure.
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates

# 测试一下扩容
$ kubectl -n test scale sts mysql-server --replicas=3
statefulset.apps/mysql-server scaled

# 查看 Pod 个数
$ kubectl get pod -n test 
NAME             READY   STATUS    RESTARTS   AGE
mysql-server-0   1/1     Running   0          52m
mysql-server-1   1/1     Running   1          49m
mysql-server-2   1/1     Running   0          45s

# 检查新扩容的从库 Pod 状态
$ kubectl -n test exec mysql-server-2 -- mysql -uroot -pspeakin_root -e 'show slave status\G;' | grep -i 'running'
mysql: [Warning] Using a password on the command line interface can be insecure.
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates

YAML

镜像我已经提交到了公开的阿里云仓库,所以下面 YAML 的镜像可以直接使用。

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config-template
  namespace: test
data:
  # MySQL 配置模板,my.cnf 中不可包含中文
  my.cnf: |
    [mysqld]
    max_connections=1000
    innodb_flush_log_at_trx_commit=2
    innodb_buffer_pool_size=1G

    # general_log_file        = /var/log/mysql/mysql.log
    # general_log             = 1
    # log_error = /var/log/mysql/error.log

    server-id={{server_id}}          # slave set 2 or other number
    log_bin                 = /var/log/mysql/mysql-bin.log
    expire_logs_days        = 60
    max_binlog_size   = 100M
    #binlog_do_db           = include_database_name
    #binlog_ignore_db       = include_database_name

    gtid_mode=on
    enforce_gtid_consistency=on
    #log-slave-updates=1
    #binlog_format=row

    #skip_slave_start=1

    #log-slave-updates=true
    #auto_increment_offset=1
    #auto_increment_increment=2

    character-set-server=utf8

    [client]
    default-character-set=utf8

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql-server
  namespace: test
spec:
  selector:
    matchLabels:
      app: mysql-server
  serviceName: mysql-server
  replicas: 2
  template:
    metadata:
      labels:
        app: mysql-server
    spec:
      containers:
      - name: mysql-server
        image: registry.cn-shenzhen.aliyuncs.com/zze/mysql-for-cluster:5.7.26
        imagePullPolicy: IfNotPresent
        livenessProbe:
          tcpSocket:
            port: 3306
          initialDelaySeconds: 15
          periodSeconds: 5
        ports:
        - containerPort: 3306
          name: mysql-server
        env: 
        # MySQL ROOT 用户密码
        - name: MYSQL_ROOT_PASSWORD
          value: "123456"
        # 要创建的用于主从复制的用户
        - name: REPL_USER
          value: "rep"
        # 主从复制用户的密码
        - name: REPL_USER_PWD
          value: "rep123"
        # 暴露这个 StatefulSet 的 Service 名称,因为是根据域名访问主库的,所以需要 Service 名称拼接 FQDN,格式:<pod_name>.<service.name>.<namespace>.svc.cluster.local
        - name: SERVICE_NAME
          value: "mysql-server"
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        # 从库第一次启动时尝试连接主库的次数,超过则认为连接失败,放弃创建主从关系,默认为 100 次
        - name: INIT_TRY_CONNECT_MASTER_COUNT
          value: "200"
        # 从库重启时尝试连接主库的次数,超过则认为连接失败,放弃维护主从关系,默认为 10 次
        - name: POST_TRY_CONNECT_MASTER_COUNT
          value: "5"
        args:
        - "--ignore-db-dir=lost+found"
        volumeMounts:
        - name: test-mysql-data
          mountPath: /var/lib/mysql
        - name: config
          mountPath: /etc/mysql/conf.d
        lifecycle:
          postStart:
            exec:
              command: 
              - /opt/run
              - create_cluster
      initContainers:
      # 初始化容器,作用是根据 ConfigMap 中的 my.cnf 模板动态创建 MySQL 配置文件
      - name: mysql-sidecar-init
        image: registry.cn-shenzhen.aliyuncs.com/zze/mysql-sidecar:1.0
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: mysql-config-template
          mountPath: /template
        - name: config
          mountPath: /config
        command: 
        - /opt/run
        - init_conf
      volumes:
      - name: mysql-config-template
        configMap:
          name: mysql-config-template
      - name: config
        emptyDir: {}
  volumeClaimTemplates:  
  - metadata:
      name: test-mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "ceph-sc-mysql"  
      resources:
        requests:
          storage: 512Mi

---
apiVersion: v1
kind: Service
metadata:
  name: mysql-server
  namespace: test
spec:
  ports:
  - port: 3306
    protocol: TCP
    targetPort: 3306
  selector:
    app: mysql-server
  type: NodePort
0

评论区