Kubernetes(K8s)学习笔记(第十四期):集群存储与有状态应用(下篇):StatefulSet 有状态应用管理

Kubernetes(K8s)学习笔记(第十四期):集群存储与有状态应用(下篇):StatefulSet 有状态应用管理

本笔记为 Kubernetes 系列第十四期,聚焦有状态应用的管理。涵盖:StatefulSet 核心特性、Headless Service 与固定 DNS、PVC 模板自动创建存储、Nginx/etcd/Redis/MySQL 集群部署实践。所有命令和 YAML 示例均已经过整理和注释。全文约 4700 字 ,包含 16 个 YAML 示例55+ 命令示例12 张对比表格,是 Kubernetes 有状态应用管理的完整指南。
知识衔接:

Kubernetes(K8s)学习笔记(第十三期):集群存储与有状态应用(上篇):动态卷供应------StorageClass + Provisioner

更多kubernetes系列知识:kubernetes从入门到进阶

我的主页:AOwhisky,这里有更多运维系统性知识整理和其他有趣内容,欢迎与我一起探讨学习~
--- Compiled and Authored by Whisky --- July 4 th, 2026

目录

  1. 引子:从"无状态"到"有状态"
  2. 无状态 vs 有状态应用
  3. StatefulSet 核心特性
  4. StatefulSet vs Deployment 对比
  5. 实验一:部署 Nginx 集群
  6. 实验二:部署 etcd 集群
  7. 实验三:部署 Redis 集群(主从 + 哨兵)
  8. 实验四:部署 MySQL 集群(主从同步)
  9. StatefulSet 更新策略
  10. StatefulSet Pod 管理
  11. 总结与知识点一览表

引子:从"无状态"到"有状态"

在前三期(ResourceQuota + LimitRange、HPA、动态卷供应)中,我们完成了集群资源管理和存储供应的基础建设。现在,我们面临一个更重要的问题:如何管理有状态应用?

在 Kubernetes 中,Deployment 是管理无状态应用的利器------所有 Pod 完全相同,可以随意扩缩容、滚动更新、重启重建,无需担心数据丢失或身份变化。但现实世界中,大多数应用都是有状态的:

  • 数据库:每个节点有独立的存储,主从身份不能混淆
  • 分布式中间件:每个节点有固定的网络标识,集群需要按顺序启动
  • 消息队列:节点间需要稳定的通信地址

这些应用需要:

  • 固定的网络标识:Pod 重启后 IP 会变化,但 hostname 必须保持不变
  • 独立的持久化存储:每个 Pod 有自己的数据卷,不能共享
  • 有序的启动和停止:主节点先启动,从节点后启动;缩容时从后往前删除

StatefulSet 正是为这些需求而设计的。

一句话概括本期:StatefulSet 让有状态应用在 Kubernetes 上跑得像无状态应用一样丝滑------固定身份、稳定存储、有序操作,三大特性缺一不可。

一、无状态 vs 有状态应用

1.1 核心区别

维度 无状态应用(Stateless) 有状态应用(Stateful)
核心特征 不保存任何请求/会话数据,每次请求独立 依赖持久化的会话/数据,请求间有依赖关系
数据存储 数据仅在请求期间存在,不落地 数据需持久化,依赖固定存储
实例一致性 所有实例完全相同,可随意扩缩容/替换 实例有身份(如编号),启动/扩缩容有顺序要求
网络标识 共享 IP/域名,无固定网络标识 有固定网络标识(如 Headless Service + DNS)
典型应用 Web 服务、API 网关、静态页面 数据库、Redis、ZooKeeper、Kafka

1.2 为什么无状态应用优先推荐?

无状态应用是云原生的最佳实践:

  • 实例可随意替换,故障自愈能力强
  • 扩缩容无需等待数据迁移,秒级完成
  • 滚动更新简单,无需考虑顺序

最佳实践:尽可能将应用拆分为"无状态业务层 + 有状态数据层",既提升扩展能力,又保障数据安全。

二、StatefulSet 核心特性

2.1 三大核心特性

