StatefulSet是唯一合理的选择,Deployment无法满足MySQL对稳定标识、有序启动和独占存储的需求。用错控制器,后续所有高可用设计都会失效。

直接说结论:StatefulSet 是唯一合理的选择,Deployment 无法满足 MySQL 对稳定标识、有序启动和独占存储的需求。用错控制器,后续所有高可用设计都会失效。
为什么必须用 StatefulSet 而不是 Deployment
MySQL 启动失败、主从同步中断、连接被拒绝——这些故障里有 60% 源于误用了 Deployment。Deployment 会随机销毁/重建 Pod,导致:
-
server-id冲突(两个 Pod 都设为1) - PVC 被错误复用(新 Pod 挂载旧数据目录但未初始化,
mysqld直接拒绝启动) - DNS 名称不固定(
mysql-0.mysql.default.svc.cluster.local无法解析,主从无法互相发现)
StatefulSet 的三个硬性保障才是关键:serviceName 触发 Headless Service 解析、volumeClaimTemplates 为每个 Pod 绑定专属 PVC、Pod 按 0→1→2 顺序创建——缺一不可。
StatefulSet 中必须配置的 4 个核心字段
漏掉任意一个,MySQL 就无法形成可管理的集群:
-
serviceName:必须指向一个clusterIP: None的 Headless Service,否则 Pod 间 DNS 不生效 -
volumeClaimTemplates:不能写在spec.template.spec.volumes下,必须是顶层字段,否则每个 Pod 不会生成独立 PVC -
selector.matchLabels和template.metadata.labels必须严格一致,否则 StatefulSet 控制器无法关联 Pod -
replicas建议设为奇数(如3),避免脑裂时无法投票选出主节点
常见错误写法:service: { name: "mysql" } —— 这是 Deployment 写法,StatefulSet 必须用 serviceName 字段。
initContainers 是主从自动分化的关键
不要指望靠 ConfigMap 一份配置打天下。主节点要开 log-bin,从节点要设 super-read-only=ON,且 server-id 必须全局唯一。靠 initContainer 动态生成最可靠:
initContainers:
- name: init-mysql
image: mysql:8.0
command: [ "bash", "-c" ]
args:
- |
set -ex
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo "[mysqld]" > /mnt/conf.d/server-id.cnf
echo "server-id=$((100 + $ordinal))" >> /mnt/conf.d/server-id.cnf
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/primary.cnf /mnt/conf.d/
else
cp /mnt/config-map/replica.cnf /mnt/conf.d/
fi
注意两点:一是用 emptyDir 挂载 /mnt/conf.d,避免覆盖镜像内默认配置;二是 server-id 偏移量设为 100,避开 MySQL 保留值 0 和常见教程用的 1。
StorageClass 和 PVC 的典型陷阱
本地测试常用 hostPath 或 local 类型 PV,但生产环境必须禁用:
-
accessModes: ["ReadWriteOnce"]是硬性要求,ReadWriteMany会导致数据损坏 - 若用 NFS,确认后端支持
hard挂载选项,否则网络抖动时 Pod 可能卡在Terminating -
volumeBindingMode: WaitForFirstConsumer必须启用,否则 PVC 会提前绑定到某个节点,后续调度失败 - StorageClass 的
provisioner值必须与集群实际插件匹配(如disk.csi.azure.com不能写成azure-disk)
最容易被忽略的是 PVC 的 resources.requests.storage:它不仅限制容量,还影响底层存储卷的性能规格(例如 AWS EBS 的 gp3 类型,IOPS 随容量线性增长)。设 10Gi 可能导致 IOPS 不足,查询延迟飙升。











