Kubernetes StatefulSet 实战:为什么数据库不能用 Deployment 部署

Kubernetes StatefulSet 实战:为什么数据库不能用 Deployment 部署

很多人第一次在 K8s 上跑 MySQL、Redis、ZooKeeper,直接抄了个 Deployment 的 YAML,结果 Pod 一重启数据就没了,或者主从复制配了半天 IP 一变就全乱套。问题不在你,而在于有状态应用根本不该用 Deployment。这篇讲清楚 StatefulSet 解决了什么、和 Deployment 差在哪,以及一个能跑起来的完整例子。

先看 Deployment 为什么不行

Deployment 管理的 Pod 是「牲畜不是宠物」------它假设每个副本完全对等、可随意替换。这带来三个对有状态应用致命的问题:

  • Pod 名字是随机的 :web-7d9f8c-x2k9web-7d9f8c-p4m1,重启后名字全变。主从复制里从库要连主库,连的地址一直变,配置根本没法写死。
  • 所有 Pod 共享同一个 PVC(或各自随机) :Deployment 的多个副本挂的是同一个存储,或者用 volumeClaimTemplates 也没法保证 Pod 和存储的固定对应。数据库多实例各写各的盘,Deployment 给不了这种保证。
  • 启动/停止是并行、无序的:Deployment 扩容时一把全拉起来。但很多集群软件要求「先起主节点,再起从节点」,顺序错了初始化就失败。

StatefulSet 就是为解决这三点而生的。

StatefulSet 给的三个稳定保证

1. 稳定的网络标识(Pod 名字固定且有序)。 StatefulSet 的 Pod 名字是 <statefulset名>-<序号>,从 0 开始:mysql-0mysql-1mysql-2。Pod 挂了重建,名字不变。配上一个 Headless Service,每个 Pod 还能拿到稳定的 DNS 域名:

复制代码
mysql-0.mysql.default.svc.cluster.local
mysql-1.mysql.default.svc.cluster.local

从此从库连主库,直接写 mysql-0.mysql 就行,永远指向 0 号 Pod,不管它重建多少次。

2. 稳定的独占存储。 StatefulSet 用 volumeClaimTemplates 给每个 Pod 生成独立 的 PVC:data-mysql-0data-mysql-1......Pod 删了再建,还是绑回原来那块盘,数据不丢。这是和 Deployment 最本质的区别。

3. 有序部署与伸缩。 默认按序号顺序创建(0→1→2),前一个 Ready 了才起下一个;缩容时逆序删除(2→1→0)。这正好满足「先主后从」的启动需求。

一个能跑的完整例子

先建 Headless Service(clusterIP: None 是关键,它让 DNS 直接解析到每个 Pod 而不是负载均衡):

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  clusterIP: None   # Headless:不分配 VIP,DNS 直接返回各 Pod IP
  selector:
    app: mysql
  ports:
    - name: mysql
      port: 3306

再写 StatefulSet:

yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql        # 必须指向上面那个 Headless Service,否则拿不到稳定 DNS
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          ports:
            - containerPort: 3306
              name: mysql
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: root-password
          volumeMounts:
            - name: data          # 对应下面 volumeClaimTemplates 的 name
              mountPath: /var/lib/mysql
  volumeClaimTemplates:           # 每个 Pod 自动生成独立 PVC
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: standard   # 换成你集群实际的 StorageClass
        resources:
          requests:
            storage: 10Gi

apply 之后观察:

bash 复制代码
kubectl apply -f mysql-statefulset.yaml

# Pod 名字是有序的,而且是一个一个起来的
kubectl get pods -w
# mysql-0   0/1   ContainerCreating
# mysql-0   1/1   Running        <- 0 号 Ready 后才起 1 号
# mysql-1   0/1   Pending
# ...

# 每个 Pod 有独立 PVC,名字带序号
kubectl get pvc
# data-mysql-0   Bound   ...
# data-mysql-1   Bound   ...
# data-mysql-2   Bound   ...

验证稳定 DNS(在集群内任意 Pod 里):

bash 复制代码
# 从 mysql-1 连 mysql-0,地址写死也永远有效
mysql -h mysql-0.mysql -uroot -p

