文章来源:Kubernetes 全站架构师
StatefulSet
StatefulSet(有状态集,缩写为 sts) 常用于部署有状态的且需要有序启动的应用程序。比如在进行 SpringCloud 项目容器化时,Eureka 的部署是比较适合用 StatefulSet 方式部署的,也可以给每个 Eureka 实例创建一个唯一且固定的标识符,并且每个 Eureka 实例无需配置多余的 Service,其余 Spring Boot 应用可以直接通过 Eureka 的 Headless Service 即可进行注册。
Eureka 的 StatefulSet 的资源名称是 eureka, eureka-0 eureka-1 eureka-2
Service: 使用 headless service 通信, 没有 ClusterIP,eureka.svc
Eureka-0.eureka-svc.NAMESPACE_NAME eureka-1.eureka-svc …
StatefulSet 的基本概念
StatefulSet 主要用于管理有状态应用程序的工作负载 API 对象。比如在生产环境中,可以部署 ElasticSearch 集群,MongoDB 集群或者需要持久化的 RabbitMQ 集群,Redis 集群,Kafka 集群和 Zookeeper 集群等。
和 Deployment 类似,一个 StatefulSet 也同样管理着基于相同容器规范的 Pod。不同的是,StatefulSet 为每个 Pod 维护了一个粘性标识。这些 Pod 是根据相同的规范创建的,但是不可互换,每个 Pod 都有一个持久的标识符,在重新调度时也会保留,一般格式为 StatefulSetName-Number。比如定义一个名字是 Redis-Sentinel 的 StatefulSet,指定创建三个 Pod,那么创建出来的 Pod 名字就为 Redis-Sentinel-0,Redis-Sentinel-1,Redis-Sentinel-2。而 StatefulSet 创建的 Pod 一般使用 Headless Service(无头服务)进行通信,和普通的 Service 的区别在于 Headless Service 没有 ClusterIP,它使用的是 Endpoint 进行互相通信,Headless 一般的格式为:
1 | statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local |
说明:
- serviceName 为 Headless Service 的名字;
- 0..N-1 为 Pod 所在的序号,从 0 开始到 N-1;
- StatefulSetName 为 StatefulSet 的名字;
- namespace 为服务所在的命名空间;
- .cluster.local 为 Cluster Domain(集群域);
比如,用一个名为 redis-ms 的 StatefulSet 部署主从架构的 Redis,第一个容器启动时,它的标识符为 redis-ms-0,并且 Pod 内主机名也为 redis-ms-0,此时就可以根据主机名来判断,当主机名为 redis-ms-0 的容器作为 Redis 的主节点,其余为从节点,那么 Slave 连接 Master 主机配置就可以使用不会更改的 Master 的 Headless Service,此时 Redis 从节点(Slave)配置文件如下
1 | port 6379 |
其中 redis-ms-0.redis-ms.public-service.svc.cluster.local
是 Redis Master 的 Headless Service。在同一个命名空间下只需要写 redis-ms-0.redis-ms 即可,后面的 public-service.svc.cluster.local 可以省略。
StatefulSet 注意事项
一般 StatefulSet 用于以下一个或者多个需求的应用程序
- 需要稳定的独一无二的网络标识符;
- 需要持久化数据;
- 需要有序的,优雅的部署和扩展;
- 需要有序的自动滚动更新。
如果应用程序不需要任何稳定的标识符或者有序的部署,删除和扩展,应该使用无状态的 Deployment 或者 ReplicaSet 部署应用程序;
StatefulSet 是 Kubernetes 1.9 版本之前的 beta 资源,在 1.5 版本之前的任何 Kubernetes 版本都没有。
Pod 所用的存储必须由 PersistentVolume Provisioner(持久化卷配置器)根据请求配置 StorageClass,或者由管理员预先配置,当然也可以不配置存储。
为了确保数据安全,删除和缩放 StatefulSet 不会删除与 StatefulSet 关联的卷,可以手动选择性地删除 PVC 和 PV;
StatefulSet 目前使用 Headless Service(无头服务)负责 Pod 的网络身份和通信,需要提前创建此服务。
删除一个 StatefulSet 时,不保证对 Pod 的终止,要在 StatefulSet 中实现 Pod 的有序和正常终止,可以在删除之前将 StatefulSet 的副本缩减为 0。
StatefulSet 组件
定义一个简单的 StatefulSet 的示例 sts-web.yaml 如下:
1 | apiVersion: v1 |
说明:
此示例没有添加存储配置
- kind: Service 定义了一个名为 nginx 的 Headless Service,创建的 Service 格式为 nginx-0.nginx.default.svc.cluster.local,其他的类似,因为没有指定 Namespace,所以默认部署在 default.
- kind: StatefulSet 定义了一个名为 web 的 StatefulSet,replicas 表述部署 Pod 的副本数,本示例为 2.
在 StatefulSet 中必须设置 Pod 的选择器(.spec.selector)用来匹配其标签(.spec.template.metadata.labels)。在 1.8 版本之前,如果未配置该字段(.spec.selector),将被设置为默认值,在 1.8 版本之后,如果未指定匹配 Pod Selector,则会导致 StatefulSet 创建错误。
当 StatefulSet 控制器创建 Pod 时,它会添加一个标签 statefulset.kubernetes.io/pod-name,该标签的值为 Pod 的名称,用于匹配 Service。
创建 StatefulSet
使用 kubectl create
命令根据定义的文件,创建一个 StatefulSet
1 | # kubectl create -f sts-web.yaml |
对于一个拥有 N 个副本的 StatefulSet,Pod 被部署时是按照 {0..N-1} 的序号顺序创建的。只有前面所有的 Pod 处于 Running 和 Ready 状态后,后面的 Pod 才会被启动;
StatefulSet 扩容和缩容
与 Deployment 类似,可以通过更新 replicas 字段扩容/缩容 StatefulSet,也可以使用 kubectl scale 或者 kubectl patch 来扩容/缩容一个 StatefulSet。
扩容
将上述创建的 sts 副本增加到 5 个(扩容之前必须保证有创建完成的静态 PV,动态 PV 和 emptyDir)
1
2# kubectl scale sts web --replicas=5
statefulset.apps/web scaled查看 Pod 状态
1
2
3
4
5
6
7# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 10m
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 79s
web-3 1/1 Running 0 61s
web-4 1/1 Running 0 43s也可以使用以下命令动态查看
1
2
3
4
5
6
7# kubectl get pod -l app=nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 11m
web-1 1/1 Running 0 11m
web-2 1/1 Running 0 2m40s
web-3 1/1 Running 0 2m22s
web-4 1/1 Running 0 2m4s
缩容
在一个终端动态查看
1
2
3
4
5
6
7kubectl get pod -l app=nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 11m
web-1 1/1 Running 0 11m
web-2 1/1 Running 0 2m40s
web-3 1/1 Running 0 2m22s
web-4 1/1 Running 0 2m4s在另一个终端将副本数改为 3
1
2# kubectl patch sts web -p '{"spec":{"replicas": 3}}'
statefulset.apps/web patched此时可以看到第一个终端显示 web-4 和 web-3 的 Pod 正在被删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# kubectl get pod -l app=nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 11m
web-1 1/1 Running 0 11m
web-2 1/1 Running 0 2m40s
web-3 1/1 Running 0 2m22s
web-4 1/1 Running 0 2m4s
web-4 1/1 Terminating 0 4m38s
web-4 0/1 Terminating 0 4m39s
web-4 0/1 Terminating 0 4m48s
web-4 0/1 Terminating 0 4m48s
web-3 1/1 Terminating 0 5m6s
web-3 0/1 Terminating 0 5m8s
web-3 0/1 Terminating 0 5m16s
web-3 0/1 Terminating 0 5m16s
更新策略
StatefulSet 的更新和 Deployment 差不多,但是 Deployment 的更新顺序是随机更新的。而 StatefulSet 是按顺序并且是从下往上更新的。
OnDelete 策略
Ondelete: 当我们选择这个更新策略并修改了 StatefulSet 的 .spec.tempate 字段时,StatefulSet 控制器不会自动更新 Pod,我们必须手动删除 Pod 才能使控制器创建新的 Pod。
RollingUpdate 策略
RollingUpdate (滚动更新)更新策略会更新一个 StatefulSet 中所有的 Pod,采用与序号索引相反的顺序进行滚动更新。 它是默认的更新策略
比如 Patch 一个名为 web 的 StatefulSet 来执行 RollingUpdate 更新
1
2kubectl patch sts web -p '{"spec": {"updateStrategy": {"type": "RollingUpdate"}}}'
statefulset.apps/web patched查看 StatefulSet 的更新策略
1
2
3
4
5
6# kubectl get sts web -oyaml | grep -A1 "updateStrategy"
f:updateStrategy:
f:rollingUpdate:
--
updateStrategy:
rollingUpdate:更改容器的镜像进行滚动更新
1
2
3
4
5
6# kubectl set image sts web nginx=nginx:1.14.2 --record
statefulset.apps/web image updated
# kubectl get statefulsets.apps web -owide
NAME READY AGE CONTAINERS IMAGES
web 3/3 3h4m nginx nginx:1.14.2灰度发布,StatefulSet 的灰度发布,主要由
.spec.updateStrategy.rollingUpdate.partition
这个参数控制,当把 partition 的值设置为2 时,则序号小于2 的不会被更新,如下所示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41apiVersion: apps/v1
kind: StatefulSet
metadata:
creationTimestamp: "2021-04-06T11:11:40Z"
generation: 4
name: web
namespace: default
spec:
podManagementPolicy: OrderedReady
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
serviceName: nginx
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:1.14.2
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
name: web
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
updateStrategy:
rollingUpdate:
partition: 2
type: RollingUpdate
级联删除与非级联删除
Kubernetes 默认的删除策略是级联删除,即删除一个 StatefulSet 时,它所关联的 Pod 也会被删除;
如果想在删除 StatefulSet 后,Pod 不被删除,只需要在删除 StatefulSet 时添加一个 --cascade=false
参数即可。如下:
1 | # kubectl delete sts web --cascade=false # 采用非级联删除 |
此时的 Pod 就成为了孤儿 Pod,手动删除时不会被重建
1 | # kubectl delete pod web-0 |