← 返回首页 K8S Web / 文档 / MySQL 部署(Helm · 1主1备)

🗄️ K8S MySQL 部署(Helm · 1主1备) M1 动手前摸底:查集群里现有 MySQL 的版本  |  M2 1主1备方案选型(Bitnami 复制架构)  |  M3 Helm 一键安装(values 逐行讲)  |  M4 主从复制验证  |  M5 主写从读连接  |  M6 升级/扩容/备份/卸载  |  M7 报错速查

M1⭐ 摸底:查看集群里 MySQL 的版本 —— 动手前先跑这组命令

装新库/对齐版本前,先看看线上现在跑的是什么。按下面顺序敲,前两条不需要密码,最快:

# 0. 先确认 kubectl 连的是目标集群(别在 docker-desktop 上白查)
kubectl config current-context
kubectl config get-contexts

# 1. 全命名空间找 MySQL 的 Pod
kubectl get pods -A | grep -i mysql

# 2. ★ 最快:直接看容器镜像 tag(不用密码、不用进容器)
kubectl get pods -A -o custom-columns='NS:.metadata.namespace,POD:.metadata.name,IMAGE:.spec.containers[*].image' | grep -i mysql
# NS       POD                                IMAGE
# fke-db   fke-mysql-hmsserver-prod-0         docker.ergesanbaishou.com/kube-publicmysql/community-operator:8.4.7-2.1.9,docker.ergesanbaishou.com/kube-public/mysql/community-server:8.4.7
# fke-db   fke-mysql-hmsserver-prod-1         ...(同上,每套库 3 个实例 -0/-1/-2)
# fke-db   fke-mysql-hmsserver-prod-router-…  docker.ergesanbaishou.com/kube-public/mysql/community-router:8.4.7
# fke-db   mysql-operator-5b99fcbbc7-qxzjb    docker.ergesanbaishou.com/kube-publicmysql/community-operator:8.4.7-2.1.9
#                                            ↑↑↑↑ ★ 本环境线上 MySQL 版本 = 8.4.7(8.4 LTS);tag 里的 -2.1.9 是 operator 版本号
# mysql    mysql-primary-0                    docker.io/bitnami/mysql:8.4.7-debian-12-rX   ← 本文目标(=线上 8.4.7,装法见 M3)

# 3. 看控制器(Deployment/StatefulSet)镜像 —— 比 Pod 更代表"当初装的版本"
kubectl get deploy,sts -A | grep -i mysql
kubectl get sts mysql-primary -n mysql -o jsonpath='{.spec.template.spec.containers[0].image}'

# 4. 问 MySQL 本体(要 root 密码;Bitnami chart 的密码存在 Secret 里)
PW=$(kubectl get secret mysql -n mysql -o jsonpath='{.data.mysql-root-password}' | base64 -d)
kubectl exec -n mysql mysql-primary-0 -- env MYSQL_PWD=$PW mysql -uroot -e "SELECT VERSION();"
kubectl exec -n mysql mysql-primary-0 -- mysql --version

# 5. Helm 装的,连 chart 版本一起看(APP VERSION = MySQL 版本)
helm list -A | grep -i mysql
helm get metadata mysql -n mysql    # 看 chart_version / app_version
💡 查到的版本怎么用:本环境线上已实测为 MySQL 8.4.7(8.4 LTS);新装时把 image.tag 固定成 8.4.7-debian-12-rX(见 M3 values),与线上对齐,避免版本差异带来的 SQL 行为/排序规则变化。

M21主1备方案怎么选 —— 本文用 Bitnami chart 复制架构

"1主1备"在 K8s 里有三种落地姿势,先对号入座:

方案架构自动故障切换适用
Bitnami chart · standalone单实例—测试/无关紧要的数据
★ Bitnami chart · replication(本文)1 主 + N 备,异步复制无(备可读,主挂需人工介入)读写分离 + 数据热备,大多数内网场景
★ mysql-operator(InnoDB Cluster)—— 本环境线上采用3 实例组复制 + Router 读写分离有(自动选主)要求 RPO≈0、DBA 运维能力
本文部署出来的样子(replication 架构,secondary.replicaCount=1):

应用(写) ──▶ mysql-primary:3306 ──── binlog 异步复制 ────▶ mysql-secondary:3306 ◀── 应用(读,可选)
              StatefulSet ×1(读写)                          StatefulSet ×1(只读热备)
              PVC: data-mysql-primary-0                      PVC: data-mysql-secondary-0
              Svc: mysql-primary / mysql-primary-h(头less)    Svc: mysql-secondary / mysql-secondary-h

