Skip to content

StatefulSet 有状态应用深度解析

为什么需要 StatefulSet

Deployment 管理的 Pod 是无状态的(Pod 名称随机、重建后 IP 变化、存储不持久)。StatefulSet 为有状态应用提供:

  • 稳定的网络标识:Pod 名称固定(pod-0pod-1pod-2
  • 稳定的存储:每个 Pod 绑定独立的 PVC,重建后自动重新挂载
  • 有序部署/扩缩容:按序号顺序操作(0→1→2)
  • 有序滚动更新:从最大序号开始(2→1→0)

典型使用场景

  • 数据库集群(MySQL、PostgreSQL、MongoDB)
  • 消息队列(Kafka、RabbitMQ)
  • 分布式存储(Elasticsearch、Cassandra)
  • 分布式协调(ZooKeeper、etcd)

完整配置示例

yaml
# Headless Service(StatefulSet 必须配套)
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  clusterIP: None  # Headless:不分配 ClusterIP
  selector:
    app: mysql
  ports:
  - port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql  # 关联 Headless Service
  replicas: 3
  selector:
    matchLabels:
      app: mysql

  # 更新策略
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0  # 只更新序号 >= partition 的 Pod(金丝雀发布)

  # Pod 管理策略
  podManagementPolicy: OrderedReady  # OrderedReady | Parallel

  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          # 根据 Pod 序号决定角色
          [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          if [[ $ordinal -eq 0 ]]; then
            echo "I am the primary"
          else
            echo "I am a replica"
          fi
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d

      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: "500m"
            memory: "1Gi"
          limits:
            cpu: "2"
            memory: "4Gi"

      volumes:
      - name: conf
        emptyDir: {}

  # 每个 Pod 独立的 PVC 模板
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: fast-ssd
      resources:
        requests:
          storage: 50Gi

稳定的网络标识

StatefulSet 配合 Headless Service,每个 Pod 获得固定 DNS 名称:

Pod 名称:mysql-0, mysql-1, mysql-2
DNS 格式:<pod-name>.<service-name>.<namespace>.svc.cluster.local

mysql-0.mysql.default.svc.cluster.local → mysql-0 的 IP
mysql-1.mysql.default.svc.cluster.local → mysql-1 的 IP
mysql-2.mysql.default.svc.cluster.local → mysql-2 的 IP

应用可以通过固定 DNS 名称访问特定 Pod(如主节点):

go
// 连接 MySQL 主节点(始终是 mysql-0)
db, err := sql.Open("mysql", "root:password@tcp(mysql-0.mysql:3306)/mydb")

有序操作

扩容(0→1→2):
  创建 mysql-0 → 等待 Ready → 创建 mysql-1 → 等待 Ready → 创建 mysql-2

缩容(2→1→0):
  删除 mysql-2 → 等待终止 → 删除 mysql-1 → 等待终止

滚动更新(2→1→0):
  更新 mysql-2 → 等待 Ready → 更新 mysql-1 → 等待 Ready → 更新 mysql-0

金丝雀发布(partition)

bash
# 只更新序号 >= 2 的 Pod(先验证 mysql-2)
kubectl patch statefulset mysql -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'
kubectl set image statefulset/mysql mysql=mysql:8.1

# 验证 mysql-2 正常后,继续更新剩余 Pod
kubectl patch statefulset mysql -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'

常用操作

bash
# 查看 StatefulSet 状态
kubectl get statefulset mysql
kubectl rollout status statefulset/mysql

# 查看各 Pod 的 PVC
kubectl get pvc -l app=mysql

# 扩容(PVC 自动创建)
kubectl scale statefulset mysql --replicas=5

# 缩容(PVC 不会自动删除,需手动清理)
kubectl scale statefulset mysql --replicas=3
kubectl delete pvc data-mysql-3 data-mysql-4

# 删除 StatefulSet(保留 PVC)
kubectl delete statefulset mysql --cascade=orphan

与 Deployment 对比

特性DeploymentStatefulSet
Pod 名称随机后缀固定序号
网络标识随机 IP固定 DNS
存储共享或无每 Pod 独立 PVC
部署顺序并行有序
更新顺序随机逆序
适用场景无状态服务有状态服务

本站内容由 褚成志 整理编写,仅供学习参考