特性 说明
固定身份标识 每个 Pod 拥有唯一的序号(从 0 开始递增),hostname 固定为「StatefulSet 名称-序号」
有序操作 部署/扩容按 0→N-1 顺序创建;缩容/删除按 N-1→0 逆序删除;更新按 N-1→0 逆序滚动
稳定存储 通过 PVC 模板为每个 Pod 自动创建独立的 PVC,Pod 重建后自动复用原 PVC

2.2 固定网络标识

StatefulSet 必须关联一个 Headless Service(ClusterIP: None),为每个 Pod 提供固定的 DNS 解析:

text 复制代码
<statefulset-name>-<ordinal>.<service-name>.<namespace>.svc.cluster.local

示例 :StatefulSet nginx,Service nginx,Namespace default

  • nginx-0.nginx.default.svc.cluster.local
  • nginx-1.nginx.default.svc.cluster.local
  • nginx-2.nginx.default.svc.cluster.local

Pod 名称的稳定来源

StatefulSet 中 Pod 的名称由两部分组成:<statefulset-name>-<ordinal>。这个名称是稳定且可预测的,不随 Pod 重启或重建而改变。

  • ordinal 从 0 开始递增,永不重复
  • Pod 重建后,ordinal 保持不变
  • 通过 metadata.name 字段,Pod 可以获取自己的名称

环境变量获取 Pod 名称

yaml 复制代码
env:
- name: POD_NAME
  valueFrom:
    fieldRef:
      fieldPath: metadata.name

2.3 有序操作详解

操作 顺序 说明
创建/扩容 0 → 1 → 2 ... 前一个 Pod 必须处于 Running 和 Ready 状态,才会创建下一个
删除/缩容 ... → 2 → 1 → 0 前一个 Pod 彻底删除后,才会删除下一个
滚动更新 ... → 2 → 1 → 0 默认从最后一个序号开始更新(逆序)

为什么需要有序操作?

有状态应用通常有启动依赖关系:

  • 主节点(ordinal=0)必须先启动,初始化集群
  • 从节点(ordinal>0)依赖主节点的网络地址加入集群
  • 如果无序启动,从节点可能无法找到主节点

StatefulSet 的有序操作保证了:

  1. 启动顺序:确保集群先有"主人",再有"随从"
  2. 终止顺序:缩容时先删除"随从",再删除"主人",避免数据丢失
  3. 更新顺序:从最后一个节点开始更新,保持集群最小可用性

2.4 稳定存储

PVC 模板(volumeClaimTemplates)

  • 为每个 Pod 自动创建独立的 PVC
  • PVC 命名格式:<template-name>-<statefulset-name>-<ordinal>
  • Pod 删除后,PVC 和 PV 仍然保留,数据不丢失
  • Pod 重建后自动复用原 PVC

示例 :StatefulSet nginx,PVC 模板 nginx-data

  • nginx-data-nginx-0
  • nginx-data-nginx-1
  • nginx-data-nginx-2

重要 :删除 StatefulSet 时,PVC 不会自动删除。如需清理数据,需要手动删除 PVC。

三、StatefulSet vs Deployment 对比

控制器 StatefulSet Deployment
适用场景 有状态应用(数据库、分布式集群、消息队列) 无状态应用(Web 服务、API 接口、静态服务)
网络 Headless Service(固定 DNS 解析) ClusterIP/LoadBalancer(共享 IP)
存储 必须 PVC + PV(固定存储卷绑定) 可选 PVC(无数据持久化要求)
扩缩容 按实例编号顺序扩缩容(如从 0→1→2) 无顺序,瞬间完成
更新策略 有序更新(如从最后一个实例开始) 滚动更新/重建,无顺序
重启/重建 实例重启后需恢复原有数据/身份 实例重启后无影响

四、实验一:部署 Nginx 集群

需求:部署 3 节点 Nginx 集群,实现数据持久化、固定网络标识、有序部署。

4.1 环境准备

bash 复制代码
root@master30:~# kubectl create ns statefulset
root@master30:~# kubectl config set-context --current --namespace statefulset

4.2 创建 Headless Service

yaml 复制代码
root@master30:~# cat > nginx-service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: statefulset
spec:
  clusterIP: None
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
    name: nginx-port
