基于 Kubernetes 1.22 版本
一、基础概念
StatefulSet 是用于管理有状态应用程序的,所谓有状态即应用程序在操作中会存储数据。它管理着一组 Pod 的部署和扩展,并保证这些 Pod 的顺序和唯一性。
在 Kubernetes 中 Pod 的管理对象 Deployment、DaemonSet、Job 都是面向无状态服务的。但实际中有很多服务是有状态,特别是存储或者中间件集群,例如 MySQL、MongoDB、Kafka、ZooKeeper 集群等。
与 Deployment 类似,StatefulSet 管理基于相同容器规范的 Pod。与 Deployment 不同的是,StatefulSet 会为每个 Pod 维护一个粘性标识。这些 Pod 是根据相同的规范创建的,但不可互换。每个 Pod 都有一个持久标识符,它在任何重新调度过程中都会维护该标识符。
StatefulSet 应用场景:
• 稳定唯一的网络标识符。
• 稳定持久的存储
• 有序优雅的部署和扩展
• 有序的自动滚动更新
使用 StatefulSet 的一些限制:
• 给定 Pod 的存储必须由 PersistentVolume Provisioner 根据请求的 Storage Class 进行配置,或者由管理员预先配置。
• 删除和缩小 StatefulSet 不会删除与 StatefulSet 关联的卷,这样做是为了确保数据安全。
• StatefulSets 目前需要一个 Headless Service 来负责 Pod 的网络标识,你需要责任创建此服务。
• StatefulSet 不保证删除StatefulSet 时 Pod 的终止。为了实现 StatefulSet 中 Pod 的有序和优雅终止,可以在删除之前将 StatefulSet 缩小到 0。
• 使用带有默认 Pod 管理策略(OrderedReady)的滚动更新时,可能会进入需要手动干预才能修复的损坏状态。
官方文档:
- 基本概念:https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
- 基础使用:https://kubernetes.io/zh/docs/tutorials/stateful-application/basic-stateful-set/
- 定义:https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/stateful-set-v1/
二、定义详解
我们先来看一个示例:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # 必须匹配 .spec.template.metadata.labels
serviceName: "nginx" # 管理此 StatefulSet 的服务的名称。该服务必须存在于 StatefulSet 之前,并负责该集合的网络标识。
replicas: 3 # 副本数,默认为1
minReadySeconds: 10 # 最小就绪秒数,默认为0
template: # Pod定义模板
metadata:
labels:
app: nginx # 必须匹配 .spec.template.metadata.labels
spec:
terminationGracePeriodSeconds: 10 # Pod 需要优雅终止的可选持续时间
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # PVC模板
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
在上面的例子中:
• 名为 nginx 的 Headless Service 用于控制网络域。
• 名为 web 的 StatefulSet 有一个规范,指出 nginx 容器的 3 个副本将在唯一的 Pod 中启动。
• 该 volumeClaimTemplates 会使用由 PersistentVolume 提供稳定的存储。
1、Pod 标识
StatefulSet Pods 有一个唯一的标识,它由一个序号、一个稳定的网络标识和一个稳定的存储组成。无论它(重新)调度在哪个节点上,标识都会附着在 Pod 上。
(1)序数索引
对于具有 N 个副本的 StatefulSet,StatefulSet 中的每个 Pod 将被分配一个整数序数,从 0 到 N-1,它在整个 Set 中是唯一的。
(2)稳定的网络ID
StatefulSet 中的每个 Pod 都从 StatefulSet 的名称和 Pod 的序号派生其主机名。构造的主机名的模式是 $(statefulset name)-$(ordinal)
。上面的示例将创建三个名为 web-0、web-1、web-2。
StatefulSet 可以使用Headless Service 来控制其 Pod 的域。此服务管理的域采用以下形式: $(service name).$(namespace).svc.cluster.local
,其中“cluster.local”是集群域。在创建每个 Pod 时,它会获得一个匹配的 DNS 子域,格式为: $(podname).$(governing service domain)
,其中管理服务由 StatefulSet 上的 serviceName 字段定义。
根据集群中 DNS 的配置方式,您可能无法立即查找新运行 Pod 的 DNS 名称。当集群中的其他客户端在创建 Pod 之前已经发送了对主机名的查询时,可能会发生此行为。负缓存(在 DNS 中很正常)意味着即使在 Pod 运行后至少几秒钟,之前失败的查找结果也会被记住并重复使用。
如果您需要在创建 Pod 后立即发现它们,您有以下几种选择:
• 直接查询 Kubernetes API,而不是依赖 DNS 查找。
• 减少 Kubernetes DNS 提供程序中的缓存时间(通常这意味着编辑 CoreDNS 的配置映射,当前缓存 30 秒)。
以下是集群域、服务名称、StatefulSet 名称的选择示例,以及它们如何影响 StatefulSet 的 Pod 的 DNS 名称。
| Cluster Domain | Service (ns/name) | StatefulSet (ns/name) | StatefulSet Domain | Pod DNS | Pod Hostname |
| cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-{0..N-1}.nginx.default.svc.cluster.local | web-{0..N-1} |
| cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-{0..N-1}.nginx.foo.svc.cluster.local | web-{0..N-1} |
| kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-{0..N-1}.nginx.foo.svc.kube.local | web-{0..N-1} |
注意:除非另有配置,否则集群域将设置为 cluster.local。
(3)稳定的存储
对于 StatefulSet 中定义的每个 VolumeClaimTemplate 条目,每个 Pod 都会收到一个 PVC。在上面的 nginx 示例中,每个 Pod 都会收到一个 PV,其中 StorageClass 为 my-storage-class 和 1 Gib 的预置存储。如果未指定 StorageClass,则将使用默认 StorageClass。当 Pod 被(重新)调度到节点上时,其 volumeMounts 会挂载与其 PVC 关联的 PV。 请注意,当 Pod 或 StatefulSet 被删除时,与 Pod 的 PVC 关联的 PV 不会被删除,这必须手动完成。
(4)Pod 名称标签
当 StatefulSet 控制器创建一个 Pod,它会添加一个标签 statefulset.kubernetes.io/pod-name
,该标签设置为 Pod 的名称。此标签允许您将 Service 附加到 StatefulSet 中的特定 Pod。
2、部署和扩展
• 对于有 N 个副本的 StatefulSet,在部署 Pod 时,它们是按顺序创建的,从 {0..N-1} 开始。
• 当 Pod 被删除时,它们会以相反的顺序从 {N-1..0} 终止。
• 在将扩展操作应用于 Pod 之前,它的所有前任都必须处于 Running 和 Ready 状态。
• 在终止 Pod 之前,必须完全关闭其所有后继者。
StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds
指定为 0。这种做法是不安全的,强烈建议不要这样做。进一步解释请参考 强制删除 StatefulSet Pods。
上面的 nginx 示例创建后,会按照 web-0、web-1、web-2 的顺序部署三个 Pod。在 web-0 处于Running 和 Ready 状态之前不会部署 web-1,而在 web-1 处于Running 和 Ready 状态之前不会部署 web-2。如果 web-0 失败,则在 web-1 运行并准备好之后,但在 web-2 启动之前,web-2 将不会启动,直到 web-0 成功重新启动并变为运行并准备好。
果用户要通过编辑 StatefulSet 来扩展已部署的示例,例如设置 replicas=1。则 web-2 将首先终止。在 web-2 完全关闭并删除之前,web-1 不会被终止。如果 web-0 在 web-2 终止并完全关闭之后失败,但在 web-1 终止之前,web-1 将不会终止,直到 web-0 运行并准备好。
3、PVC 保留
在 1.23 中,有可选 .spec.persistentVolumeClaimRetentionPolicy
字段控制在 StatefulSet 的生命周期中是否保留或者删除 PVC。
您必须启用 StatefulSetAutoDeletePVC feature gate 才能使用此字段。启用后,您可以为每个 StatefulSet 配置两个策略:
- whenDeleted:配置删除 StatefulSet 时应用的卷保留行为。
- whenScaled:配置当 StatefulSet 的副本数减少时应用的卷保留行为。
对于上面两个策略,可以将值设置为 Delete 或 Retain。
- Delete:对于受策略影响的每个Pod,将删除从 StatefulSet volumeClaimTemplate 创建的PVC。使用 whenDeleted 策略,volumeClaimTemplate 中的所有PVC 将在其 Pod 被删除后被删除。使用 whenScaled 策略,在删除 Pod 副本后,仅删除与正在缩小的 Pod 副本相对应的PVC。
- Retain(默认):volumeClaimTemplate 中的 PVC 在其 Pod 被删除时不受影响。1.23 之前版本也是这样的行为。
- Retain(默认):volumeClaimTemplate 中的 PVC 在其 Pod 被删除时不受影响。1.23 之前版本也是这样的行为。
三、示例
下面创建一个 StatefulSet, 同时创建了一个 Headless Service nginx:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: nfs
resources:
requests:
storage: 1Gi
查看 StatefulSet Pod 创建过程,如上所述为顺序创建,当然 StatefulSet 也支持并行启动。
[root@cp statefulset]# kubectl apply -f web.yaml
[root@cp statefulset]# kubectl get pod -w
NAME READY STATUS RESTARTS AGE
web-1 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 12s
web-0 1/1 Running 0 30s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 11s
web-1 1/1 Running 0 18s
[root@cp statefulset]# kubectl get sts
NAME READY AGE
web 2/2 82s
查看 PV 和 PVC,删除 StatefulSet 后,其 Pod 关联 PVC 和 PV 默认会保留。
[root@cp statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-26df6b71-1e95-470d-bfd0-88b2ceaa2fb1 1Gi RWO Delete Bound default/www-web-0 nfs 6m20s
pvc-ba4e5bf1-85b1-47e0-bbe8-b8d94fa4a510 1Gi RWO Delete Bound default/www-web-1 nfs 6m1s
[root@cp statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-26df6b71-1e95-470d-bfd0-88b2ceaa2fb1 1Gi RWO nfs 6m25s
www-web-1 Bound pvc-ba4e5bf1-85b1-47e0-bbe8-b8d94fa4a510 1Gi RWO nfs 6m6s
DNS 测试:
[root@cp statefulset]# kubectl run -it --rm --image busybox:1.28 dns-test
/ # nslookup web-0.nginx
Server: 10.1.0.10
Address 1: 10.1.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.0.192 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server: 10.1.0.10
Address 1: 10.1.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.0.193 web-1.nginx.default.svc.cluster.local
访问测试:
[root@cp ~]# for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
[root@cp ~]# for i in 0 1; do kubectl exec -it "web-$i" -- curl http://localhost/; done
web-0
web-1
# 使用 FQDN 的方式访问
[root@cp statefulset]# kubectl run -it --rm --image=nginx test -- bash
If you don't see a command prompt, try pressing enter.
root@test:/# curl web-0.nginx
web-0
root@test:/# curl web-1.nginx
web-1
参考文章: