Kubernetes(K8s)笔记Day07: StatefulSet 有状态控制器详解与使用案例

一. StatefulSet 控制器:概念、原理解读

StatefulSet(有状态副本集) 是为了管理有状态服务而设计的 Kubernetes 原生控制器。

好的,我帮你把对比部分全部去掉,只保留各自独立的理论定义,做到精炼、干净、无废话


1.1 有状态服务

定义:服务端会"记住"客户端的状态或产生的数据,不同请求之间相互关联。

核心特征

  • 数据持久化 :数据需保存到持久化存储 (如 PVC),且必须使用单独存储,Pod 重启或重新调度后数据依然存在。
  • 稳定身份标识 :每个实例拥有固定的、稳定的标识符(如 Pod 名称、DNS 记录),该身份标识持久不变,即使 Pod 重建也不会改变。
  • 实例间有依赖 :实例之间存在启动/停止的顺序依赖(如数据库主从),启动时按顺序,停止时逆序

典型代表:MySQL、Redis、Kafka、Elasticsearch

Kubernetes 管理方式StatefulSet

1.2 无状态服务

定义:服务端不保存任何客户端状态或会话数据,每个请求都是独立的,包含处理所需的所有信息。

核心特征

  • 无本地数据存储:数据不在本地持久化,可随时丢失;需要存储时通常依赖外部系统(如数据库、缓存、对象存储)。
  • 实例可互换:所有实例完全对等,任何一个实例被销毁或重建,都不会影响服务整体功能。
  • 实例间无依赖:实例之间没有任何关联,可随意并行启动、停止、扩缩容。

典型代表:Nginx、API 网关、微服务后端

Kubernetes 管理方式Deployment / ReplicaSet


有状态服务 = 有身份 + 存数据 + 有序启动;无状态服务 = 可互换 + 不存数据 + 随意扩缩。

1.4 FQDN(完全限定域名)

FQDN = Fully Qualified Domain Name,即全限定域名,是同时带有主机名和域名的完整名称。

FQDN = Hostname + DomainName

示例:

主机名:www

域名:baidu.com

FQDN:www.baidu.com

Kubernetes 中资源的全局 FQDN 格式:

格式:

<service-name>.<namespace-name>.svc.cluster.local.

  • svc:固定字段,代表这是一个 Service 类型的资源
  • cluster.local:Kubernetes 集群的默认本地域名
  • 末尾的 . 代表 DNS 根域,可省略

StatefulSet 中 Pod 的 DNS 名称格式:

StatefulSet 中的每个 Pod 都会被分配一个唯一的、稳定的 DNS 名称 ,当你在集群内解析这个 DNS 名称时,它会直接解析到该 Pod 当前的 IP 地址

格式:

<pod-name>.<service-name>.<namespace-name>.svc.cluster.local

例如:web-0.nginx.default.svc.cluster.local



1.5 无状态使用 Deployment + 共享存储案例

Deployment 管理的无状态 Pod,天然适合"共享同一份数据"。

1. 创建存储卷声明(PVC):

bash 复制代码
[root@hd1 ~]# cat mypvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-volume-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: nfs  #这里使用的是前一章节我们创建的nfs存储类
  resources:
    requests:
      storage: 1Gi

2. 在 Deployment 的 Pod 模板中挂载 PVC:

yaml 复制代码
[root@hd1 ~]# cat mynginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: docker.io/library/nginx:latest
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: shared-volume
              mountPath: /usr/share/nginx/html/
      volumes:
        - name: shared-volume
          persistentVolumeClaim:
            claimName: shared-volume-claim

3. 应用 YAML 文件:

bash 复制代码
[root@hd1 k8s]# kubectl apply -f mypvc.yaml
[root@hd1 k8s]# kubectl apply -f mynginx.yaml

4. 查看 PVC:

bash 复制代码
[root@hd1 k8s]# kubectl get pvc | grep shared
shared-volume-claim   Bound    pvc-6c584a5a-85c5-4409-8009-0f6bba2d6c1c ......

5. 在 NFS 共享目录中创建测试文件:

