Files
Cloud-book/Kubernetes/resources/mysql集群/bitnami-mysql记录.md
2025-08-27 17:10:05 +08:00

37 KiB
Raw Blame History

此文档为 bitnami/mysql 配置文件解读,详细 values 等信息见官方文档

bitnami MySQL 集群中的容器全部均采用 https://hub.docker.com/r/bitnami/mysql容器中包含初始化脚本后文会提及

镜像源码(包含所有脚本)可见 https://github.com/bitnami/containers/tree/main/bitnami/mysql/

chart 源码可见 https://github.com/bitnami/charts/tree/main/bitnami/mysql

简单部署

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-mysql ./mysql  \
	--set global.strorageClass=$YOUR_STORAGECLASS  \
	--set architecture=replication  \
	--set secondary.replicaCount=$REPILICA_NUM
  • global.strorageClass:设置存储类,如果不设置默认采用 default StorageClass如果没有默认 SC 创建的 PVC 将处于 Pending 状态。
  • architecture:设置 MySQL 架构,有 standalone 单主机和 replication 集群两个选项,默认为单主机。
  • secondary.replicaCount:设置集群从节点个数,即 Slave 个数。

也可以通过 values.yaml 进行高级配置。

如遇网络问题可通过 pull 拉取,再上传到所需主机。

helm pull oci://registry-1.docker.io/bitnamicharts/mysql --version 12.3.4

tar -zxvf mysql mysql-12.3.4.tgz && cd mysql/

helm install my-mysql mysql/

获取资源清单

由上文设置的 values 获取 helm chart 配置清单。

$ helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
my-mysql        default         1               2025-04-25 19:51:56.787669028 +0800 CST deployed        mysql-12.3.4    8.4.5
helm get manifest my-mysql > my-mysql.yaml

资源清单解释

资源清单中包含 NetWorkPolicy、PodDisruptionBudget、ServiceAccount、Secret、ConfigMapprimary+secondary、Serviceprimary+secondary cluster+headless、StatefulSetprimary+secondary

StatefulSet

primary 和 secondary 除在标签命名上的区别,在环境变量声明上还有区别。

Tip

若不在 values.yaml 中设置,部署时 persistentVolumeClaimRetentionPolicy 字段中的两个子字段 whenDeletedwhenScaled 均会默认被设置成 Retain,删除 release 时需要手动删除 PVC 和 PV。

# Source: mysql/templates/primary/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-mysql-primary
  namespace: "default"
  labels:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.4.5
    helm.sh/chart: mysql-12.3.4
    app.kubernetes.io/part-of: mysql
    app.kubernetes.io/component: primary
