StatefulSet 是 Kubernetes 中的一种控制器(Controller),用于管理有状态应用程序的部署。与Deployment 控制器不同,StatefulSet 为每个 Pod 实例分配了一个唯一的标识符,并确保这些标识符在 Pod 重新创建时保持不变。这为有状态应用程序提供了一些关键的功能和保证,例如稳定的网络标识符、有序的部署和扩展以及持久化存储。
下面是 StatefulSet 的一些基本介绍:1、唯一标识符:每个 StatefulSet 管理的 Pod 实例都被分配了一个唯一的标识符,通常是一个整数索引。这个标识符可以用于在网络中唯一地标识每个 Pod 实例。
2、稳定的网络标识符:StatefulSet 中的每个 Pod 实例都有一个稳定的网络标识符,通常是一个DNS 名称。这使得其他应用程序可以通过名称轻松地访问特定的 Pod 实例,而不需要关注其具体的 IP地址变化。
3、有序的部署和扩展:StatefulSet 控制器确保 Pod 实例按照定义的顺序逐个启动和关闭。这对于依赖先前实例状态的应用程序非常重要。此外,扩展 StatefulSet 时,新的 Pod 实例也会按照指定的顺序逐个创建。
4、持久化存储:StatefulSet 允许每个 Pod 实例关联一个持久化卷(Persistent Volume),这使得有状态应用程序可以在 Pod 重新创建时保留其数据。这为应用程序提供了持久化存储的能力,使得数据不会丢失或重置。
备注:什么是有状态服务?有状态服务是指在服务运行过程中需要维护和管理一些状态信息的服务。与之相对的是无状态服务,它们在处理请求时不依赖或不保存任何状态信息。
有状态服务的一个常见场景是在分布式系统中,当需要处理一系列相关的请求时,服务可能需要保留
先前请求的状态信息。例如,一个在线购物网站的结账过程,每个请求都需要知道当前用户的购物车内容、支付状态等信息,这些信息需要在整个结账过程中保持一致性。又或者,在一个聊天应用中,服务需要跟踪每个用户的聊天历史记录以提供正确的会话。
为了实现有状态服务,可以采用以下方法之一:1、会话状态:在每个客户端和服务之间建立一个会话,将状态信息存储在会话中。客户端在每个请求中提供会话标识符,服务根据该标识符检索和更新相应的状态。常见的实现方式是使用会话标识符和键值存储(如 Redis)来存储状态信息。
2、数据库存储:将状态信息存储在数据库中。服务可以将状态数据存储在关系型数据库(如MySQL)或 NoSQL 数据库(如 MongoDB)中。通过数据库查询和更新操作,服务可以管理状态的读取和修改。
3、分布式状态管理:在分布式系统中,可以使用分布式状态管理工具(例如 ZooKeeper、etcd 或Consul)来存储和管理状态信息。这些工具提供了分布式一致性保证,允许多个服务实例之间共享和同
步状态。
编写资源清单
bash
root@ubuntu0:/devops/pv# kubectl explain statefulset
KIND: StatefulSet
VERSION: apps/v1
字段说明:
• apiVersion:定义 StatefulSet 资源需要使用的 API 版本。
• kind:定义资源类型。
• metadata:元数据对象,包含 StatefulSet 的描述信息。
• spec:容器相关的信息,定义了 StatefulSet 中 Pod 的期望标识。
2、查看 StatefulSet 的 spec 字段如何定义,使用以下命令:kubectl explain
statefulset.spec
字段说明:
• podManagementPolicy:Pod 的管理策略。
• replicas:副本数,定义 StatefulSet 中 Pod 的数量。
• revisionHistoryLimit:保留的历史版本数。
• selector:标签选择器,用于选择与之关联的 Pod。
• serviceName:Headless Service 的名称。
• template:生成 Pod 的模板,用于描述将创建的 Pod 的属性。
• updateStrategy:更新策略,定义如何更新 Pod。
• volumeClaimTemplates:存储卷申请模板,定义 Pod 使用的存储卷。
3、查看 StatefulSet 的 spec.template 字段如何定义,使用以下命令:kubectl explain
statefulset.spec.template
字段说明:
• metadata:元数据对象,包含 Pod 模板的描述信息。
• spec:定义 Pod 的属性和配置。
首先创建一个存储类
root@ubuntu0:/devops/statfulset# cat class-web.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-web
provisioner: example.com/nfs
reclaimPolicy: Retain #回收策略
root@ubuntu0:/devops/statfulset# kubectl get storageclasses.storage.k8s.io
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs example.com/nfs Delete Immediate false 2d10h
nfs-web example.com/nfs Retain Immediate false 67s
把 nginx 的离线压缩包 nginx.tar.gz 上传到 ubuntu1、ubuntu2 上,手动解压:
root@ubuntu0:/devops/statfulset# cat nginx.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
serviceName: "my-web"
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.23
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/htm
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteMany"]
storageClassName: "nfs-web"
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: my-web
labels:
app: nginx
spec:
ports:
- port: 80
name: web
#protocol: tcp
# targetPort: 80
clusterIP: None
selector:
app: nginx
root@ubuntu0:/devops/statfulset# kubectl apply -f nginx.yaml
statefulset.apps/nginx created
service/my-web created
root@ubuntu0:/devops/statfulset# kubectl get storageclasses.storage.k8s.io
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-web example.com/nfs Retain Immediate false 47h
root@ubuntu0:/devops/statfulset# kubectl get statefulsets.apps
NAME READY AGE
nginx 1/1 14s
root@ubuntu0:/devops/statfulset# kubectl get statefulsets.apps -owide
NAME READY AGE CONTAINERS IMAGES
nginx 1/1 18s nginx nginx:1.23
root@ubuntu0:/devops/statfulset# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-0 1/1 Running 0 51s 10.100.1.11 ubuntu1 <none> <none>
xpp-c44d68dcf-456kt 1/1 Running 1 (22h ago) 4d10h 10.100.1.10 ubuntu1 <none> <none>
为什么是nginx-0
在 Kubernetes StatefulSet 中,Pod 的命名规则是 固定的命名模式:
从 0 开始的序号:
pod中的名字是根据statefulset中的名字而来
root@ubuntu0:/devops/statfulset# kubectl get statefulsets.apps
NAME READY AGE
nginx 1/1 42m
第一个 Pod:nginx-0
第二个 Pod:nginx-1
第三个 Pod:nginx-2
稳定性和唯一性:
即使 Pod 重启或重新调度,名称保持不变
每个序号对应一个稳定的网络标识和存储卷
Pod 主机名:nginx-0
DNS 名称:nginx-0.my-web.default.svc.cluster.local
nginx-0.my-web.default.svc.cluster.local
└──┬── └──┬── └──┬── └─┬─ └──┬────┘
│ │ │ │ │
│ │ │ │ └─ 集群域名后缀(可配置)
│ │ │ └─ 服务类型(Service)
│ │ └─ 命名空间(Namespace)
│ └─ 服务名(Service Name)
└─ Pod 名称
可以通过 Service 访问:kubectl run -it --rm --image=busybox:1.28 test -- sh
bash
# 在 busybox 容器中测试
wget -O- http://nginx-0.my-web
root@ubuntu0:/devops/statfulset# kubectl exec -it nginx-0 -- sh
#
#
# hostname
nginx-0
# hostname -f
nginx-0.my-web.default.svc.xp.com
# cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.100.1.11 nginx-0.my-web.default.svc.xp.com nginx-0
查看svc
root@ubuntu0:/devops/statfulset# kubectl get svc -l app=nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-web ClusterIP None <none> 80/TCP 7m18s
root@ubuntu0:/devops/statfulset# kubectl describe svc my-web
Name: my-web
Namespace: default
Labels: app=nginx
Annotations: <none>
Selector: app=nginx
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: None
IPs: None
Port: web 80/TCP
TargetPort: 80/TCP
Endpoints: 10.100.1.11:80
Session Affinity: None
Events: <none>
此时查看pv,pvc
root@ubuntu0:/devops/statfulset# kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-ea67db55-19e6-4be0-ab7b-726198ca2712 1Gi RWX Retain Bound default/www-nginx-0 nfs-web 7m49s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/www-nginx-0 Bound pvc-ea67db55-19e6-4be0-ab7b-726198ca2712 1Gi RWX nfs-web 7m50s
对于无头的svc,需要访问pod
root@ubuntu0:/devops/statfulset# curl nginx-0.my-web.default.svc.xp.com
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
</body>
</html>
举例说明 service 和 headless service 区别:
bash
1、通过 deployment 创建 pod,pod 前端创建一个 service,这个 svc 是有 ip 的
[root@xuegod63 ~]# cat deploy-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: ClusterIP
ports:
- port: 80 #service 的端口,暴露给 k8s 集群内部服务访问
protocol: TCP
targetPort: 80 #pod 容器中定义的端口
selector:
run: my-nginx #选择拥有 run=my-nginx 标签的 pod
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: busybox
ports:
- containerPort: 80
command:
- sleep
- "3600"
#更新资源清单文件
[root@xuegod63 ~]# kubectl apply -f deploy-service.yaml
#查看 service
[root@xuegod63 ~]# kubectl get svc -l run=my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
my-nginx ClusterIP 10.100.89.90 <none> 80/TCP
#查看 pod
[root@xuegod63 ~]# kubectl get pods -l run=my-nginx
NAME READY STATUS RESTARTS AGE
my-nginx-58f74fc5b6-jzbvk 1/1 Running 0 70s
my-nginx-58f74fc5b6-n9lqv 1/1 Running 0 53s
#通过上面可以看到 deployment 创建的 pod 是随机生成的
[root@xuegod63 ~]#kubectl run busybox --image busybox:1.28 --restart=Never --rm -
it busybox -- sh
root@web-1:/# nslookup my-nginx.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: my-nginx.default.svc.cluster.local
Address: 10.100.89.90
#解析的是 service 的 ip 地址
有 ip 的 service 和没有 ip 的 service 有啥区别:
bash
从效率的角度来看,解析带有 IP 的 SVC 和解析没有 IP 的 SVC,这两种方式的效率可能会有所
不同
1. 解析带有 IP 的 SVC:
• 解析到 SVC 的 IP 地址可以直接将请求发送到指定的 IP 地址,省略了额外的 DNS
查询步骤,因此通常具有较高的效率。
• 由于 SVC 具有 IP 地址,请求可以直接路由到指定的 IP 地址上,跳过了额外的
DNS 解析和负载均衡步骤。
• 这种方式适用于从集群外部或其他命名空间访问 SVC,或者需要直接使用 SVC 的 IP
地址进行访问的场景。
2. 解析没有 IP 的 SVC:
• 解析到没有 IP 的 SVC,DNS 解析会将 SVC 名称解析为与之关联的后端 Pod 的
IP 地址。这涉及到额外的 DNS 查询和解析步骤,可能会引入一定的延迟。
• 解析到后端 Pod 的 IP 地址后,请求需要通过负载均衡机制(如轮询或基于权重的负
载均衡)选择一个后端 Pod 来处理请求,这可能会增加一定的延迟。
• 这种方式适用于集群内部访问 SVC 的场景,其中请求在集群内部的服务间进行通信。
实战:Statefulset 管理 pod-扩容、缩容、更新
bash
Statefulset 实现 pod 的动态扩容
root@ubuntu0:/devops/statfulset# grep 2 nginx.yaml
replicas: 2
image: nginx:1.23
root@ubuntu0:/devops/statfulset# kubectl apply -f nginx.yaml
statefulset.apps/nginx configured
service/my-web unchanged
root@ubuntu0:/devops/statfulset#
root@ubuntu0:/devops/statfulset#
root@ubuntu0:/devops/statfulset# kubectl get statefulsets.apps
NAME READY AGE
nginx 2/2 13h
root@ubuntu0:/devops/statfulset# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 13h
nginx-1 1/1 Running 0 15s
#也可以直接编辑控制器实现扩容
root@ubuntu0:/devops/statfulset# kubectl edit statefulsets.apps nginx
spec:
podManagementPolicy: OrderedReady
replicas: 2
Statefulset 实现 pod 的动态缩容
如果我们觉得 4 个 Pod 副本太多了,想要减少,只需要修改配置文件 statefulset.yaml 里的
replicas 的值即可,把 replicaset:4 变成 replicas: 2,修改之后,执行如下命令更新:
Statefulset 实现 pod 的更新
1.上传myapp.tar.gz
2.更改镜像
root@ubuntu0:/devops/statfulset# grep ikubernetes/myapp:v2 nginx.yaml
image: ikubernetes/myapp:v2
root@ubuntu0:/devops/statfulset# kubectl apply -f nginx.yaml
statefulset.apps/nginx configured
service/my-web unchanged
root@ubuntu0:/devops/statfulset# kubectl get pods -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-0 1/1 Running 0 6s 10.100.1.12 ubuntu1 <none> <none>
nginx-1 1/1 Running 0 9s 10.100.2.19 ubuntu2 <none> <none>
root@ubuntu0:/devops/statfulset# kubectl describe pods nginx-0|grep image
Normal Pulled 22s kubelet Container image "ikubernetes/myapp:v2" already present on machine
通过上面可以看到 pod 已经使用刚才更新的镜像 ikubernetes/myapp:v2 了
root@ubuntu0:/devops/statfulset# kubectl describe statefulsets.apps
Name: nginx
Namespace: default
CreationTimestamp: Thu, 01 Jan 2026 21:47:51 +0800
Selector: app=nginx
Labels: <none>
Annotations: <none>
Replicas: 2 desired | 2 total
Update Strategy: RollingUpdate
Partition: 0 #partition=1 表示创建的 pod 序号>=1 的才会被更新
Pods Status: 2 Running / 0 Waiting / 0 Succeeded / 0 Failed