# "备"的两层含义都占了:① 数据实时同步(热备,主盘炸了数据还在备上);
#                      ② 备库可对外提供只读服务(读写分离),不是躺着睡觉的冷备
💡 本环境线上实况(2026-10 实测):fke-db / fke-db-dev 里的 MySQL 全部是 mysql-operator(InnoDB Cluster) 方案 —— 每套库 = 3 个 community-server:8.4.7 实例(-0/-1/-2) + 2~4 个 community-router:8.4.7 + 全局一个 mysql-operator(8.4.7-2.1.9),镜像走私仓 docker.ergesanbaishou.com。本文仍用 Bitnami chart 装自己的 1主1备,版本对齐线上 8.4.7;要与线上完全同构(自动切换)请走 mysql-operator 路线 —— 注意 InnoDB Cluster 最少 3 实例起步,没有"1主1备"形态。
⚠️ 两个必须知道的真相:① 该 chart 不做自动故障切换 —— primary Pod 挂了 K8s 会把它原样拉起(数据在 PVC 里不丢),但 secondary 不会自动升主;要"主挂备自动顶上"请上 mysql-operator,复杂度高一个量级。② 异步复制有秒级延迟:刚在主上写完立刻查备库可能读不到,强一致读写都走 primary。

M3安装:Helm 一键部署 1主1备 —— values 逐行讲

第 0 步:准备 Helm 客户端(能连集群的机器上装即可)

# Linux(master 或跳板机)
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Windows(开发机)
choco install kubernetes-helm        # 或官网下 zip 解压进 PATH
# 通用前提:kubectl 能连通目标集群(helm 全靠 ~/.kube/config)
helm version                          # version.BuildInfo{Version:"v3.x.x"...} 即 OK

第 1 步:加仓库,看清楚版本 —— ★ 最新 chart ≠ 线上版本,必须锁

helm repo add bitnami https://charts.bitnami.com/bitnami && helm repo update
helm search repo bitnami/mysql
# NAME          CHART VERSION   APP VERSION   DESCRIPTION
# bitnami/mysql 14.0.3          9.4.0         ...   ← 默认只显示最新:跟踪 MySQL 9.x 创新版,不是我们要的!

# ★ 要 8.4.x(LTS):加 --versions 列历史版本,找 APP VERSION = 8.4.x 的行(实测输出)
helm search repo bitnami/mysql --versions | grep -E 'CHART|8\.4\.' | head -5
# NAME          CHART VERSION   APP VERSION
# bitnami/mysql 12.3.5          8.4.5        ← ★ 8.4 系最高 chart 到此为止(APP 8.4.5),没有 8.4.7 的 chart
# bitnami/mysql 12.3.4          8.4.5
# bitnami/mysql 12.3.3          8.4.4
# bitnami/mysql 12.3.2          8.4.4

# ★ 结论:chart 锁 12.3.5;精确到 8.4.7 靠 image.tag(同 8.4 系小版本,安全)
#   先确认 Docker Hub 有没有 8.4.7 的 tag(有输出即存在,照抄最精确那个):
curl -s 'https://hub.docker.com/v2/repositories/bitnami/mysql/tags/?name=8.4.7&page_size=10' \
  | grep -o '"name":"[^"]*"' | head -3
# ★ 2026-10 实测结论:docker.io/bitnami/mysql 已被清空(上面 curl 无任何输出)!
#   Bitnami 2025-08 起免费镜像迁移,旧 tag 全部归档到 bitnamilegacy:
curl -s 'https://hub.docker.com/v2/repositories/bitnamilegacy/mysql/tags/?name=8.4&page_size=10' \
  | grep -o '"name":"[^"]*"' | head -5
# 有 8.4.5-debian-12-r0 等 → ★ 第 2 步 values 里加 image.repository: bitnamilegacy/mysql

# (可选)看该 chart 默认镜像 tag 的格式
helm show values bitnami/mysql --version 12.3.5 | grep -A3 '^image:'
⚠️ 为什么不"最新 chart + 强改 image.tag":chart 的启动脚本/模板与 APP VERSION 配套,用 14.x(9.4) 的 chart 回退 8.4 镜像属于跨大版本混搭,容易起不来;chart 和镜像一起锁在 8.4 系才是稳的。MySQL 8.4 是 LTS,9.x 是创新版 —— 对齐线上选 8.4。

第 2 步:创建 values-mysql.yaml(★ 2026-10 服务器实测终版,整段可直接粘贴)

mkdir -p /data/git/mysql && cd /data/git/mysql     # 集中放 mysql 部署文件,以后好找