spec:
  replicas: 1
  # pod 创建、删除和扩展顺序,默认 OrderedReady
  podManagementPolicy: ""
  selector:
    matchLabels:
      app.kubernetes.io/instance: my-mysql
      app.kubernetes.io/name: mysql
      app.kubernetes.io/part-of: mysql
      app.kubernetes.io/component: primary
  serviceName: my-mysql-primary-headless
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      annotations:
        checksum/configuration: a581348f7af561e486fad8d76b185ef64f865137e8229ff5d0fa6cdf95694ea1
      labels:
        app.kubernetes.io/instance: my-mysql
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.4.5
        helm.sh/chart: mysql-12.3.4
        app.kubernetes.io/part-of: mysql
        app.kubernetes.io/component: primary
    spec:
      serviceAccountName: my-mysql
      
      automountServiceAccountToken: false
      affinity:
        podAffinity:
          
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app.kubernetes.io/instance: my-mysql
                    app.kubernetes.io/name: mysql
                topologyKey: kubernetes.io/hostname
              weight: 1
        nodeAffinity:
          
      securityContext:
        fsGroup: 1001
        fsGroupChangePolicy: Always
        supplementalGroups: []
        sysctls: []
      initContainers:
        - name: preserve-logs-symlinks
          image: docker.io/bitnami/mysql:8.4.5-debian-12-r0
          imagePullPolicy: "IfNotPresent"
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
              - ALL
            readOnlyRootFilesystem: true
            runAsGroup: 1001
            runAsNonRoot: true
            runAsUser: 1001
            seLinuxOptions: {}
            seccompProfile:
              type: RuntimeDefault
          resources:
            limits:
              cpu: 750m
              ephemeral-storage: 2Gi
              memory: 768Mi
            requests:
              cpu: 500m
              ephemeral-storage: 50Mi
              memory: 512Mi
          command:
            - /bin/bash
          args:
            - -ec
            - |
              #!/bin/bash

              . /opt/bitnami/scripts/libfs.sh
              # We copy the logs folder because it has symlinks to stdout and stderr
              if ! is_dir_empty /opt/bitnami/mysql/logs; then
                cp -r /opt/bitnami/mysql/logs /emptydir/app-logs-dir
              fi
          volumeMounts:
            - name: empty-dir
              mountPath: /emptydir
      containers:
        - name: mysql
          image: docker.io/bitnami/mysql:8.4.5-debian-12-r0
          imagePullPolicy: "IfNotPresent"
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
              - ALL
            readOnlyRootFilesystem: true
            runAsGroup: 1001
            runAsNonRoot: true
            runAsUser: 1001
            seLinuxOptions: {}
            seccompProfile:
              type: RuntimeDefault
          env:
            - name: BITNAMI_DEBUG
              value: "false"
            - name: MYSQL_ROOT_PASSWORD_FILE
              value: /opt/bitnami/mysql/secrets/mysql-root-password
            - name: MYSQL_ENABLE_SSL
              value: "no"
            - name: MYSQL_PORT
              value: "3306"
            - name: MYSQL_DATABASE
              value: "my_database"
            - name: MYSQL_REPLICATION_MODE
              value: "master"
            - name: MYSQL_REPLICATION_USER
              value: "replicator"
            - name: MYSQL_REPLICATION_PASSWORD_FILE
              value: /opt/bitnami/mysql/secrets/mysql-replication-password
          envFrom:
          ports:
            - name: mysql
              containerPort: 3306
          livenessProbe:
            failureThreshold: 3
            initialDelaySeconds: 5
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
            exec:
              command:
                - /bin/bash
                - -ec
                - |
                  password_aux="${MYSQL_ROOT_PASSWORD:-}"
                  if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                      password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                  fi
                  mysqladmin status -uroot -p"${password_aux}"
          readinessProbe:
            failureThreshold: 3
            initialDelaySeconds: 5
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
            exec:
              command:
                - /bin/bash
                - -ec
                - |
                  password_aux="${MYSQL_ROOT_PASSWORD:-}"
                  if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                      password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                  fi
                  mysqladmin ping -uroot -p"${password_aux}" | grep "mysqld is alive"
          startupProbe:
            failureThreshold: 10
            initialDelaySeconds: 15
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
            exec:
              command:
                - /bin/bash
                - -ec
                - |
                  password_aux="${MYSQL_ROOT_PASSWORD:-}"
                  if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                      password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                  fi
                  mysqladmin ping -uroot -p"${password_aux}" | grep "mysqld is alive"
          resources:
            limits:
              cpu: 750m
              ephemeral-storage: 2Gi
              memory: 768Mi
            requests:
              cpu: 500m
              ephemeral-storage: 50Mi
              memory: 512Mi
          volumeMounts:
            - name: data
              mountPath: /bitnami/mysql
            - name: empty-dir
              mountPath: /tmp
              subPath: tmp-dir
            - name: empty-dir
              mountPath: /opt/bitnami/mysql/conf
              subPath: app-conf-dir
            - name: empty-dir
              mountPath: /opt/bitnami/mysql/tmp
              subPath: app-tmp-dir
            - name: empty-dir
              mountPath: /opt/bitnami/mysql/logs
              subPath: app-logs-dir
            - name: config
              mountPath: /opt/bitnami/mysql/conf/my.cnf
              subPath: my.cnf
            - name: mysql-credentials
              mountPath: /opt/bitnami/mysql/secrets/
      volumes:
        - name: config
          configMap:
            name: my-mysql-primary
        - name: mysql-credentials
          secret:
            secretName: my-mysql
            items:
              - key: mysql-root-password
                path: mysql-root-password
              - key: mysql-password
                path: mysql-password
              - key: mysql-replication-password
                path: mysql-replication-password
        - name: empty-dir
          emptyDir: {}
  volumeClaimTemplates:
    - metadata:
        name: data
        labels:
          app.kubernetes.io/instance: my-mysql
          app.kubernetes.io/name: mysql
          app.kubernetes.io/component: primary
      spec:
        accessModes:
          - "ReadWriteOnce"
        resources:
          requests:
            storage: "8Gi"

initContainers

通过 /opt/bitnami/scripts/libfs.sh 标准化文件系统管理,内容可见 libfs.sh

检查 MySQL 日志目录是否为空,如果目录不为空,将整个日志目录(包含符号链接)复制到临时目录,确保日志文件能被挂载到主容器的 /opt/bitnami/mysql/logs/app-logs-dir 目录下,便于使用 Kubernetes 的日志收集和管理功能。

Containers

环境变量

设置环境变量,为之后初始化脚本做准备