EOF

root@master30:~# kubectl apply -f nginx-service.yaml
root@master30:~# kubectl get svc
NAME    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   None         <none>        80/TCP    5s

4.3 创建 StatefulSet

yaml 复制代码
root@master30:~# cat > nginx-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx
  namespace: statefulset
spec:
  serviceName: nginx
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nginx-data
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: nginx-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "nfs-storage"   # 使用上一期部署的 NFS Provisioner
      resources:
        requests:
          storage: 10Gi
EOF

root@master30:~# kubectl apply -f nginx-statefulset.yaml

4.4 观察 Pod 创建顺序

bash 复制代码
root@master30:~# kubectl get pods -w
NAME       READY   STATUS    RESTARTS   AGE
nginx-0    1/1     Running   0          10s
nginx-1    1/1     Running   0          30s
nginx-2    1/1     Running   0          50s

关键观察 :Pod 按 nginx-0nginx-1nginx-2 顺序创建,每个 Pod 间隔约 20 秒。

4.5 验证 PVC

bash 复制代码
root@master30:~# kubectl get pvc
NAME                 STATUS   VOLUME                                     CAPACITY
nginx-data-nginx-0   Bound    pvc-8eb06bf1-a5de-4e94-bf83-514941f73c9f   10Gi
nginx-data-nginx-1   Bound    pvc-e853eccd-8060-4326-8c45-0b88c34a3d97   10Gi
nginx-data-nginx-2   Bound    pvc-27d944f7-1e62-4352-9023-3874e6867ef9   10Gi

4.6 验证固定 DNS

bash 复制代码
root@master30:~# kubectl run test --image=nginx -it -- bash
root@test:/# curl nginx-0.nginx
nginx-data-nginx-0
root@test:/# curl nginx-1.nginx
nginx-data-nginx-1
root@test:/# curl nginx-2.nginx
nginx-data-nginx-2

4.7 验证数据持久化

bash 复制代码
# 删除 nginx-2
root@master30:~# kubectl delete pod nginx-2

# 等待重新创建
root@master30:~# kubectl get pods
NAME       READY   STATUS    RESTARTS   AGE
nginx-0    1/1     Running   0          10m
nginx-1    1/1     Running   0          10m
nginx-2    1/1     Running   0          8s

# 验证数据仍然存在
root@master30:~# kubectl exec test -- curl nginx-2.nginx
nginx-data-nginx-2

4.8 实验小结

  • StatefulSet 保证 Pod 按序号从 0 到 N-1 顺序创建
  • 每个 Pod 获得独立的 PVC,数据持久化
  • Pod 重建后 DNS 名称不变,数据不丢失
  • Headless Service 为每个 Pod 提供固定 DNS 解析

4.9 清理环境

bash 复制代码
root@master30:~# kubectl delete sts nginx
root@master30:~# kubectl delete svc nginx
root@master30:~# kubectl delete pvc nginx-data-nginx-{0..2}

五、实验二:部署 etcd 集群

需求:部署 3 节点 etcd 集群,实现数据持久化、固定网络标识、集群自动组网。

5.1 准备 NFS 存储

bash 复制代码
root@master30:~# mkdir -m 777 -p /shares/etcd/data-{0..2}
root@master30:~# systemctl restart nfs-server.service

5.2 创建 Headless Service

yaml 复制代码
root@master30:~# cat > etcd-service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: etcd
  namespace: statefulset
spec:
  clusterIP: None
  selector:
    app: etcd
  ports:
  - port: 2379
    name: client
  - port: 2380
    name: peer
EOF

root@master30:~# kubectl apply -f etcd-service.yaml

5.3 创建 PV(静态方式)

yaml 复制代码
root@master30:~# cat > etcd-pv.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
  name: etcd-pv-0
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: etcd-storage
  nfs:
    server: 10.1.8.30
    path: /shares/etcd/data-0
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: etcd-pv-1
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: etcd-storage
  nfs:
    server: 10.1.8.30
    path: /shares/etcd/data-1
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: etcd-pv-2
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: etcd-storage
  nfs:
    server: 10.1.8.30
    path: /shares/etcd/data-2