cat > values-mysql.yaml <<'EOF'
global:
  security:
    allowInsecureImages: true      # ★ 用 bitnamilegacy 归档镜像必须放行(Bitnami 校验,2025-08 新增,否则 upgrade 拒装)
architecture: replication          # ★ 复制模式(默认 standalone 单机,别漏!)
auth:
  rootPassword: "Root@123456"      # ★ root 密码,改成你的
  database: "appdb"                # 自动创建的业务库
  username: "app"                  # 业务账号(仅对 appdb 授权)
  password: "App@123456"           # ★ 改成你的
  replicationUser: replicator      # 主从复制专用账号(chart 自动建并授复制权限)
  replicationPassword: "Repl@123456"
image:
  repository: bitnamilegacy/mysql   # ★ 2026-10 实测:docker.io/bitnami 已清空,8.4 系全在 bitnamilegacy 归档
# 注:不写 tag = 用 chart 12.3.5 默认的 8.4.5-debian-12-r0(与线上 8.4.7 同系 8.4 LTS,tag 名不变)
primary:
  persistence:
    size: 8Gi                      # 主库数据卷;线上建议按数据量给足(可在线扩容,见 k8s-issues)
    # storageClass: "xxx"          # Pod 卡 Pending(绑不上默认 SC)时取消注释,填 kubectl get sc 查到的名字
  resources:
    requests: {cpu: 250m, memory: 512Mi}
    limits:   {cpu: "1",  memory: 1Gi}
secondary:
  replicaCount: 1                  # ★★★ 1 主 1 备的灵魂行:备库数量 = 1
  persistence:
    size: 8Gi                      # 建议与主库同尺寸
  resources:
    requests: {cpu: 250m, memory: 512Mi}
    limits:   {cpu: "1",  memory: 1Gi}
metrics:
  enabled: false                   # 先关(少拉一个镜像,提高首次安装成功率);要 Prometheus 指标再改 true
EOF

第 3 步:一键安装 + 盯 Pod

helm install mysql bitnami/mysql -n mysql --create-namespace \
  --version 12.3.5 -f /data/git/mysql/values-mysql.yaml
#        ★ --version 锁 8.4 系 chart,别省!(13/14.x 已是 9.x 系)
#        ★ -f 用绝对路径最稳:换了目录再跑相对路径会报
#          Error: INSTALLATION FAILED: open values-mysql.yaml: no such file or directory
# NAME: mysql  LAST DEPLOYED: ...  STATUS: deployed  REVISION: 1

kubectl get pods -n mysql -w       # 首次要拉镜像,耐心等两个都 Running(Ctrl+C 退出)
# NAME                  READY   STATUS    RESTARTS   AGE
# mysql-secondary-0     1/1     Running   0          3m
# mysql-primary-0       1/1     Running   0          3m

# 装出来的全家桶一览
kubectl get all,pvc,secret,cm -n mysql | head -30
⚠️ 重装陷阱:卸载后 PVC 默认保留!旧数据卷里存着旧 root 密码,新密码不生效、Pod 起不来 —— 重装前要么删 PVC(kubectl delete pvc -n mysql data-mysql-primary-0 data-mysql-secondary-0),要么沿用旧密码。磁盘/PVC 扩容实战见 问题总汇。

M4验证:主从复制是否正常 —— 写一条数据全程跑一遍

# 取 root 密码(后面反复用)
PW=$(kubectl get secret mysql -n mysql -o jsonpath='{.data.mysql-root-password}' | base64 -d)

# 1. 主库写入测试数据
kubectl exec -n mysql mysql-primary-0 -- env MYSQL_PWD=$PW mysql -uroot -e \
 "CREATE DATABASE IF NOT EXISTS repl_test; USE repl_test; \
  CREATE TABLE t1(id INT, name VARCHAR(32)); INSERT INTO t1 VALUES (1,'qingfeng');"

# 2. 备库读到了 = 复制链路通
kubectl exec -n mysql mysql-secondary-0 -- env MYSQL_PWD=$PW mysql -uroot -e "SELECT * FROM repl_test.t1;"
# +------+-----------+
# | id   | name      |
# | 1    | qingfeng  |        ← 备库能查到,主→备复制正常

# 3. 复制线程体检(两个 Running 都要 Yes)
kubectl exec -n mysql mysql-secondary-0 -- env MYSQL_PWD=$PW mysql -uroot -e "SHOW REPLICA STATUS\G" \
 | grep -E 'Replica_IO_Running|Replica_SQL_Running|Seconds_Behind'