bash 复制代码
[root@hd1 k8s]# ls /data/nfs_pro
#查看与我们pvc对应的路径目录,那个就是我们挂载的共享目录
[root@hd1 ~]# cd /data/nfs_pro/default-shared-volume-claim-pvc-6c584a5a-85c5-4409-8009-0f6bba2d6c1c/
[root@hd1 default-shared-volume-claim-pvc-6c584a5a-85c5-4409-8009-0f6bba2d6c1c]# echo abcd > index.html

6. 查看两个 Pod 的 IP 地址:

bash 复制代码
[root@hd1 ~]#  kubectl get pod -o wide | grep myapp
myapp-deployment-557fc5bc9b-82gs8   1/1     Running   0               5m13s   10.244.169.88   hd3    <none>           <none>
myapp-deployment-557fc5bc9b-rqjdt   1/1     Running   0               5m13s   10.244.59.164   hd2    <none>           <none>

7. 访问共享网页目录:

两个 Pod 都可以访问到 NFS 共享目录中的 index.html 文件,验证了共享存储的效果。

bash 复制代码
[root@hd1 ~]# curl 10.244.169.88
abcd
[root@hd1 ~]# curl 10.244.59.164 
abcd

二. StatefulSet 资源清单文件

StatefulSet 是 Kubernetes 专门用来解决有状态应用部署难题的控制器 ,它的核心作用是让 Pod 拥有固定的身份和独立的存储,并保证它们有序的启动和停止

如果我们把它和RS,deployment进行对比,可以发现:RS、Deployment、StatefulSet 在语法和配置上非常相似

因为它们都属于"工作负载控制器(Workload Controllers) ",核心职责都是"管理 Pod 的生命周期",它们的 YAML 骨架共享了这三个核心字段:

  • replicas(要几个副本?)
  • selector(管哪些 Pod?)
  • template(怎么造 Pod?)

如此一来,理解起来就非常简单了,StatefulSet 是与 Deployment 并列的,有状态的专属控制器

2.1 StatefulSet 的三大组成部分

  • Headless Service(无头服务)

    • 不分配 ClusterIP
    • 为每个 Pod 生成唯一的 DNS 记录
    • 格式:<pod-name>.<service-name>.<namespace>.svc.cluster.local
  • volumeClaimTemplates(存储卷申请模板)

    • 自动为每个 Pod 生成独立的 PVC
    • PVC 命名格式:<模板名称>-<statefulset名称>-<序号>
    • 每个 Pod 绑定独立的 PV,数据隔离
  • StatefulSet 控制器

    • 管理 Pod 的创建、扩缩容、更新
    • 保证 Pod 名称有序(web-0, web-1, web-2...)
    • 保证启动/停止顺序

2.1.1 Headless Service 无头服务

1. 什么是 Headless Service?

Headless Service 是一种不分配 ClusterIP 的 Service ,其 clusterIP 字段被显式设置为 None

2. 为什么需要 Headless Service?

普通的service域名在解析时会返回 service 的 ClusterIP(虚拟 IP),而Headless Service 不分配 ClusterIP(虚拟 IP) ,DNS 解析将直接返回所有匹配该 Service 且处于就绪状态的 Pod IP 列表 ,而非 Service IP。

同时,当 Headless Service 与 StatefulSet 配合使用时,StatefulSet 会为每个 Pod 创建一个独立的 DNS 记录,格式为:

<pod-name>.<service-name>.<namespace>.svc.cluster.local

该记录始终解析到该 Pod 的当前 IP,相当于为每个 Pod 赋予了固定的、不变的身份标识

3. Headless Service 的核心功能

Headless Service 会为 Service 分配一个域名:

<service-name>.<namespace-name>.svc.cluster.local

这个域名可用解析出所有就绪 Pod 的 IP 列表

StatefulSet 会为每个 Pod 分配一个 固定且唯一可解析的 DNS 名称:

<pod-name>.<service-name>.<namespace-name>.svc.cluster.local

这个dns名称可以单独指向这个pod

