K8s 系列 | 第 10 天:StatefulSet:有状态应用的部署与管理


tags:
– Kubernetes
– K8s系列
– DevOps
– StatefulSet
– 有状态应用
– 存储
– 容器编排


K8s 系列 | 第 10 天:StatefulSet:有状态应用的部署与管理

第 10/30 天


引言

在前面的文章中,我们学习了 Deployment 和 ReplicaSet 如何管理无状态应用——这些 Pod 可以随时被销毁、重建、迁移,像极了”一次性筷子”。然而,现实世界中有大量有状态应用:数据库(MySQL、PostgreSQL、Redis)、消息队列(Kafka、RabbitMQ)、分布式存储(Cassandra、Elasticsearch)等。这些应用要求:

  • 每个 Pod 实例有稳定的网络标识(即使重启也不变)
  • 每个 Pod 绑定独立的持久化存储
  • Pod 按顺序启动、更新、销毁

Kubernetes 为此提供了 StatefulSet 控制器。今天,我们就来深入探讨 StatefulSet 的工作原理、核心特性与实战部署。


核心概念

StatefulSet vs Deployment

特性 Deployment StatefulSet
Pod 标识 随机名称(如 app-5d7f8c9b6-x3k2p 有序名称(如 web-0, web-1, web-2
存储 共享卷或临时卷 每个 Pod 绑定独立 PVC
启停顺序 无序并行 顺序启停(0→1→2,2→1→0)
网络标识 Pod IP(不稳定) 稳定 DNS 名称(如 web-0.svc.ns.svc.cluster.local
适用场景 无状态应用 有状态应用

核心特性详解

  1. 有序、优雅的部署与扩缩
  2. 创建时从 0N-1 依次启动
  3. 缩容时从 N-10 依次终止
  4. 每个 Pod 的 READY 状态确认后才启动下一个

  5. 稳定的网络标识

  6. 每个 Pod 名称固定:<statefulset-name>-<ordinal>
  7. 配合 Headless ServiceClusterIP: None)实现稳定的 DNS 解析
  8. Pod 重启后 IP 可能变,但 DNS 名称不变

  9. 稳定的持久化存储

  10. 基于 volumeClaimTemplates 自动创建 PVC
  11. 每个 Pod 绑定专属 PV,Pod 重建后挂载同一存储
  12. Pod 删除不会清除 PVC,确保数据安全

Pod 名称模式

当创建一个名为 mysql、副本数为 3 的 StatefulSet 时:

  • Pod 名称:mysql-0, mysql-1, mysql-2
  • DNS:mysql-0.mysql.default.svc.cluster.local
  • PVC 名称:data-mysql-0, data-mysql-1, data-mysql-2

实战步骤

1. 部署 Headless Service

StatefulSet 必须配合 Headless Service 使用,为每个 Pod 提供稳定的 DNS 入口。

# headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - port: 3306
    name: mysql
  clusterIP: None       # Headless Service 关键字段
  selector:
    app: mysql

2. 创建 StatefulSet

下面部署一个 MySQL 主从架构的 StatefulSet:

# statefulset-mysql.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql          # 关联 Headless Service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: root-password
        - name: MYSQL_REPLICATION_USER
          value: "repl_user"
        - name: MYSQL_REPLICATION_PASSWORD
          value: "repl_password"
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        readinessProbe:
          exec:
            command:
            - mysqladmin
            - ping
          initialDelaySeconds: 10
          timeoutSeconds: 5
  volumeClaimTemplates:       # 自动为每个 Pod 创建 PVC
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: standard   # 使用默认 StorageClass(前文第9天内容)
      resources:
        requests:
          storage: 10Gi

3. 创建 Secret(用于 MySQL 密码)

# mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque
data:
  root-password: cHJvZDJkYjIwMjY=      # base64 编码的密码

4. 部署并验证

# 1. 创建命名空间(可选)
kubectl create namespace database

# 2. 部署 Secret
kubectl apply -f mysql-secret.yaml -n database

# 3. 部署 Headless Service
kubectl apply -f headless-svc.yaml -n database

# 4. 部署 StatefulSet
kubectl apply -f statefulset-mysql.yaml -n database

# 5. 观察 Pod 有序创建(逐个启动)
kubectl get pods -n database -w

预期输出顺序如下:

mysql-0   0/1   Pending   0     0s
mysql-0   0/1   Pending   0     2s
mysql-0   0/1   ContainerCreating   0     2s
mysql-0   1/1   Running   0     30s
mysql-1   0/1   Pending   0     1s      ← 等待 mysql-0 Ready 后才启动
mysql-1   0/1   ContainerCreating   0     2s
mysql-1   1/1   Running   0     35s
mysql-2   0/1   Pending   0     1s      ← 等待 mysql-1 Ready 后才启动
mysql-2   0/1   ContainerCreating   0     2s
mysql-2   1/1   Running   0     40s

5. 验证稳定网络标识

# 进入一个临时 Pod 测试 DNS 解析
kubectl run -it --rm dns-test --image=busybox:1.28 -- sh

# 在容器内执行
nslookup mysql-0.mysql.database.svc.cluster.local
# 输出: Name: mysql-0.mysql.database.svc.cluster.local
#       Address: 10.244.1.10

nslookup mysql-1.mysql.database.svc.cluster.local
# 输出: Name: mysql-1.mysql.database.svc.cluster.local
#       Address: 10.244.2.11

即使 Pod 被删除重建,DNS 名称保持不变,新的 Pod 会以相同的序号重新接管。

6. 扩缩容操作

# 扩容到 5 个副本
kubectl scale statefulset mysql --replicas=5 -n database
# 观察:mysql-3 启动 → mysql-4 启动(有序扩展)

# 缩容到 2 个副本
kubectl scale statefulset mysql --replicas=2 -n database
# 观察:mysql-4 终止 → mysql-3 终止 → mysql-2 终止(从高序号到低序号)

7. 滚动更新

# 更新策略:滚动更新 + 分区更新
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2        # 只更新序号 >= 2 的 Pod
# 触发滚动更新(修改 image 标签)
kubectl set image statefulset/mysql mysql=mysql:8.0.35 -n database

# 查看更新状态
kubectl rollout status statefulset/mysql -n database

常见问题

Q1:StatefulSet 删除后数据会丢失吗?

不会。 StatefulSet 删除时不会自动删除关联的 PVC。需要手动清理:

# 先删除 StatefulSet
kubectl delete statefulset mysql -n database

# 手动删除 PVC(确认数据不需要后)
kubectl delete pvc -l app=mysql -n database

Q2:如何实现 MySQL 主从自动配置?

可以在 StatefulSet 的 Pod 中通过 $HOSTNAME 环境变量判断角色:

# mysql-0 作为主库
if [[ "$HOSTNAME" == "mysql-0" ]]; then
  # 配置为主节点
  mysql -e "CREATE USER 'repl_user'@'%' IDENTIFIED BY 'password';"
  mysql -e "GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%';"
else
  # 配置为从节点
  MASTER_HOST="mysql-0.mysql.database.svc.cluster.local"
  mysql -e "CHANGE MASTER TO MASTER_HOST='${MASTER_HOST}', ..."
  mysql -e "START SLAVE;"
fi

Q3:StatefulSet 支持并行启动吗?

支持。设置 podManagementPolicy: Parallel 可以让所有 Pod 同时启动,适用于不需要严格顺序的场景(如 Cassandra、Elasticsearch):

spec:
  podManagementPolicy: Parallel
  serviceName: elasticsearch
  replicas: 3

Q4:StorageClass 不可用怎么办?

确保集群中有可用的 StorageClass。如果使用本地存储或特定云平台,需要先配置:

# 查看当前 StorageClass
kubectl get sc

# 不存在时可以使用 local-path-provisioner(K3s 自带)
# 或参考第 9 天内容配置 NFS/Rook Ceph 等

总结

StatefulSet 是 Kubernetes 中管理有状态应用的核心控制器,它的三大基石——稳定的 Pod 标识独立的持久化存储有序的部署与销毁——让数据库、消息队列等有状态工作负载能够自信地在 K8s 上运行。

关键点 说明
Headless Service 必须配合 StatefulSet,提供稳定 DNS
volumeClaimTemplates 自动为每个 Pod 创建独立 PVC
Pod 命名规则 <sts-name>-<ordinal>,从 0 开始
有序策略 默认 OrderedReady,可改为 Parallel
更新策略 支持分区更新、回滚、金丝雀发布

一句话记住:无状态用 Deployment,有状态用 StatefulSet——这是 K8s 工作负载设计的第一原则。


下期预告

第 11 天我们将学习 Ingress 与 Ingress Controller——如何将集群外部的流量优雅地路由到内部 Service,实现域名绑定、TLS 终结和路径转发,打通用户到应用的”最后一公里”。


系列目录:
– 第 1 天:Kubernetes 是什么?核心概念与架构全景解析
– 第 2 天:手把手搭建你的第一个 K8s 集群(kubeadm 实战)
– 第 3 天:Pod 详解:K8s 最小的调度单元与生命周期管理
– 第 4 天:Deployment 与 ReplicaSet:声明式应用管理
– 第 5 天:Service 与网络基础:ClusterIP、NodePort、LoadBalancer 详解
– 第 6 天:Namespace 与资源配额:多租户隔离基础
– 第 7 天:ConfigMap 与 Secret:配置管理最佳实践
– 第 8 天:Volume 与 PersistentVolume:存储抽象层的核心机制
– 第 9 天:StorageClass 与动态存储供给实战
第 10 天:StatefulSet:有状态应用的部署与管理(本篇)
– 第 11 天:Ingress 与 Ingress Controller:外部流量接入全攻略
– … 持续更新中 …

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容