#                   Replica_IO_Running: Yes      ← 从主库拉 binlog 的线程
#                  Replica_SQL_Running: Yes      ← 回放 binlog 的线程
#              Seconds_Behind_Source: 0          ← 延迟秒数(旧版字段名 Seconds_Behind_Master)
💡 健康标准:IO/SQL 双 Yes + Seconds_Behind 稳定在个位数。任一为 No 去 M7 查对应报错。env MYSQL_PWD=$PW 是为了避免密码出现在 ps/历史记录里,比 -p密码 规范。

M5连接使用:主写从读 —— 谁该连哪个地址

                     ┌─ NodePort 30306 ──→ mysql-primary-0   (10.244.0.94)  读写
集群外(Navicat等) ───┤
                     └─ NodePort 30307 ──→ mysql-secondary-0 (10.244.0.93)  只读热备

集群内应用 ──→ mysql-primary.mysql.svc.cluster.local:3306    (写)
           ──→ mysql-secondary.mysql.svc.cluster.local:3306  (读)
场景连谁地址
写入 / 事务 / 强一致读主库(必)mysql-primary.mysql.svc.cluster.local:3306
报表/大查询等可容忍延迟的读备库(可选)mysql-secondary.mysql.svc.cluster.local:3306
同 namespace 内的 Pod均可短名 mysql-primary / mysql-secondary
集群外临时调试port-forwardkubectl port-forward svc/mysql-primary -n mysql 3306:3306 后连 127.0.0.1:3306
# 应用连接串示例(JDBC;app 账号即 values 里 auth.username/password)
# 写库: jdbc:mysql://mysql-primary.mysql.svc.cluster.local:3306/appdb
# 读库: jdbc:mysql://mysql-secondary.mysql.svc.cluster.local:3306/appdb

# 集群外长期访问:Service 改 NodePort(本环境惯用套路,和 ingress 同思路)
# (默认 ClusterIP:只在集群内可达,所以 get svc 看不到 nodePort、EXTERNAL-IP 是  —— 正常设计,不是漏配)
# ★ 正规做法:写进 values(★ 手 kubectl patch 会被下次 helm upgrade 打回原形!)
#   即在已有 primary:/secondary: 段下各加 service 块(type + nodePorts 端口取 30000-32767):
sed -i '/^primary:/a\  service:\n    type: NodePort\n    nodePorts:\n      mysql: 30306' values-mysql.yaml
sed -i '/^secondary:/a\  service:\n    type: NodePort\n    nodePorts:\n      mysql: 30307' values-mysql.yaml
helm upgrade mysql bitnami/mysql -n mysql --version 12.3.5 -f values-mysql.yaml
kubectl get svc -n mysql
# service/mysql-primary     NodePort   10.x.x.x      3306:30306/TCP   ← 节点IP:30306 = 主库(读写)
# service/mysql-secondary   NodePort   10.x.x.x      3306:30307/TCP   ← 节点IP:30307 = 备库(只读)
# 之后任意节点 IP:30306 即主库;生产内网可用,公网别裸开 3306

# 只读语义自测:往备库写会被拒
kubectl exec -n mysql mysql-secondary-0 -- env MYSQL_PWD=$PW mysql -uroot -e \
 "USE repl_test; INSERT INTO t1 VALUES (2,'x');"
# ERROR 1290 (HY000): The MySQL server is running with the --read-only option
#                              ↑ 这不是故障,是备库的正确姿态!写必须走 primary

M6日常运维:升级 / 扩容 / 备份 / 卸载

① 升级 MySQL 版本(如 8.4.6 → 8.4.7,只改 image.tag)

helm upgrade mysql bitnami/mysql -n mysql -f values-mysql.yaml \
  --version 12.3.5 --set image.tag=8.4.7-debian-12-rX   # chart 版本同样要带,与安装时保持一致
kubectl rollout status sts/mysql-primary -n mysql     # 等滚动完成
kubectl rollout status sts/mysql-secondary -n mysql
# 复查:回到 M1 第 4 条 SELECT VERSION();复制状态回 M4 体检

② 读扩容:备库 1 → 2(1主2备;缩容同理改回 1)

helm upgrade mysql bitnami/mysql -n mysql -f values-mysql.yaml --set secondary.replicaCount=2
kubectl get pods -n mysql     # 新增 mysql-secondary-1,自动从主库同步全量数据

③ 备份(逻辑备份,在主库跑;二进制大库可对备库跑减少主库压力)

PW=$(kubectl get secret mysql -n mysql -o jsonpath='{.data.mysql-root-password}' | base64 -d)
kubectl exec -n mysql mysql-primary-0 -- env MYSQL_PWD=$PW sh -c \
 'mysqldump -uroot --all-databases --single-transaction --routines --triggers --events' \
 > mysql-all-$(date +%F).sql