EOF

root@master30:~# kubectl apply -f etcd-pv.yaml

提示:生产环境建议使用动态卷供应(StorageClass),而非手动创建 PV。

5.4 创建 StatefulSet

yaml 复制代码
root@master30:~# cat > etcd-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: etcd
  namespace: statefulset
spec:
  serviceName: etcd
  replicas: 3
  selector:
    matchLabels:
      app: etcd
  template:
    metadata:
      labels:
        app: etcd
    spec:
      containers:
      - name: etcd
        image: registry.aliyuncs.com/google_containers/etcd:3.5.10-0
        command:
        - /usr/local/bin/etcd
        ports:
        - containerPort: 2379
          name: client
        - containerPort: 2380
          name: peer
        env:
        - name: ETCD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: ETCD_DATA_DIR
          value: /var/lib/etcd
        - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
          value: "http://$(ETCD_NAME).etcd.statefulset.svc.cluster.local:2380"
        - name: ETCD_LISTEN_PEER_URLS
          value: "http://0.0.0.0:2380"
        - name: ETCD_LISTEN_CLIENT_URLS
          value: "http://0.0.0.0:2379"
        - name: ETCD_ADVERTISE_CLIENT_URLS
          value: "http://$(ETCD_NAME).etcd.statefulset.svc.cluster.local:2379"
        - name: ETCD_INITIAL_CLUSTER
          value: "etcd-0=http://etcd-0.etcd.statefulset.svc.cluster.local:2380,etcd-1=http://etcd-1.etcd.statefulset.svc.cluster.local:2380,etcd-2=http://etcd-2.etcd.statefulset.svc.cluster.local:2380"
        - name: ETCD_INITIAL_CLUSTER_TOKEN
          value: "etcd-token"
        - name: ETCD_INITIAL_CLUSTER_STATE
          value: "new"
        volumeMounts:
        - name: etcd-data
          mountPath: /var/lib/etcd
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
  volumeClaimTemplates:
  - metadata:
      name: etcd-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "etcd-storage"
      resources:
        requests:
          storage: 10Gi
EOF

root@master30:~# kubectl apply -f etcd-statefulset.yaml

5.5 验证 etcd 集群状态

bash 复制代码
root@master30:~# kubectl exec etcd-0 -- etcdctl member list
8747632212a8465, started, etcd-2, http://etcd-2.etcd.statefulset.svc.cluster.local:2380, http://etcd-2.etcd.statefulset.svc.cluster.local:2379, false
881e7c876792f042, started, etcd-0, http://etcd-0.etcd.statefulset.svc.cluster.local:2380, http://etcd-0.etcd.statefulset.svc.cluster.local:2379, false
b6d311da39fc1138, started, etcd-1, http://etcd-1.etcd.statefulset.svc.cluster.local:2380, http://etcd-1.etcd.statefulset.svc.cluster.local:2379, false

5.6 验证数据持久化

bash 复制代码
# 写入数据
root@master30:~# kubectl exec etcd-0 -- etcdctl put /users/user1/name laowang

# 删除 etcd-2
root@master30:~# kubectl delete pod etcd-2

# 等待重建后验证
root@master30:~# kubectl exec etcd-2 -- etcdctl get /users/user1/name
/users/user1/name
laowang

5.7 实验小结

  • etcd 集群通过 StatefulSet 实现了固定网络标识
  • 每个 etcd 节点通过 ${ETCD_NAME} 环境变量获取自己的名称
  • ETCD_INITIAL_CLUSTER 使用固定的 DNS 名称,实现集群自动组网
  • 删除 Pod 后数据不丢失(PVC 保留)

5.8 清理环境

bash 复制代码
root@master30:~# kubectl delete sts etcd
root@master30:~# kubectl delete svc etcd
root@master30:~# kubectl delete pvc etcd-data-etcd-{0..2}
root@master30:~# kubectl delete pv etcd-pv-{0..2}

六、实验三:部署 Redis 集群(主从 + 哨兵)

需求:部署 3 节点 Redis 集群(1 主 2 从),实现固定网络标识、数据持久化、哨兵高可用。

6.1 准备 NFS 存储

