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

1226 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

此文档为 bitnami/mysql 配置文件解读,详细 values 等信息见[官方文档](https://artifacthub.io/packages/helm/bitnami/mysql)。
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。
# 简单部署
```bash
helm repo add bitnami https://charts.bitnami.com/bitnami
```
```bash
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 拉取,再上传到所需主机。
>
> ```bash
> 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 配置清单。
```bash
$ 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
```
```bash
helm get manifest my-mysql > my-mysql.yaml
```
# 资源清单解释
资源清单中包含 NetWorkPolicy、PodDisruptionBudget、ServiceAccount、Secret、ConfigMapprimary+secondary、Serviceprimary+secondary cluster+headless、StatefulSetprimary+secondary
## <span id="statefulset">StatefulSet</span>
primary 和 secondary 除在标签命名上的区别,在[环境变量声明]()上还有区别。
>[!TIP]
>
>若不在 values.yaml 中设置,部署时 `persistentVolumeClaimRetentionPolicy` 字段中的两个子字段 `whenDeleted`、`whenScaled` 均会默认被设置成 `Retain`,删除 release 时需要手动删除 PVC 和 PV。
```yaml
# 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](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/prebuildfs/opt/bitnami/scripts/libfs.sh)。
检查 MySQL 日志目录是否为空,如果目录不为空,将整个日志目录(包含符号链接)复制到临时目录,确保日志文件能被挂载到主容器的 `/opt/bitnami/mysql/logs/app-logs-dir` 目录下,便于使用 Kubernetes 的日志收集和管理功能。
### Containers
#### 环境变量
设置环境变量,为之后初始化脚本做准备
**primary**
```yaml
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
```
环境变量解释
```ini
# 调试日志开关:禁用
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](#secret) 中指定的 3 个密码,通过 Volumes 分别挂载至 `/opt/bitnami/mysql/secrets/` 目录下,再由 `MYSQL_ROOT_PASSWOR_FILE``MYSQL_REPLICATION_PASSWORD_FILE` 环境变量指定密码文件位置,通过 shell 脚本实现密码获取。
**secondary**
```yaml
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
```
环境变量解释,上面提到过的略
```ini
# 主库端口,需与主库 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
```
## <span id="service">Service</span>
Primary 和 Secondary 配置文件中仅有名称的区别,故不赘述。
```yaml
# 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
```
## <span id="configmap">ConfigMap</span>
Primary 和 Secondary 配置文件中仅有名称的区别,故不赘述。
```yaml
# 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
```
## <span id="secret">Secret</span>
```yaml
# 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=="
```
```bash
kubectl get secret --namespace default my-mysql -o jsonpath="{.data.mysql-root-password}" | base64 -d
```
该命令获取的密码即为 `mysql-root-password` base64 decode 值。
## <span id="networdpolicy">NetWorkPolicy</span>
```yaml
# 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
```
## <span id="serviceaccount">ServiceAccount</span>
```yaml
# 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
```bash
# 准备工作
yum install -y libaio numactl wget
```
```bash
# 下载并解压
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
```
```bash
# 创建 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
```
```bash
# 初始化数据库
/usr/local/mysql/bin/mysqld \
--initialize \
--user=mysql \
--basedir=/usr/local/mysql \
--datadir=/usr/local/mysql/data
```
```bash
# 配置 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
```
```bash
# 设置环境变量
echo 'export PATH=/usr/local/mysql/bin:$PATH' >> /etc/profile
source /etc/profile
```
```bash
# Systemd 服务配置
mkdir /etc/systemd/system/mysqld.service
```
```ini
[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
```
```bash
# 启动服务
systemctl daemon-reload
systemctl start mysqld
systemctl enable mysqld
```
## Master 配置
修改 `/etc/my.cnf`
```ini
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
binlog-do-db = test_db # 需同步的数据库
```
重启MySQL并授权复制用户
```sql
CREATE USER 'repl'@'%' IDENTIFIED BY 'SecurePass123!';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
```
记录二进制日志位置:
```sql
SHOW MASTER STATUS;
```
## Slave配置
修改 `/etc/my.cnf`
```ini
[mysqld]
server-id = 2 # 必须与主服务器不同
relay-log = relay-log
replicate-do-db = test_db
```
配置主从连接:
```sql
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;
```
验证复制状态:
```sql
SHOW SLAVE STATUS\G
```
# 镜像
bitnami MySQL 镜像可见 [Dockerfile](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/Dockerfile)。MySQL 二进制包安装目录为 `/opt/bitnami`
bitnami 提供的 MySQL 镜像可以直接部署集群无需 kubernetes部署详情可见 [README.md](https://github.com/bitnami/containers/blob/main/bitnami/mysql/README.md)。
bitnami MySQL 集群初始化依赖镜像中的脚本 [scripts](https://github.com/bitnami/containers/tree/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts)、依赖 [lib](https://github.com/bitnami/containers/tree/main/bitnami/mysql/8.4/debian-12/prebuildfs/opt/bitnami/scripts),容器内位置为 `/opt/bitnami/scripts/mysql`
镜像内脚本执行顺序为 [entrypoint.sh](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts/mysql/entrypoint.sh)[setup.sh](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts/mysql/entrypoint.sh)[run.sh](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts/mysql/run.sh) 。
**以下脚本解读顺序为镜像内 MySQL 初始化顺序。**
> [!WARNING]
>
> [setup.sh](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts/mysql/entrypoint.sh) 脚本仅为 MySQL 初始化,不负责启动 MySQLMySQL 集群只能通过 [run.sh](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts/mysql/run.sh) 脚本启动。
## mysql_validate()
[libmysql.sh](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts/libmysql.sh)
检查环境变量,`()` 内为 [Statefulset](#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](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts/libmysql.sh)
MySQL 初始化脚本,相当于上文手动安装流程从 **创建 mysql 用户****配置 mysql** 以及所有 **主从配置操作**
```bash
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
```
相当于:
```bash
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
```
***
```bash
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](#configmap) 挂载至此路径,覆盖容器初始化 `mysql_create_default_config()` 函数根据环境变量创建的 `my.cnf`
```bash
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](#statefulset) 中并没有设置 `defaultMode` ,这里的 `my.cnf` 文件并不会被 `mysql_update_custom_config()` 函数修改成 `my_custom.cnf`
***
`my_custom.cnf()` 部分在 kubernetes 中并不存在,故不做讨论。
***
```bash
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不会启动服务。
```bash
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
```bash
/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
```bash
/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` 命令启动复制。
```bash
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
```bash
/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
```bash
/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 root``mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}"` 连接来判断 MySQL 是否准备好接受数据。
```bash
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
}
```
转换为:
```bash
# 循环尝试连接直到成功或超时
# 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 环境变量选择复制方式,全量数据同步或者仅配置复制连接。
```bash
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。
```bash
mysql_upgrade() {
info "Running mysql_upgrade"
mysql_stop
mysql_start_bg "--upgrade=${DB_UPGRADE}"
}
```
## mysql_custom_scripts()
用于执行用户自定义的初始化脚本,支持 `.sh``.sql``.sql.gz` 三种类型的文件,检查文件目录为:
```bash
/docker-entrypoint-initdb.d/ # 首次初始化时执行的脚本
/docker-entrypoint-startdb.d/ # 每次启动时执行的脚本
```
本文暂不讨论。
## mysql_stop()
最后停止 MySQL至此 MySQL 初始化完成。
## run.sh
[run.sh](https://github.com/bitnami/containers/blob/main/bitnami/mysql/8.4/debian-12/rootfs/opt/bitnami/scripts/mysql/run.sh)
此脚本是 MySQL 容器的主入口点,负责正确和安全地启动 MySQL 服务器。
使用 `mysqld` 启动 MySQL 不用 `mysqld_safe`,因为 `mysqld` 允许日志输出到 stdout/stderr便于容器环境中的日志收集。