ls -lh mysql-all-*.sql        # 恢复:mysql -uroot -p < mysql-all-2026-10-22.sql
# 参考:线上 fke-db 的 hms-mysql-backup / apifox-mysql-backup(CronJob 定时备份)用的就是 mysqlsh dump/restore

④ 卸载(PVC 默认保留;连数据一起删才执行第二步)

helm uninstall mysql -n mysql
kubectl delete pvc -n mysql data-mysql-primary-0 data-mysql-secondary-0   # ★ 数据将清空,确认再敲
kubectl delete ns mysql

⑤ values 文件丢了 / 要部署到另一套集群:导出与迁移

# 从已有 release 导回 values(接手别人装的环境同样适用!)
helm get values mysql -n mysql          # 只看你 -f 传的自定义项 = values-mysql.yaml 的等效物
helm get values mysql -n mysql --all    # 自定义 + chart 默认全量(排错看全貌用)
helm get manifest mysql -n mysql        # 渲染后的最终 YAML(所有资源对象实际长这样)
helm history mysql -n mysql             # 升级历史,每次 REVISION 都可回滚

# 把这套部署搬去另一套集群(如公司线上 fke)的 checklist:
# 1) 传文件:scp values-mysql.yaml 用户@线上操作机:/data/git/mysql/
# 2) 切集群:kubectl config get-contexts 看有哪些 → kubectl config use-context <目标>
# 3) values 按目标集群改:密码(换正式的!)/storageClass(kubectl get sc 查目标集群的)/size/NodePort 端口(避开占用)
# 4) 镜像源:公司线上走私仓 docker.ergesanbaishou.com —— image.repository 改私仓路径(bitnami 镜像需私仓已有或可代理;
#    若线上直连 docker.io 受限,bitnamilegacy 同样要提前验证可拉)
# 5) 安装命令原样照搬:helm install ... --version 12.3.5 -f /data/git/mysql/values-mysql.yaml
💡 变更三板斧:改 values → helm upgrade(Helm 只 apply 差异)→ kubectl rollout status 盯滚动。永远别 kubectl edit Helm 管理的资源 —— 下次 upgrade 会被打回原形。Helm 常用命令速查另见 命令速查。

M7高频报错速查表

现象/报错根因解决
Pod 一直 Pending没有默认 StorageClass / PVC 绑不上kubectl get sc;values 里显式 storageClass;排障详见 问题总汇 · 挂载失败
ImagePullBackOff / unexpected media type text/html★ 2026-10 实测:docker.io/bitnami 已清空,8.4 镜像在 bitnamilegacy 归档(纯 i/o timeout 才是网络问题)values 改 image.repository: bitnamilegacy/mysql 后 helm upgrade(见 M3 第 2 步);其他排查见 问题总汇 · 拉不下镜像
root 登录 Access denied重装时旧 PVC 带着旧密码,新密码没写进去用旧密码;或删 PVC 重装(见 M3 重装陷阱)
备库查不到最新数据复制延迟 / 复制线程断了SHOW REPLICA STATUS\G 看 Seconds_Behind 与 Last_Error;双 No → 检查 replicationUser 密码与 primary 可达
ERROR 1290 ... --read-only option往备库写设计如此,写走 mysql-primary(见 M5)
Pod CrashLoopBackOff 反复重启Secret 密码与 PVC 旧数据不一致 / 内存 limit 太小kubectl logs 看启动报错;对齐密码或删 PVC;调大 resources
secondary 起不来,日志报连不上 primaryprimary 未就绪时 secondary 先起等 primary Running 后 kubectl delete pod mysql-secondary-0 -n mysql 让它重连
helm upgrade 后配置没变资源被手动 kubectl edit 过,upgrade 跳过漂移项所有变更走 helm upgrade,别手改(见 M6 三板斧)
helm install 报 open values-mysql.yaml: no such file or directory当前目录没有该文件(创建后换了目录跑)-f 用绝对路径,如 -f /data/git/mysql/values-mysql.yaml(见 M3 第 3 步)
upgrade 报 Original containers have been substituted for unrecognized onesBitnami chart 校验到非官方仓库镜像(如 bitnamilegacy)values 加 global.security.allowInsecureImages: true(见 M3 第 2 步)
💡 互链:排障三板斧(describe/logs/events)见 问题总汇 · 第 07 节;磁盘满止血与 PVC 在线扩容(数据库盘最常用)见 问题总汇;Harbor 私仓对接见 Harbor 文档。