bash 复制代码
root@master30:~# mkdir -m 777 -p /shares/redis/data-{0..2}
root@master30:~# systemctl restart nfs-server.service

6.2 创建 Headless Service

yaml 复制代码
root@master30:~# cat > redis-service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: statefulset
spec:
  clusterIP: None
  selector:
    app: redis
  ports:
  - port: 6379
    name: redis
  - port: 26379
    name: sentinel
EOF

root@master30:~# kubectl apply -f redis-service.yaml

6.3 创建 PV

yaml 复制代码
root@master30:~# cat > redis-pv.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv-0
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: redis-storage
  nfs:
    server: 10.1.8.30
    path: /shares/redis/data-0
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv-1
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: redis-storage
  nfs:
    server: 10.1.8.30
    path: /shares/redis/data-1
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: redis-pv-2
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: redis-storage
  nfs:
    server: 10.1.8.30
    path: /shares/redis/data-2
EOF

root@master30:~# kubectl apply -f redis-pv.yaml

6.4 创建 StatefulSet

yaml 复制代码
root@master30:~# cat > redis-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  namespace: statefulset
spec:
  serviceName: redis
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7.0-alpine
        ports:
        - containerPort: 6379
          name: redis
        - containerPort: 26379
          name: sentinel
        command: ["/bin/sh", "-c"]
        args:
        - |
          NODE_ID=$(hostname | awk -F'-' '{print $2}')
          cat > /data/redis.conf << EOF
          bind 0.0.0.0
          protected-mode no
          port 6379
          dir /data
          appendonly yes
          requirepass "redis123"
          masterauth "redis123"
          EOF
          if [ "$NODE_ID" != "0" ]; then
            echo "replicaof redis-0.redis.statefulset.svc.cluster.local 6379" >> /data/redis.conf
          fi
          redis-server /data/redis.conf &
          until nslookup redis-0.redis.statefulset.svc.cluster.local; do
            echo "Waiting for DNS resolution..."
            sleep 2
          done
          cat > /data/sentinel.conf << EOF
          bind 0.0.0.0
          protected-mode no
          port 26379
          sentinel resolve-hostnames yes
          sentinel announce-hostnames yes
          sentinel monitor mymaster redis-0.redis.statefulset.svc.cluster.local 6379 2
          sentinel auth-pass mymaster redis123
          sentinel down-after-milliseconds mymaster 5000
          sentinel failover-timeout mymaster 10000
          EOF
          redis-sentinel /data/sentinel.conf
        volumeMounts:
        - name: redis-data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "redis-storage"
      resources:
        requests:
          storage: 10Gi
EOF

root@master30:~# kubectl apply -f redis-statefulset.yaml

6.5 验证主从状态

bash 复制代码
root@master30:~# kubectl exec redis-0 -- redis-cli -a redis123 info replication
Warning: Using a password with '-a' option may not be safe.
# Replication
role:master
connected_slaves:2
slave0:ip=10.224.113.181,port=6379,state=online,lag=1
slave1:ip=10.224.19.24,port=6379,state=online,lag=1

6.6 验证哨兵状态

bash 复制代码
root@master30:~# kubectl exec redis-0 -- redis-cli -p 26379 sentinel master mymaster
 1) "name"
 2) "mymaster"
 3) "ip"
 4) "redis-0.redis.statefulset.svc.cluster.local"
 5) "port"
 6) "6379"

6.7 实验小结

  • 使用 hostname | awk -F'-' '{print $2}' 获取 Pod 序号,决定主从角色
  • redis-0 固定为主节点,其他节点为从节点
  • 哨兵配置 sentinel monitor mymaster redis-0.redis.statefulset.svc.cluster.local 6379 2 监控主节点
  • sentinel resolve-hostnames yes 启用 hostname 解析,确保哨兵能通过 DNS 名称找到主节点

6.8 清理环境

bash 复制代码
root@master30:~# kubectl delete sts redis
root@master30:~# kubectl delete svc redis
root@master30:~# kubectl delete pvc redis-data-redis-{0..2}
root@master30:~# kubectl delete pv redis-pv-{0..2}

七、实验四:部署 MySQL 集群(主从同步)

