一. 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-0、web-1),重建后名称不变 |
| 顺序控制 | 扩容/启动 时按序号从小到大依次创建(0→1→2...);缩容/删除时按序号从大到小逆序删除(2→1→0...) |
| 存储绑定保障 | 与 volumeClaimTemplates 协同工作,确保每个 Pod 重建后能自动绑定回其原有的 PVC |
| 滚动更新管理 | 支持 RollingUpdate(默认)和 OnDelete 两种更新策略,更新时按逆序逐个替换 Pod |
| 状态持久化 | 即使 Pod 被驱逐或节点故障,控制器也会尝试重新调度并恢复其身份与存储 |
关键特性详解
-
稳定的网络标识
- Pod 名称固定,配合 Headless Service 生成不变的 DNS 记录
- 其他服务可通过
<pod-name>.<service-name>.<namespace>.svc.cluster.local精确访问特定 Pod
-
独立的持久化存储
- 通过
volumeClaimTemplates为每个序号创建独立的 PVC - Pod 重建后自动挂载回原来的 PVC,保证数据不丢失
- 通过
-
有序的部署与扩展
- 扩容时必须等待前一个 Pod 处于
Running和Ready状态后才创建下一个 - 缩容时则按相反顺序逐个删除,保证集群的稳定性
- 扩容时必须等待前一个 Pod 处于
-
可配置的更新策略
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-0 → web-1 → web-2 |
| 缩容(replicas 减少) | 按序号从大到小逆序删除:web-2 → web-1 → web-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-0、web-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: 2 → replicas: 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: 3 → replicas: 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 部署示例,对比 Deployment 和 StatefulSet 在管理有状态应用时的核心差异,帮助理解 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 拥有独立的 PVC (
www-mysql-0、www-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 模式管理数据库集群 | 需要精细化运维,大型生产集群推荐 |
参考资料
- 有状态服务部署案例(Cassandra):https://kubernetes.io/zh-cn/docs/tutorials/stateful-application/cassandra/
- 列式存储数据库(如 Cassandra):大数据分析领域,查询极快
- 行式存储数据库(如 MySQL):OLTP 场景,事务支持完善