primary

env:
  - name: BITNAMI_DEBUG
    value: "false"
  - name: MYSQL_ENABLE_SSL
    value: "no"
  - name: MYSQL_PORT
    value: "3306"
  - name: MYSQL_DATABASE
    value: "my_database"
  - name: MYSQL_REPLICATION_MODE
    value: "master"
  - name: MYSQL_REPLICATION_USER
    value: "replicator"
  - name: MYSQL_ROOT_PASSWORD_FILE
    value: /opt/bitnami/mysql/secrets/mysql-root-password
  - name: MYSQL_REPLICATION_PASSWORD_FILE
    value: /opt/bitnami/mysql/secrets/mysql-replication-password

环境变量解释

 # 调试日志开关:禁用
 BITNAMI_DEBUG=false
 # 控制SSL加密连接禁用
 MYSQL_ENABLE_SSL=no
 # MySQL监听端口
 MYSQL_PORT=3306
 # 初始化时创建的默认数据库
 MYSQL_DATABASE=my_database
 # 定义节点角色:主库
 MYSQL_REPLICATION_MODE=master
 # 复制专用用户名,主库自动创建此用户并授权 REPLICATION SLAVE 权限
 MYSQL_REPLICATION_USER=replicator
 # 指定root密码路径
 MYSQL_ROOT_PASSWOR_FILE=/opt/bitnami/mysql/secrets/mysql-root-password
 # 复制用户密码文件路径
 MYSQL_REPLICATION_PASSWORD_FILE=/opt/bitnami/mysql/secrets/mysql-replication-password

Secret 中指定的 3 个密码,通过 Volumes 分别挂载至 /opt/bitnami/mysql/secrets/ 目录下,再由 MYSQL_ROOT_PASSWOR_FILEMYSQL_REPLICATION_PASSWORD_FILE 环境变量指定密码文件位置,通过 shell 脚本实现密码获取。

secondary

env:
  - name: BITNAMI_DEBUG
    value: "false"
  - name: MYSQL_ENABLE_SSL
    value: "no"
  - name: MYSQL_PORT
    value: "3306"
  - name: MYSQL_MASTER_PORT_NUMBER
    value: "3306"
  - name: MYSQL_REPLICATION_MODE
    value: "slave"
  - name: MYSQL_MASTER_HOST
    value: my-mysql-primary 
  - name: MYSQL_MASTER_ROOT_USER
    value: "root"
  - name: MYSQL_REPLICATION_USER
    value: "replicator"
  - name: MYSQL_MASTER_ROOT_PASSWORD_FILE
    value: /opt/bitnami/mysql/secrets/mysql-root-password
  - name: MYSQL_REPLICATION_PASSWORD_FILE
    value: /opt/bitnami/mysql/secrets/mysql-replication-password

环境变量解释,上面提到过的略

# 主库端口,需与主库 MYSQL_PORT 一致
MYSQL_MASTER_PORT_NUMBER=3306
# 定义节点角色:从库,自动配置 server-id 和复制参数
MYSQL_REPLICATION_MODE=slave
# 主库主机名/IP从库通过此值连接主库
MYSQL_MASTER_HOST=my-mysql-primary 
# 主库root用户用于从库初始化时连接主库需与主库root用户一致
MYSQL_MASTER_ROOT_USER=root
# 复制专用用户名主库需提前创建此用户并授权REPLICATION SLAVE权限
MYSQL_REPLICATION_USER=replicator

Service

Primary 和 Secondary 配置文件中仅有名称的区别,故不赘述。

# Source: mysql/templates/primary/svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-mysql-primary-headless
  namespace: "default"
  labels:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.4.5
    helm.sh/chart: mysql-12.3.4
    app.kubernetes.io/part-of: mysql
    app.kubernetes.io/component: primary
spec:
  type: ClusterIP
  clusterIP: None
  publishNotReadyAddresses: true
  ports:
    - name: mysql
      port: 3306
      targetPort: mysql
  selector:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/name: mysql
    app.kubernetes.io/component: primary
---

# Source: mysql/templates/primary/svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-mysql-primary
  namespace: "default"
  labels:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.4.5
    helm.sh/chart: mysql-12.3.4
    app.kubernetes.io/part-of: mysql
    app.kubernetes.io/component: primary
spec:
  type: ClusterIP
  sessionAffinity: None  
  ports:
    - name: mysql
      port: 3306
      protocol: TCP
      targetPort: mysql
      nodePort: null
  selector:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/name: mysql
    app.kubernetes.io/part-of: mysql
    app.kubernetes.io/component: primary

ConfigMap