需求:部署 2 节点 MySQL 集群(1 主 1 从),实现数据持久化、固定网络标识、主从自动同步。

7.1 准备 NFS 存储

bash 复制代码
root@master30:~# mkdir -m 777 -p /shares/mysql/data-{0,1}
root@master30:~# systemctl restart nfs-server.service

7.2 创建 Headless Service

yaml 复制代码
root@master30:~# cat > mysql-service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: statefulset
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
  - port: 3306
    targetPort: 3306
    name: mysql-port
EOF

root@master30:~# kubectl apply -f mysql-service.yaml

7.3 创建 PV

yaml 复制代码
root@master30:~# cat > mysql-pv.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv-0
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: mysql-storage
  nfs:
    server: 10.1.8.30
    path: /shares/mysql/data-0
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv-1
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: mysql-storage
  nfs:
    server: 10.1.8.30
    path: /shares/mysql/data-1
EOF

root@master30:~# kubectl apply -f mysql-pv.yaml

7.4 创建 StatefulSet

yaml 复制代码
root@master30:~# cat > mysql-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: statefulset
spec:
  serviceName: mysql
  replicas: 2
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        ports:
        - containerPort: 3306
          name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "123456"
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "mysql-storage"
      resources:
        requests:
          storage: 10Gi
EOF

root@master30:~# kubectl apply -f mysql-statefulset.yaml

7.5 验证 Pod 域名解析

bash 复制代码
root@master30:~# kubectl run test-pod --image=busybox -- sleep 3600
root@master30:~# kubectl exec test-pod -- ping -c2 mysql-1.mysql
PING mysql-1.mysql (10.224.113.148): 56 data bytes
64 bytes from 10.224.113.148: seq=0 ttl=62 time=1.586 ms

7.6 验证数据持久化

bash 复制代码
# 创建数据库
root@master30:~# kubectl exec -it mysql-1 -- mysql -u root -p123456 -e 'create database wordpress;'

# 删除 mysql-1
root@master30:~# kubectl delete pod mysql-1

# 等待重建后验证数据
root@master30:~# kubectl exec -it mysql-1 -- mysql -u root -p123456 -e 'show databases;'
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| wordpress          |
+--------------------+

7.7 实验小结

  • MySQL 主从同步需要额外配置,本实验主要演示 StatefulSet 的数据持久化能力
  • 删除 Pod 后数据不丢失,验证了 PVC 的持久化效果
  • 固定 DNS 名称使主从配置更为稳定

7.8 清理环境

bash 复制代码
root@master30:~# kubectl delete sts mysql
root@master30:~# kubectl delete svc mysql
root@master30:~# kubectl delete pvc mysql-data-mysql-{0..1}
root@master30:~# kubectl delete pv mysql-pv-{0..1}

八、StatefulSet 更新策略

8.1 更新策略类型

策略 行为 适用场景
RollingUpdate(默认) 从最后一个序号开始逆序滚动更新 大多数场景
OnDelete 只有手动删除 Pod 时才更新 需要完全控制更新节奏

8.2 配置更新策略

yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2   # 只更新序号 >= 2 的 Pod

partition 参数

  • 用于金丝雀发布或灰度更新
  • 序号 >= partition 的 Pod 会更新
  • 序号 < partition 的 Pod 保持不变

九、StatefulSet Pod 管理

9.1 Pod 替换

bash 复制代码
# 删除指定 Pod(会触发重建,且数据保留)
kubectl delete pod <pod-name>

9.2 获取 Pod 状态

bash 复制代码
kubectl get sts
kubectl get pods -l app=<label>
kubectl describe sts <name>

9.3 扩缩容

bash 复制代码
# 扩容
kubectl scale sts <name> --replicas=5

# 缩容(从最大序号开始删除)
kubectl scale sts <name> --replicas=2

十、总结与知识点一览表

10.1 StatefulSet 核心特性总结

特性 说明 关键配置
固定身份 每个 Pod 有唯一序号和固定 hostname Headless Service + serviceName
有序操作 创建/更新按顺序,缩容逆序 由 StatefulSet 控制器自动保证
稳定存储 每个 Pod 独立 PVC,删除后保留 volumeClaimTemplates
固定 DNS Pod 可通过 <name>.<service>.<ns>.svc.cluster.local 访问 Headless Service