三个最容易踩的坑

坑一:删了 StatefulSet,PVC 不会自动删。 这其实是保护机制------防止误删丢数据。但你重建时要注意,新 StatefulSet 会复用同名的旧 PVC,里面还是老数据。想彻底清空得手动删 PVC:

bash 复制代码
kubectl delete statefulset mysql       # Pod 没了,但 PVC 还在
kubectl get pvc                         # data-mysql-0/1/2 仍然 Bound
kubectl delete pvc data-mysql-0 data-mysql-1 data-mysql-2  # 要清数据得手动删

坑二:缩容不会删 PVC。 把 replicas 从 3 改成 1,mysql-1/mysql-2 的 Pod 被删,但 data-mysql-1/data-mysql-2 这两个 PVC 保留着。下次扩回 3,这两个 Pod 会绑回原来的盘、拿回原来的数据。这是特性不是 bug,但计费时别忘了这些「孤儿」PVC 还在占存储费。

坑三:滚动更新默认也是逆序、一个一个来。 StatefulSet 的 updateStrategy 默认是 RollingUpdate,从最大序号往小更新,一次一个。这比 Deployment 慢,但对有状态服务是安全的。如果你要金丝雀验证,可以用 partition 只更新序号 >= N 的 Pod:

yaml 复制代码
spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2   # 只更新 mysql-2,mysql-0/1 保持旧版本,验证 OK 再把 partition 调到 0

什么时候该用 StatefulSet

判断标准很简单:Pod 之间是不是需要「区分身份」

  • 需要固定名字/固定存储/固定启动顺序 → StatefulSet:数据库、消息队列(Kafka/RabbitMQ)、分布式协调(ZooKeeper/etcd)、需要 sticky 会话或分片的服务。
  • 无状态、副本完全对等、随便杀随便换 → Deployment:HTTP API、前端、无状态 worker。

别为了「看起来高级」把无状态服务塞进 StatefulSet,那只会让你的滚动更新变慢、扩缩容变笨。

小结

  • Deployment 的 Pod 名字随机、存储不固定、启停无序,这三点让它不适合有状态应用
  • StatefulSet 提供三个稳定保证:有序固定的 Pod 名字 + Headless Service 给的稳定 DNS每 Pod 独立且重建不丢的 PVC有序的部署与伸缩
  • 必配 serviceName 指向一个 clusterIP: None 的 Headless Service,存储用 volumeClaimTemplates 而不是共享 PVC。
  • 记住三个坑:删 StatefulSet 和缩容都不删 PVC (保护数据,但要手动清理);滚动更新逆序单个进行,可用 partition 做金丝雀。

一句话记忆点:Pod 需要有名有姓有自己的盘,就用 StatefulSet;随便替换的用 Deployment。

相关推荐
斑马13913 分钟前
Linux软件编程学习笔记(十三)——数据库
数据库·oracle
初願致夕霞27 分钟前
MySQL_索引
数据库·mysql
十六年开源服务商1 小时前
2026网站备份方案完整指南
数据库·oracle
一只小李郁vickie2 小时前
mysql 开启压缩传输3402条数据2.1秒压缩到毫秒级
数据库·mysql
张继雁2 小时前
张继雁 个人技术简介|磨削加工过滤方向
大数据·数据库·论文阅读·人工智能·机器学习·创业创新·业界资讯
云祺vinchin2 小时前
桌面云如何高效备份?某科技公司灾备建设实战
云原生·数据安全·数据备份·无代理备份·桌面云
名字还没想好☜2 小时前
Docker 数据卷实战:volume、bind mount、tmpfs 到底怎么选,数据持久化与权限坑
运维·docker·容器·kubernetes
IvorySQL2 小时前
PostgreSQL 日报|不重启动态修改 shared_buffers(9 月 8 日)
数据库·postgresql
这个DBA有点耶3 小时前
2026年做数据库开发,国产数据库已经是绕不开的选项了
数据库·dba·敏捷开发
这个DBA有点耶3 小时前
COUNT慢不是因为用了*,是这5个原因——1000万行数据实测+执行计划深度解析
数据库·mysql·算法