Primary 和 Secondary 配置文件中仅有名称的区别,故不赘述。

# Source: mysql/templates/primary/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-mysql-primary
  namespace: "default"
  labels:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.4.5
    helm.sh/chart: mysql-12.3.4
    app.kubernetes.io/part-of: mysql
    app.kubernetes.io/component: primary
data:
  my.cnf: |-
    [mysqld]
    # 控制多因素认证策略,* ,, 表示允许所有认证方式(如密码、插件等)
    authentication_policy='* ,,'
    # 禁用DNS反向解析仅通过IP授权访问
    skip-name-resolve
    # 禁用TIMESTAMP列的自动更新特性需显式指定默认值
    explicit_defaults_for_timestamp
    # MySQL安装目录
    basedir=/opt/bitnami/mysql
    # 插件存放路径
    plugin_dir=/opt/bitnami/mysql/lib/plugin
    # 监听端口
    port=3306
    # 禁用 MySQL X Protocol默认 33060 端口)
    mysqlx=0
    mysqlx_port=33060
    socket=/opt/bitnami/mysql/tmp/mysql.sock
    # 数据文件存放目录
    datadir=/bitnami/mysql/data
    # 临时文件目录
    tmpdir=/opt/bitnami/mysql/tmp
    # 单此传输最大数据包大小
    max_allowed_packet=16M
    # 监听所有IP地址
    bind-address=*
    
    pid-file=/opt/bitnami/mysql/tmp/mysqld.pid
    # 错误日志路径
    log-error=/opt/bitnami/mysql/logs/mysqld.log
    # 默认字符集
    character-set-server=UTF8
    # 关闭慢查询日志
    slow_query_log=0
    # 若启用慢查询记录超过10秒的查询
    long_query_time=10.0
    
    [client]
    port=3306
    # 本地通信的Unix套接字路径
    socket=/opt/bitnami/mysql/tmp/mysql.sock
    default-character-set=UTF8
    plugin_dir=/opt/bitnami/mysql/lib/plugin
    
    [manager]
    port=3306
    socket=/opt/bitnami/mysql/tmp/mysql.sock
    pid-file=/opt/bitnami/mysql/tmp/mysqld.pid

Secret

# Source: mysql/templates/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-mysql
  namespace: "default"
  labels:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.4.5
    helm.sh/chart: mysql-12.3.4
    app.kubernetes.io/part-of: mysql
type: Opaque
data:
  mysql-root-password: "N0ZveE5ZdXRobg=="
  mysql-password: "TVlvWXZYOFZiZQ=="
  mysql-replication-password: "NzNkc0NtbmtJMQ=="
kubectl get secret --namespace default my-mysql -o jsonpath="{.data.mysql-root-password}" | base64 -d

该命令获取的密码即为 mysql-root-password base64 decode 值。

NetWorkPolicy

# Source: mysql/templates/networkpolicy.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: my-mysql
  namespace: "default"
  labels:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.4.5
    helm.sh/chart: mysql-12.3.4
    app.kubernetes.io/part-of: mysql
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/instance: my-mysql
      app.kubernetes.io/managed-by: Helm
      app.kubernetes.io/name: mysql
      app.kubernetes.io/version: 8.4.5
      helm.sh/chart: mysql-12.3.4
  policyTypes:
    - Ingress
    - Egress
  egress:
    - {}
  ingress:
    # Allow connection from other cluster pods
    - ports:
        - port: 3306

ServiceAccount

# Source: mysql/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-mysql
  namespace: "default"
  labels:
    app.kubernetes.io/instance: my-mysql
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mysql
    app.kubernetes.io/version: 8.4.5
    helm.sh/chart: mysql-12.3.4
    app.kubernetes.io/part-of: mysql
automountServiceAccountToken: false
secrets:
  - name: my-mysql

手动安装示例

常规手动二进制安装 mysql下文脚本执行的顺序可以参照手动部署 mysql 的顺序。

安装 MySQL

# 准备工作
yum install -y libaio numactl wget
# 下载并解压
wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.28-linux-glibc2.12-x86_64.tar.xz 
tar -xvf mysql-8.0.28-linux-glibc2.12-x86_64.tar.xz
mv mysql-8.0.28-linux-glibc2.12-x86_64 /usr/local/mysql
# 创建 mysql 用户
groupadd mysql
useradd -r -g mysql -s /bin/false mysql
mkdir -p /usr/local/mysql/data /var/log/mysql
chown -R mysql:mysql /usr/local/mysql /var/log/mysql
# 初始化数据库
/usr/local/mysql/bin/mysqld \
	--initialize \
	--user=mysql \
	--basedir=/usr/local/mysql \
	--datadir=/usr/local/mysql/data