10.2 StatefulSet vs Deployment 对比

控制器 StatefulSet Deployment
适用场景 有状态应用 无状态应用
网络 Headless Service(固定 DNS) ClusterIP/LoadBalancer(共享 IP)
存储 必须 PVC + PV 可选 PVC
扩缩容 有序(0→1→2) 无序
更新策略 有序更新(逆序) 滚动更新

10.3 各实验对比

实验 应用 Pod 数量 主要特点
实验一 Nginx 3 演示基础 StatefulSet 特性
实验二 etcd 3 集群自动组网,固定 DNS
实验三 Redis 3 主从 + 哨兵高可用
实验四 MySQL 2 数据持久化验证

10.4 常用命令速查

操作 命令
创建 StatefulSet kubectl apply -f sts.yaml
查看 StatefulSet kubectl get sts
查看 StatefulSet 详情 kubectl describe sts <name>
查看 Pod 顺序 kubectl get pods -w
查看 PVC kubectl get pvc
扩缩容 StatefulSet kubectl scale sts <name> --replicas=<n>
删除 StatefulSet kubectl delete sts <name>

10.5 常见错误排查

错误 原因 解决方法
Pod 一直 Pending PVC 无法绑定或节点资源不足 检查 PV 状态和节点资源
创建顺序异常 Headless Service 未正确配置 确认 clusterIP: None
数据丢失 PVC 被误删 使用 Retain 回收策略
DNS 解析失败 Service 名称不匹配 确认 serviceName 与 Service 名称一致
主从同步失败 网络不通或配置错误 检查 Pod 间网络连通性

10.6 生产环境最佳实践

实践 说明
使用动态卷供应 避免手动创建 PV,使用 StorageClass 自动管理
设置资源限制 为有状态应用配置合理的 CPU/内存请求和限制
合理设置副本数 生产环境至少 3 节点(满足 etcd/Redis 多数派要求)
定期备份数据 有状态应用的数据是关键资产,需定期备份
使用 Retain 回收策略 防止误删 PVC 导致数据丢失
配置 PodDisruptionBudget 保护有状态应用不被同时驱逐

笔记完结说明:至此,Kubernetes 系列笔记(第一期至第十四期)已全部完成,覆盖从基础架构、集群部署、容器管理、网络策略、调度管理、资源管理、存储管理到有状态应用的全链路内容。建议读者按照"先理解架构 → 动手部署集群 → 掌握日常运维 → 进阶存储与有状态应用"的路径循序渐进地学习。如有后续进阶内容(如服务网格 Istio、Kubernetes 安全策略、GitOps 持续部署等),可根据需要继续扩展。感谢学习!
--- Compiled and Authored by Whisky --- July 4 th, 2026

相关推荐
来生硬件工程师1 小时前
【硬件笔记】DCDC电源设计—BUCK电路设计要点
笔记·单片机·嵌入式硬件·硬件工程·智能硬件
EntyIU1 小时前
CentOS-高可用部署手册-MySQL双主RedisNginx
linux·mysql·centos
nongcunqq2 小时前
编辑 cookie 的插件
笔记
艾文伯特2 小时前
k8s-1.35-ubuntu-安装文档.md
ubuntu·容器·kubernetes
AOwhisky2 小时前
kubernetes(K8s)学习笔记:第八期与第九期核心知识点自测与详解
笔记·云原生·kubernetes·云计算·k8s·集群·网络策略
凉、介2 小时前
KVM + QEMU 虚拟化
笔记·学习·嵌入式·arm·qemu·虚拟化·kvm
条纹布鲁斯2 小时前
ubuntu 26.04 k8s 1.36 ceph
kubernetes
爱吃龙利鱼2 小时前
k8s指定命名空间kubeconfig文件生成教程
容器·kubernetes
承渊政道2 小时前
【MySQL数据库学习】(MySQL访问、连接池原理与简易网站数据流动)
数据库·学习·mysql·mysql访问·连接池原理