4. 普通 Service vs Headless Service 对比:
对比项 普通 Service Headless Service
ClusterIP 无(clusterIP: None
DNS 域名解析结果 返回 Service 的 ClusterIP 返回所有 Pod 的 IP 地址列表
负载均衡 由 kube-proxy 实现 DNS 轮询(客户端自行选择)
适用场景 无状态服务 有状态服务(StatefulSet)

2.1.2 volumeClaimTemplates(存储卷申请模板)

volumeClaimTemplates(存储卷申请模板)是定义在 StatefulSet 的 YAML 文件中的,它位于 spec 字段下,与 replicas、selector、template 是平级的关系。

volumeClaimTemplates 的作用:

为 StatefulSet 中的每一个 Pod,自动申请一个专属的 PVC 。它用"模板化"的方式解决了有状态服务每个实例必须拥有独立持久化存储的刚性需求,并且默认保留数据,防止误删

stsPod、PVC 和 PV 的命名关系
text 复制代码
web-0  →  PVC: www-web-0  →  PV: pvc-xxx-0
web-1  →  PVC: www-web-1  →  PV: pvc-xxx-1
web-2  →  PVC: www-web-2  →  PV: pvc-xxx-2

值得注意的是:

StatefulSet 创建的 Pod :名称格式固定为 <statefulset名称>-<序号>,所以你看到的是 web-0 和 web-1(按顺序递增,且重建后名字不变)

PVC 命名格式<模板名称>-<statefulset名称>-<序号>

PV 命名格式 :pvc-<PVC的UID>

Deployment 创建的 Pod :名称格式为 <deployment名称>-<模板哈希值>-<随机字符串>,例如 myapp-deployment-557fc5bc9b-4kzkb(名字里带有一长串随机后缀,每次重建都会变)

2.1.3 StatefulSet 控制器

StatefulSet 控制器 是 Kubernetes 中专门用于管理有状态应用的核心控制逻辑。它通过协调 API Server 与底层容器运行时,确保 StatefulSet 中定义的每个 Pod 都按照预定义的顺序、命名规则和存储绑定关系进行部署和维护。

核心职责
职责 说明
Pod 生命周期管理 负责 Pod 的创建、更新、删除和重建,确保始终与 YAML 中声明的状态一致
有序命名保证 Pod 名称严格按照 /<statefulset-name>-<序号> 格式生成(如 web-0web-1),重建后名称不变
顺序控制 扩容/启动 时按序号从小到大依次创建(0→1→2...);缩容/删除时按序号从大到小逆序删除(2→1→0...)
存储绑定保障 volumeClaimTemplates 协同工作,确保每个 Pod 重建后能自动绑定回其原有的 PVC
滚动更新管理 支持 RollingUpdate(默认)和 OnDelete 两种更新策略,更新时按逆序逐个替换 Pod
状态持久化 即使 Pod 被驱逐或节点故障,控制器也会尝试重新调度并恢复其身份与存储
关键特性详解
  1. 稳定的网络标识

    • Pod 名称固定,配合 Headless Service 生成不变的 DNS 记录
    • 其他服务可通过 <pod-name>.<service-name>.<namespace>.svc.cluster.local 精确访问特定 Pod
  2. 独立的持久化存储

    • 通过 volumeClaimTemplates 为每个序号创建独立的 PVC
    • Pod 重建后自动挂载回原来的 PVC,保证数据不丢失
  3. 有序的部署与扩展

    • 扩容时必须等待前一个 Pod 处于 RunningReady 状态后才创建下一个
    • 缩容时则按相反顺序逐个删除,保证集群的稳定性
  4. 可配置的更新策略

    • RollingUpdate:自动逆序滚动更新,可配置 partition 控制更新范围
    • OnDelete:仅当手动删除 Pod 时才触发更新,适用于精细控制
StatefulSet 完整配置示例
yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web                        # StatefulSet 名称,Pod 名称前缀为 "web-"
  namespace: default               # 命名空间
  labels:
    app: nginx                     # StatefulSet 自身的标签
spec:
  # -------- 副本数与选择器 --------
  replicas: 2                      # 期望的 Pod 副本数,会创建 web-0 和 web-1
  selector:                        #标签选择器
    matchLabels:
      app: nginx                   # ⚠️ 必须与 template.metadata.labels 匹配,否则 K8s 拒绝创建

  # -------- 关联 Headless Service --------
  serviceName: "nginx"             # ⚠️ 必须与已存在的 Headless Service 名称一致
                                   # 用于为每个 Pod 生成固定 DNS 记录
                                   # DNS 格式:<pod-name>.<service-name>.<namespace>.svc.cluster.local

  # -------- Pod 模板 --------
  template:
    metadata:
      labels:
        app: nginx                 # Pod 标签,必须被 selector 匹配
    spec:
      containers:
        - name: nginx
          image: docker.io/library/nginx:latest
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: www            # 引用 volumeClaimTemplates 中定义的存储卷名称
              mountPath: /usr/share/nginx/html
                                   # 挂载到 Nginx 网站根目录
                                   # ⚠️ 挂载后该目录原有内容会被卷内容覆盖

  # -------- 存储卷申请模板(StatefulSet 特有) --------
  volumeClaimTemplates:
    - metadata:
        name: www                  # PVC 名称前缀
                                   # ⚠️ 实际 PVC 名称:www-<statefulset名称>-<序号>
                                   # 例如:www-web-0、www-web-1
      spec:                        #这里指pvc的规格
        accessModes: ["ReadWriteOnce"]     # 访问模式:单节点读写
        storageClassName: "nfs-web"   # 关联 StorageClass,启用动态存储供应
        resources:
          requests:
            storage: 1Gi           # 每个 Pod 独立申请 1Gi

  # -------- 更新策略(可选) --------
  updateStrategy:
    type: RollingUpdate            # 可选值:RollingUpdate(默认)、OnDelete
    rollingUpdate:
      partition: 0                 # 仅更新序号 >= partition 的 Pod
                                   # 例如设置为 2,则只更新 web-2、web-3...
                                   # 用于金丝雀发布或灰度验证

  # -------- Pod 管理策略(可选) --------
  podManagementPolicy: OrderedReady
                                   # 可选值:OrderedReady(默认)、Parallel
                                   # OrderedReady:严格按顺序启动/停止
                                   # Parallel:并行操作,不保证顺序

  # -------- 历史版本保留数(可选) --------
  revisionHistoryLimit: 10         # 保留的滚动更新历史版本数,默认 10
StatefulSet 关键字段
字段 类型 必填 说明
replicas integer 副本数,默认为 1
selector Object 标签选择器,必须匹配 template.metadata.labels
serviceName string 关联的 Headless Service 名称(必须已存在)
template Object Pod 模板,定义容器、挂载点等
volumeClaimTemplates \[\]Object 存储卷申请模板,为每个 Pod 自动生成独立 PVC
updateStrategy.type string RollingUpdate(默认)或 OnDelete
updateStrategy.rollingUpdate.partition integer 按序号分区更新,仅更新 >= partition 的 Pod
podManagementPolicy string OrderedReady(默认)或 Parallel
revisionHistoryLimit integer 保留的历史版本数,默认 10
扩缩容与更新行为总结
操作 行为
扩容(replicas 增加) 按序号从小到大依次创建:web-0web-1web-2
缩容(replicas 减少) 按序号从大到小逆序删除:web-2web-1web-0
滚动更新(修改镜像等) 按序号从大到小逆序更新:先更新 web-2,再 web-1,最后 web-0
Pod 重建(故障恢复) 保留原名称(如 web-0),并自动挂载回原有的 PVC(www-web-0

设计理念:StatefulSet 通过这些机制,让有状态应用在 Kubernetes 这种"临时性"容器环境中,依然能像在物理机或虚拟机上一样,拥有稳定的身份、独立的数据存储和可控的启动顺序。

2.2 StatefulSet 的资源定义字段

我们了解了 StatefulSet 的三大组成部分和完整示例,但在实际编写 YAML 时,我们还需要精确知道每个字段的含义,接下来我们通过 kubectl explain 来梳理这些字段

StatefulSet 资源定义字段

字段路径 类型 必填 说明
apiVersion string API 版本,固定为 apps/v1
kind string 资源类型,固定为 StatefulSet
metadata Object 元数据(名称、命名空间、标签等)
spec Object StatefulSet 规格定义(核心配置)

StatefulSet.spec 核心字段

字段 类型 必填 说明
replicas integer 副本数,默认 1
selector Object 标签选择器,必须匹配 .spec.template.metadata.labels
serviceName string 关联的 Headless Service 名称
template Object Pod 模板,定义 Pod 的容器、标签等
volumeClaimTemplates \[\]Object 存储卷申请模板,为每个 Pod 自动生成独立 PVC
updateStrategy Object 更新策略(RollingUpdate / OnDelete
podManagementPolicy string Pod 管理策略(OrderedReady / Parallel
revisionHistoryLimit integer 保留的历史版本数,默认 10

StatefulSet.spec.template 核心字段

bash 复制代码
[root@hd1 ~]# kubectl explain statefulset.spec.template

FIELDS:
  metadata    <Object>    # Pod 的元数据
  spec        <Object>    # 定义容器属性的(PodSpec)

注意 :StatefulSet 资源中有两个 spec 字段:

  • 第一个 spec:声明 StatefulSet 的副本数、标签选择器、Pod 模板、存储卷申请模板
  • 第二个 spec.template.spec:主要用于 Pod 里容器的属性配置

重要约束 :在 .spec.selector 中定义的标签选择器必须能够匹配 .spec.template.metadata.labels 中定义的 Pod 标签,否则 Kubernetes 将不允许创建 StatefulSet。


三. StatefulSet 使用案例:部署 Web 站点

接下来我们通过一个实验来熟悉 StatefulSet 的部署流程

同时本实验作为第一节实验:" Deployment + NFS 共享存储案例 " 的对照组,用最直观的方式去感受 无状态 与 有状态 在存储隔离层面的本质区别

步骤1:创建 StorageClass

yaml 复制代码
[root@hd1 ~]# cat class-web.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-web
provisioner: example.com/nfs    #供应商名称

注意,本次使用的供应商依旧是昨天实验部署的NFS Provisioner,可以移步昨天的文档,第七章节,步骤5:部署 NFS Provisioner

如果已经清除掉的,按照之前的步骤再搭建供应商即可,本次实验SC是新建的。(或者直接复用昨天的sc也可以)

bash 复制代码
[root@hd1 ~]# kubectl apply -f class-web.yaml
#查看sc
[root@hd1 ~]# kubectl get sc
NAME      PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs       example.com/nfs   Delete          Immediate           false                  9h
nfs-web   example.com/nfs   Delete          Immediate           false                  12s

步骤2:编写 StatefulSet + Headless Service 资源清单

yaml 复制代码
[root@hd1 ~]# cat statefulset.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx                   #Service 名称(必须与 StatefulSet 的 serviceName 保持一致)
  labels:
    app: nginx
spec:
  ports:
    - port: 80
      name: web
  clusterIP: None                     # 创建 Headless Service(无头服务),不会分配 ClusterIP(虚拟 IP),
  selector:                      #标签选择器,这里的标签必须与 StatefulSet Pod 模板中的 labels 保持一致
    app: nginx             
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web                     # StatefulSet 名称,也是 Pod 名称的前缀"web-"
spec:
  selector:                     #StatefulSet的标签选择器
    matchLabels:
      app: nginx                #这个标签必须与 template.metadata.labels 保持一致
  serviceName: "nginx"           # 关联 Headless Service,必须与上方 Service 的 metadata.name 保持一致
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx             #pod的标签,必须与 selector.matchLabels 保持一致,确保 Service 能关联到这些 Pod
    spec:
      containers:
        - name: nginx
          image: docker.io/library/nginx:latest
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: www              #挂载的卷名,必须与 volumeClaimTemplates 中的 metadata.name 保持一致
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:               # 存储卷申请模板,为每个 Pod 自动生成独立的 PVC
    - metadata:
        name: www                    #PVC 名称前缀必须与 volumeMounts 中的 name 保持一致
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "nfs-web"  #指向集群中已存在的 StorageClass(我们之前定义的),必须与 StorageClass 的 metadata.name 保持一致
        resources:
          requests:
            storage: 1Gi

步骤3:更新资源清单文件

bash 复制代码
[root@hd1 ~]# kubectl apply -f statefulset.yaml
service/nginx created
statefulset.apps/web created

步骤4:验证部署结果

查看 StatefulSet:

bash 复制代码
[root@hd1 ~]# kubectl get sts -o wide
NAME   READY   AGE    CONTAINERS   IMAGES
web    2/2     117s   nginx        docker.io/library/nginx:latest

查看 Pod(验证有序命名):

bash 复制代码
[root@hd1 ~]# kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          2m48s
web-1   1/1     Running   0          2m44s

注意 :sts创建的 Pod 名称是有序的(web-0web-1),而非随机字符串。

查看 Headless Service:

bash 复制代码
[root@hd1 ~]# kubectl get svc -l app=nginx
NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)    AGE
nginx                 ClusterIP      None            <none>          80/TCP     39s

查看 PVC(验证独立存储):

bash 复制代码
[root@hd1 ~]# kubectl get pvc
NAME                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
shared-volume-claim   Bound    pvc-6c584a5a-85c5-4409-8009-0f6bba2d6c1c   1Gi        RWX            nfs            3h
www-web-0             Bound    pvc-dfd07a04-f6e2-444d-8f8f-4f2618f42c14   1Gi        RWO            nfs-web        16m
www-web-1             Bound    pvc-83b42579-dcd4-49ff-a1c9-976cb2f3d5b9   1Gi        RWO            nfs-web        15m

查看 PV:

bash 复制代码
[root@hd1 statefulset]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   AGE
pvc-39a9755f-3248-49ff-8f9e-5b068b609c8f   1Gi        RWO,RWX        Delete           Bound    default/www-web-0    nfs-web        8m3s
pvc-be93d4a3-1aca-44cc-802f-ddeb38c05018   1Gi        RWO,RWX        Delete           Bound    default/www-web-1    nfs-web        7m59s

查看 Pod 主机名:

bash 复制代码
[root@hd1 ~]# for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
web-0
web-1

DNS 解析验证

有状态服务的另一个关键特征是每个 Pod 拥有固定的 DNS 名称。通过解析 Pod 的 DNS 记录,可以验证 StatefulSet + Headless Service 是否成功为每个 Pod 分配了独立的网络标识。

安装 DNS 工具:

bash 复制代码
[root@hd1 ~]# kubectl exec -it web-1 -- /bin/bash
#更新 Debian/Ubuntu 系统的软件包索引,让后面的安装能找到最新的 dnsutils 版本
root@web-1:/# apt-get update
root@web-1:/# apt-get install dnsutils -y

解析 Pod DNS 记录:

bash 复制代码
root@web-1:/# nslookup web-1.nginx
;; Got recursion not available from 10.96.0.10
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-1.nginx.default.svc.cluster.local
Address: 10.244.169.90      # 解析的是 Pod 的 IP 地址
;; Got recursion not available from 10.96.0.10  

解析 Service DNS 记录(返回所有 Pod IP):

bash 复制代码
root@web-1:/# nslookup nginx.default.svc.cluster.local
;; Got recursion not available from 10.96.0.10
;; Got recursion not available from 10.96.0.10
;; Got recursion not available from 10.96.0.10
;; Got recursion not available from 10.96.0.10
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx.default.svc.cluster.local
Address: 10.244.59.165
Name:   nginx.default.svc.cluster.local
Address: 10.244.169.90
;; Got recursion not available from 10.96.0.10    # 返回了所有 Pod 的 IP 地址

使用 dig 工具查询:

bash 复制代码
root@web-1:/# dig -t A nginx.default.svc.cluster.local @10.96.0.10
......
nginx.default.svc.cluster.local. 30 IN  A       10.244.59.165
nginx.default.svc.cluster.local. 30 IN  A       10.244.169.90
......

dig 命令格式说明:

text 复制代码
dig -t A nginx.default.svc.cluster.local @10.96.0.10

- -t A:指定查询类型为 A 记录(域名 → IP 地址)
- @10.96.0.10:指定 DNS 服务器地址

四. StatefulSet 管理 Pod:扩容、缩容、更新

动态扩容

方法一:修改配置文件后 apply

修改 statefulset.yaml 中的 replicas: 2replicas: 3

bash 复制代码
[root@hd1 ~]# kubectl apply -f statefulset.yaml
service/nginx unchanged
statefulset.apps/web configured

[root@hd1 ~]# kubectl get sts
NAME   READY   AGE
web    3/3     30m

[root@hd1 ~]# kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          29m
web-1   1/1     Running   0          29m
web-2   1/1     Running   0          8s    # 新 Pod 按序号递增创建

方法二:直接编辑控制器(实时生效)

bash 复制代码
[root@hd1 ~]# kubectl edit sts web
# 修改 spec.replicas 的值,保存退出后立即生效

动态缩容

修改 statefulset.yaml 中的 replicas: 3replicas: 2

bash 复制代码
[root@hd1 ~]# kubectl apply -f statefulset.yaml
service/nginx unchanged
statefulset.apps/web configured

[root@hd1 ~]# kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          32m
web-1   1/1     Running   0          31m

注意 :缩容时 Pod 会从最大序号开始逆序删除(先删 web-3,再删 web-2),保证有状态服务的稳定性。

更新(镜像升级)

bash 复制代码
[root@hd1 ~]# kubectl edit sts web
# 修改镜像:image: nginx → image: busybox:1.28
# 保存退出后自动触发滚动更新

遗憾的是,我们使用 busybox 演示镜像更新会导致失败,因为这个镜像没有前台命令,会导致pod被不断的重启。

但是这个方式是可行的,因为我们改回nginx镜像之后就恢复正常了,这恰恰从另一方面验证了此方法的正确性(详见下文踩坑记录

更新策略说明 :StatefulSet 默认使用 滚动更新(RollingUpdate) 策略,按 Pod 序号从大到小逆序更新(先更新 web-1,再更新 web-0)。

踩坑记录

由于没有观测到镜像的更改,所以我决定删除sts pod,让它重建(虽然不小心删掉了供应商,但及时重启了)

bash 复制代码
[root@hd1 ~]# kubectl delete pod --all
pod "nfs-provisioner-9877d685d-89qnv" deleted
pod "web-0" deleted
pod "web-1" deleted
#重启供应商
[root@hd1 ~]# kubectl apply -f nfs-deployment.yaml
deployment.apps/nfs-provisioner unchanged

#但是sts的pod一直没有正常启动
[root@hd1 ~]# kubectl get pod
NAME                              READY   STATUS             RESTARTS      AGE
nfs-provisioner-9877d685d-z9gcg   1/1     Running            0             30s
web-0                             0/1     CrashLoopBackOff   2 (14s ago)   30s

选择查看web-0的详细信息

bash 复制代码
[root@hd1 ~]# kubectl describe pod web-0
Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  2m46s                 default-scheduler  Successfully assigned default/web-0 to hd2
  Normal   Pulled     80s (x5 over 2m46s)   kubelet            Container image "busybox:1.28" already present on machine
  Normal   Created    80s (x5 over 2m46s)   kubelet            Created container nginx
  Normal   Started    80s (x5 over 2m46s)   kubelet            Started container nginx
  Warning  BackOff    54s (x10 over 2m43s)  kubelet            Back-off restarting failed container nginx in pod web-0_default(91c1bc23-b9d4-4cce-99cf-076e3179d5d5)

找到根本原因了! Pod 崩溃是因为 StatefulSet 的 Pod 模板里使用的镜像不是 Nginx,而是 busybox:1.28,但 busybox 容器启动后没有常驻前台进程,立刻退出了,导致 Kubernetes 不断重启它

我们使用edit立刻编辑修复,改回nginx

bash 复制代码
[root@hd1 ~]# kubectl edit sts web
#删除卡死的pod
[root@hd1 ~]# kubectl delete pod web-0
#查看
[root@hd1 ~]# kubectl get pods 
NAME                              READY   STATUS    RESTARTS   AGE
nfs-provisioner-9877d685d-z9gcg   1/1     Running   0          37m
web-0                             1/1     Running   0          31m
web-1                             1/1     Running   0          31m

问题解决

五. StatefulSet 部署有状态服务案例:MySQL

本节通过两个简化的 MySQL 部署示例,对比 DeploymentStatefulSet 在管理有状态应用时的核心差异,帮助理解 StatefulSet 的设计意图。

⚠️ 以下案例仅供理解概念使用,并非生产级部署方案。 生产环境部署数据库请参考官方 Operator 方案(如 Percona XtraDB Cluster Operator)或云厂商托管数据库服务。

案例1:使用 Deployment 部署 MySQL

yaml 复制代码
# deploy_mysql.yaml
apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs
  resources:
    requests:
      storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
        - image: mysql:8.0
          imagePullPolicy: IfNotPresent
          name: mysql
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: "123456"
          ports:
            - containerPort: 3306
              name: mysql
          volumeMounts:
            - name: mysql-persistent-storage
              mountPath: /var/lib/mysql
      volumes:
        - name: mysql-persistent-storage
          persistentVolumeClaim:
            claimName: mysql-pv-claim
  • 使用 Deployment 管理 MySQL,多个 Pod 共享同一个 PVC。
  • 体现了 Deployment 在**存储层面是"共享"**的------适合数据完全一致的场景(如多个只读从库共用同一份数据),但无法保证 Pod 身份的稳定性,不适合主库或需要独立存储的场景。

案例2:使用 StatefulSet 部署多个独立的 MySQL

yaml 复制代码
# sts_mysql.yaml
apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql100
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  serviceName: "wordpress-mysql100"
  replicas: 2
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
        - image: mysql:8.0
          imagePullPolicy: IfNotPresent
          name: mysql
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: "123456"
          ports:
            - containerPort: 3306
              name: mysql
          volumeMounts:
            - name: www
              mountPath: /var/lib/mysql
  volumeClaimTemplates:
    - metadata:
        name: www
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "nfs"
        resources:
          requests:
            storage: 2Gi
  • 使用 StatefulSet 管理 MySQL,每个 Pod 拥有独立的 PVCwww-mysql-0www-mysql-1)。
  • 体现了 StatefulSet 在存储层面是"独立"的------每个实例都有自己的数据目录,数据完全隔离,适合需要保持数据独立性的场景(如数据库主从集群中的各个节点)。

小结

通过这两个案例的对比,可以直观理解:

Deployment 管理 MySQL StatefulSet 管理 MySQL
PVC 数量 1 个(多个 Pod 共享) 每个 Pod 各 1 个独立 PVC
数据隔离 ❌ 数据共享 ✅ 数据隔离
Pod 身份 ❌ 随机名称,不稳定 ✅ 固定名称,稳定
核心价值 演示"无状态管控器的存储共享行为" 演示"有状态管控器的存储独立行为"

💡 关于生产环境部署数据库的说明 :上述案例仅用于学习理解 StatefulSet 与 Deployment 在存储管理上的差异。生产环境中部署 MySQL、PostgreSQL 等有状态数据库,建议使用官方维护的 Operator(如 Percona XtraDB Cluster Operator、CloudNativePG),它们内置了自动备份、故障恢复、集群扩缩容等生产级能力,无需手动编写复杂的 YAML 文件。

容器化 MySQL 主从同步方案

方案 描述 适用场景
方案一 用 StatefulSet 部署两个独立的 MySQL Pod,之后手动配置主从同步 学习测试、临时搭建,生产环境绝对禁止
方案二 用 StatefulSet 部署两个独立的 MySQL Pod,使用Shell 脚本自动化实现主从同步 自动化运维场景,但维护成本高,可靠性低
方案三 使用 Helm Chart 配合 Operator 实现有状态服务的自动化部署和管理 生产环境推荐
方案四 使用 Kubernetes 原生 Operator 模式管理数据库集群 需要精细化运维,大型生产集群推荐

参考资料

相关推荐
不相心 -w-1 小时前
Cmake的基础用法
linux·开发语言·c++
深爱水瓶 血影S狂风1 小时前
Linux.NET学习手记(2)
linux·学习·.net
张文君1 小时前
docker registry 删除镜像
运维·docker·容器
cuiyaonan20002 小时前
some weird issues regarding docker
运维·docker·容器
zerwave2 小时前
Docker 学习:多容器网络互通——从 host 模式到自定义 bridge 网络
网络·学习·docker
sg_knight2 小时前
Codex CLI 安装全攻略:macOS / Linux / Windows(WSL2)三端实战
linux·windows·macos·openai·ai编程·coding·codex
人间凡尔赛2 小时前
2026 云原生“后容器时代“:WebAssembly 如何重构后端架构?
后端·云原生·架构
大眼、不聚光2 小时前
9.linux系统管理-ssh免密配置
linux·ssh·github
啦啦啦!2 小时前
虚拟机如何接入Codex并配置中转站(vscode插件版)
linux·vscode·ubuntu·编辑器