# 配置 mysql
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
socket=/var/lib/mysql/mysql.sock
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
# 设置环境变量
echo 'export PATH=/usr/local/mysql/bin:$PATH' >> /etc/profile
source /etc/profile
# Systemd 服务配置
mkdir /etc/systemd/system/mysqld.service
[Unit]
Description=MySQL Server
After=network.target

[Service]
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf
LimitNOFILE=5000

[Install]
WantedBy=multi-user.target
# 启动服务
systemctl daemon-reload
systemctl start mysqld
systemctl enable mysqld

Master 配置

修改 /etc/my.cnf

[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
binlog-do-db = test_db  # 需同步的数据库

重启MySQL并授权复制用户

CREATE USER 'repl'@'%' IDENTIFIED BY 'SecurePass123!';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

记录二进制日志位置:

SHOW MASTER STATUS;

Slave配置

修改 /etc/my.cnf

[mysqld]
server-id = 2  # 必须与主服务器不同
relay-log = relay-log
replicate-do-db = test_db

配置主从连接:

CHANGE MASTER TO
MASTER_HOST='192.168.1.100',  # 主服务器IP
MASTER_USER='repl',
MASTER_PASSWORD='SecurePass123!',
MASTER_LOG_FILE='mysql-bin.000003',
MASTER_LOG_POS=785;
START SLAVE;

验证复制状态:

SHOW SLAVE STATUS\G

镜像

bitnami MySQL 镜像可见 Dockerfile。MySQL 二进制包安装目录为 /opt/bitnami

bitnami 提供的 MySQL 镜像可以直接部署集群无需 kubernetes部署详情可见 README.md

bitnami MySQL 集群初始化依赖镜像中的脚本 scripts、依赖 lib,容器内位置为 /opt/bitnami/scripts/mysql

镜像内脚本执行顺序为 entrypoint.shsetup.shrun.sh

以下脚本解读顺序为镜像内 MySQL 初始化顺序。

Warning

setup.sh 脚本仅为 MySQL 初始化,不负责启动 MySQLMySQL 集群只能通过 run.sh 脚本启动。

mysql_validate()

libmysql.sh

检查环境变量,() 内为 Statefulset 中的环境变量名:

  • DB_REPLICATION_MODE="master" 检查 DB_REPLICATION_USERMYSQL_REPLICATION_USER、DB_ROOT_PASSWORDMYSQL_ROOT_PASSWORD_FILE
  • DB_REPLICATION_MODE="slave" 检查 DB_MASTER_HOSTMYSQL_MASTER_HOST
  • DB_ROOT_PASSWORD、DB_PASSWORD、DB_REPLICATION_PASSWORD 检查

mysql_initialize()

libmysql.sh

MySQL 初始化脚本,相当于上文手动安装流程从 创建 mysql 用户配置 mysql 以及所有 主从配置操作

debug "Ensuring expected directories/files exist"
for dir in "$DB_DATA_DIR" "$DB_TMP_DIR" "$DB_LOGS_DIR"; do
    ensure_dir_exists "$dir"
    am_i_root && chown "$DB_DAEMON_USER":"$DB_DAEMON_GROUP" "$dir"
done

相当于:

mkdir -p /bitnami/mysql/data /opt/bitnami/mysql/tmp /opt/bitnami/mysql/logs

chown mysql:mysql /bitnami/mysql/data
chown mysql:mysql /opt/bitnami/mysql/tmp
chown mysql:mysql /opt/bitnami/mysql/logs

if is_file_writable "$DB_CONF_FILE"; then
    info "Updating 'my.cnf' with custom configuration"
    mysql_update_custom_config
else
    warn "The ${DB_FLAVOR} configuration file '${DB_CONF_FILE}' is not writable. Configurations based on environment variables will not be applied for this file."
fi

检查 /opt/bitnami/mysql/conf/my.cnf 是否存在并可写,这里如果是用之前的 helm 部署 my.cnf 会随 configmap 挂载至此路径,覆盖容器初始化 mysql_create_default_config() 函数根据环境变量创建的 my.cnf

mysql 04:48:45.58 WARN  ==> The mysql configuration file '/opt/bitnami/mysql/conf/my.cnf' is not writable. Configurations based on environment variables will not be applied for this file.

由于 ConfigMap 默认以只读形式挂载,statefulset 中并没有设置 defaultMode ,这里的 my.cnf 文件并不会被 mysql_update_custom_config() 函数修改成 my_custom.cnf


my_custom.cnf() 部分在 kubernetes 中并不存在,故不做讨论。


if [[ -e "$DB_DATA_DIR/mysql" ]]; then
    info "Using persisted data"
    # mysql_upgrade requires the server to be running
    [[ -n "$(get_master_env_var_value ROOT_PASSWORD)" ]] && export ROOT_AUTH_ENABLED="yes"
    # https://dev.mysql.com/doc/refman/8.0/en/replication-upgrade.html
    mysql_upgrade
else
    debug "Cleaning data directory to ensure successfully initialization"
    rm -rf "${DB_DATA_DIR:?}"/*
    info "Installing database"
    mysql_install_db
    mysql_start_bg
    wait_for_mysql_access
    # we delete existing users and create new ones with stricter access
    # commands can still be executed until we restart or run 'flush privileges'
    info "Configuring authentication"
    mysql_execute "mysql" <<EOF
DELETE FROM mysql.user WHERE user not in ('mysql.sys','mysql.infoschema','mysql.session','mariadb.sys');
EOF
    # slaves do not need to configure users
    if [[ -z "$DB_REPLICATION_MODE" ]] || [[ "$DB_REPLICATION_MODE" = "master" ]]; then
        if [[ "$DB_REPLICATION_MODE" = "master" ]]; then
            debug "Starting replication"
            if [[ "$(mysql_get_version)" =~ ^8\.0\. ]]; then
                echo "RESET MASTER;" | debug_execute "$DB_BIN_DIR/mysql" --defaults-file="$DB_CONF_FILE" -N -u root
            else
                echo "RESET BINARY LOGS AND GTIDS;" | debug_execute "$DB_BIN_DIR/mysql" --defaults-file="$DB_CONF_FILE" -N -u root
            fi
        fi
        mysql_ensure_root_user_exists "$DB_ROOT_USER" "$DB_ROOT_PASSWORD" "$DB_AUTHENTICATION_PLUGIN"
        mysql_ensure_user_not_exists "" # ensure unknown user does not exist
        if [[ -n "$DB_USER" ]]; then
            local -a args=("$DB_USER")
            [[ -n "$DB_PASSWORD" ]] && args+=("-p" "$DB_PASSWORD")
            [[ -n "$DB_AUTHENTICATION_PLUGIN" ]] && args+=("--auth-plugin" "$DB_AUTHENTICATION_PLUGIN")
            mysql_ensure_optional_user_exists "${args[@]}"
        fi
        if [[ -n "$DB_DATABASE" ]]; then
            local -a createdb_args=("$DB_DATABASE")
            [[ -n "$DB_USER" ]] && createdb_args+=("-u" "$DB_USER")
            [[ -n "$DB_CHARACTER_SET" ]] && createdb_args+=("--character-set" "$DB_CHARACTER_SET")
            [[ -n "$DB_COLLATE" ]] && createdb_args+=("--collate" "$DB_COLLATE")
            mysql_ensure_optional_database_exists "${createdb_args[@]}"
        fi
        [[ -n "$DB_ROOT_PASSWORD" ]] && export ROOT_AUTH_ENABLED="yes"
    fi
    [[ -n "$DB_REPLICATION_MODE" ]] && mysql_configure_replication
    # we run mysql_upgrade in order to recreate necessary database users and flush privileges
    mysql_upgrade
fi
}

检查 /bitnami/mysql/data 中是否有文件,如果有则说明 mysql 在容器中已被初始化,执行 mysql_upgrade() 此函数本文暂时不讨论;如果没有,则删除改目录下所有文件,执行 mysql_install_db()mysql_start_bg()wait_for_mysql_access()

这里只讨论 else 中的情况。

mysql_install_db()

通过 mysql_extra_flags() 获取额外参数,首次初始化 mysql不会启动服务。

mysql_install_db() {
    local command="${DB_BIN_DIR}/mysql_install_db"
    local -a args=("--defaults-file=${DB_CONF_FILE}" "--basedir=${DB_BASE_DIR}" "--datadir=${DB_DATA_DIR}")

    # Add flags specified via the 'DB_EXTRA_FLAGS' environment variable
    read -r -a db_extra_flags <<< "$(mysql_extra_flags)"
    [[ "${#db_extra_flags[@]}" -gt 0 ]] && args+=("${db_extra_flags[@]}")

    am_i_root && args=("${args[@]}" "--user=$DB_DAEMON_USER")
    command="${DB_BIN_DIR}/mysqld"
    args+=("--initialize-insecure")

    debug_execute "$command" "${args[@]}"
}

在命令行相当于:

master

/opt/bitnami/mysql/sbin/mysqld \
    --defaults-file=/opt/bitnami/mysql/conf/my.cnf \
    --basedir=/opt/bitnami/mysql \
    --datadir=/bitnami/mysql/data \
    --server-id=123 \              		# 随机生成的3位数服务器ID
    --log-bin=mysql-bin \          		# 启用二进制日志
    --sync-binlog=1 \             		# 每次事务后同步二进制日志
    --innodb_flush_log_at_trx_commit=1  # 每次事务提交时刷新日志

slave

/opt/bitnami/mysql/sbin/mysqld \
    --defaults-file=/opt/bitnami/mysql/conf/my.cnf \
    --basedir=/opt/bitnami/mysql \
    --datadir=/bitnami/mysql/data \
    --server-id=456 \              	# 随机生成的3位数服务器ID
    --log-bin=mysql-bin \          	# 启用二进制日志
    --sync-binlog=1 \             	# 每次事务后同步二进制日志
    --relay-log=mysql-relay-bin \  	# 中继日志文件名
    --log-replica-updates=1 \      	# 从库更新写入二进制日志
    --read-only=1                  	# 只读模式

mysql_start_bg()

在后台启动 MySQL 服务,如果检测到 MySQL 正在运行,直接退出。通过 wait_for_mysql() 查询是否有 MySQL PID等待 MySQL 启动。

Warning

此时 MySQL 还在初始化阶段,不允许外部访问,使用 --skip-replica-start 避免在初始化阶段未配置好复制用户凭据时就开始复制,等待所有初始化完成(包括用户创建、权限设置等)后会在在 run.sh 中执行 START REPLICA 命令启动复制。

mysql_start_bg() {
    local -a flags=("--defaults-file=${DB_CONF_FILE}" "--basedir=${DB_BASE_DIR}" "--datadir=${DB_DATA_DIR}" "--socket=${DB_SOCKET_FILE}")

    # Only allow local connections until MySQL is fully initialized, to avoid apps trying to connect to MySQL before it is fully initialized
    flags+=("--bind-address=127.0.0.1")

    # Add flags specified via the 'DB_EXTRA_FLAGS' environment variable
    read -r -a db_extra_flags <<<"$(mysql_extra_flags)"
    [[ "${#db_extra_flags[@]}" -gt 0 ]] && flags+=("${db_extra_flags[@]}")

    # Do not start as root, to avoid permission issues
    am_i_root && flags+=("--user=${DB_DAEMON_USER}")

    # The replica should only start in 'run.sh', elseways user credentials would be needed for any connection
    flags+=("--skip-replica-start")
    flags+=("$@")

    is_mysql_running && return

    info "Starting $DB_FLAVOR in background"
    debug_execute "${DB_SBIN_DIR}/mysqld" "${flags[@]}" &

    # we cannot use wait_for_mysql_access here as mysql_upgrade for MySQL >=8 depends on this command
    # users are not configured on slave nodes during initialization due to --skip-slave-start
    wait_for_mysql

    # Special configuration flag for system with slow disks that could take more time
    # in initializing
    if [[ -n "${DB_INIT_SLEEP_TIME}" ]]; then
        debug "Sleeping ${DB_INIT_SLEEP_TIME} seconds before continuing with initialization"
        sleep "${DB_INIT_SLEEP_TIME}"
    fi
}

在命令行相当于:

Master

/opt/bitnami/mysql/sbin/mysqld \
    --defaults-file=/opt/bitnami/mysql/conf/my.cnf \
    --basedir=/opt/bitnami/mysql \
    --datadir=/bitnami/mysql/data \
    --socket=/opt/bitnami/mysql/tmp/mysql.sock \
    --bind-address=127.0.0.1 \
    --server-id=123 \           # 随机生成的3位数
    --log-bin=mysql-bin \       # 开启二进制日志
    --sync-binlog=1 \          	# 同步写入二进制日志
    --innodb_flush_log_at_trx_commit=1 \  # Master节点特有配置
    --user=mysql \             	# 如果以root运行则切换用户
    --skip-replica-start \     	# 禁止复制自动启动
    &							# 将进程放入后台,不需要用 systemctl 启动

Slave

/opt/bitnami/mysql/sbin/mysqld \
    --defaults-file=/opt/bitnami/mysql/conf/my.cnf \
    --basedir=/opt/bitnami/mysql \
    --datadir=/bitnami/mysql/data \
    --socket=/opt/bitnami/mysql/tmp/mysql.sock \
    --bind-address=127.0.0.1 \
    --server-id=456 \          # 随机生成的3位数
    --log-bin=mysql-bin \      # 开启二进制日志
    --sync-binlog=1 \          # 同步写入二进制日志
    --relay-log=mysql-relay-bin \  # Slave节点特有配置
    --log-replica-updates=1 \      # Slave节点特有配置
    --read-only=1 \               # Slave节点特有配置
    --user=mysql \              # 如果以root运行则切换用户
    --skip-replica-start \      # 禁止复制自动启动
    &							# 将进程放入后台

wait_for_mysql_access()

根据 ROOT_AUTH_ENABLED 情况使用 mysql -u rootmysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}" 连接来判断 MySQL 是否准备好接受数据。

wait_for_mysql_access() {
    # wait until the server is up and answering queries.
    local -r user="${1:-root}"
    local -a args=("mysql" "$user")
    is_boolean_yes "${ROOT_AUTH_ENABLED:-false}" && args+=("$(get_master_env_var_value ROOT_PASSWORD)")
    local -r retries=300
    local -r sleep_time=2
    is_mysql_accessible() {
        echo "select 1" | mysql_execute "${args[@]}"
    }
    if ! retry_while is_mysql_accessible "$retries" "$sleep_time"; then
        error "Timed out waiting for MySQL to be accessible"
        return 1
    fi
}

转换为:

# 循环尝试连接直到成功或超时
# ROOT_AUTH_ENABLED=false
for i in {1..300}; do
    echo "select 1" | mysql mysql -u root && break
    sleep 2
    [ $i -eq 300 ] && echo "Timed out waiting for MySQL" && exit 1
done

# ROOT_AUTH_ENABLED=true
for i in {1..300}; do
    echo "select 1" | mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}" && break
    sleep 2
    [ $i -eq 300 ] && echo "Timed out waiting for MySQL" && exit 1
done

wait_for_mysql()wait_for_mysql_access() 区别在于前者只检查 PID后者通过 mysql 命令连接。

mysql_configure_replication()

主从配置实现master 创建复制用户并赋予复制权限slave 等待主库根据 DB_REPLICATION_SLAVE_DUMP 环境变量选择复制方式,全量数据同步或者仅配置复制连接。

mysql_configure_replication() {
    if [[ "$DB_REPLICATION_MODE" = "slave" ]]; then
        info "Configuring replication in slave node"
        debug "Checking if replication master is ready to accept connection"
        while ! echo "select 1" | mysql_remote_execute "$DB_MASTER_HOST" "$DB_MASTER_PORT_NUMBER" "mysql" "$DB_MASTER_ROOT_USER" "$DB_MASTER_ROOT_PASSWORD"; do
            sleep 1
        done

        if [[ "$DB_REPLICATION_SLAVE_DUMP" = "true" ]]; then
            mysql_exec_initial_dump
        else
            debug "Replication master ready!"
            debug "Setting the master configuration"
            mysql_execute "mysql" <<EOF
CHANGE REPLICATION SOURCE TO SOURCE_HOST='$DB_MASTER_HOST',
SOURCE_PORT=$DB_MASTER_PORT_NUMBER,
SOURCE_USER='$DB_REPLICATION_USER',
SOURCE_PASSWORD='$DB_REPLICATION_PASSWORD',
SOURCE_DELAY=$DB_MASTER_DELAY,
SOURCE_CONNECT_RETRY=10,
GET_SOURCE_PUBLIC_KEY=1;
EOF

        fi
    elif [[ "$DB_REPLICATION_MODE" = "master" ]]; then
        info "Configuring replication in master node"
        if [[ -n "$DB_REPLICATION_USER" ]]; then
            mysql_ensure_replication_user_exists "$DB_REPLICATION_USER" "$DB_REPLICATION_PASSWORD"
        fi
    fi
}

mysql_upgrade()

升级 MySQL 架构。调用 mysql_stop() 确保 MySQL 完全停止,等待数据文件解锁,再通过 DB_UPGRADE 作为启动参数启动 MySQL。

mysql_upgrade() {
    info "Running mysql_upgrade"
    mysql_stop
    mysql_start_bg "--upgrade=${DB_UPGRADE}"
}

mysql_custom_scripts()

用于执行用户自定义的初始化脚本,支持 .sh.sql.sql.gz 三种类型的文件,检查文件目录为:

/docker-entrypoint-initdb.d/   # 首次初始化时执行的脚本
/docker-entrypoint-startdb.d/  # 每次启动时执行的脚本

本文暂不讨论。

mysql_stop()

最后停止 MySQL至此 MySQL 初始化完成。

run.sh

run.sh

此脚本是 MySQL 容器的主入口点,负责正确和安全地启动 MySQL 服务器。

使用 mysqld 启动 MySQL 不用 mysqld_safe,因为 mysqld 允许日志输出到 stdout/stderr便于容器环境中的日志收集。