StatefulSet
StatefulSet简介
StatefulSet 是用来管理有状态应用的工作负载 API 对象。
StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。
和 Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。 尽管 StatefulSet 中的单个 Pod 仍可能出现故障, 但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。
使用 StatefulSet
StatefulSet 对于需要满足以下一个或多个需求的应用程序很有价值:
- 稳定的、唯一的网络标识符。
- 稳定的、持久的存储。
- 有序的、优雅的部署和扩缩。
- 有序的、自动的滚动更新。
在上面描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的。 如果应用程序不需要任何稳定的标识符或有序的部署、删除或扩缩, 则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序,比如 Deployment 或者 ReplicaSet 可能更适用于你的无状态应用部署需要。
限制
- 给定 Pod 的存储必须由 PersistentVolume Provisioner 基于所请求的
storage class来制备,或者由管理员预先制备。 - 删除或者扩缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
- StatefulSet 当前需要无头服务来负责 Pod 的网络标识。你需要负责创建此服务。
- 当删除一个 StatefulSet 时,该 StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序且体面地终止,可以在删除之前将 StatefulSet 缩容到 0。
- 在默认 Pod 管理策略(
OrderedReady) 时使用滚动更新, 可能进入需要人工干预才能修复的损坏状态。
组件
编辑yaml文件
cat > statefulset.yaml << EOF
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"
replicas: 3 # 默认值是 1
minReadySeconds: 10 # 默认值是 0
template:
metadata:
labels:
app: nginx # 必须匹配 .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
#image: registry.k8s.io/nginx-slim:0.8
image: mirrorgooglecontainers/nginx-slim:0.8
ports:
- containerPort: 80 # 这个端口只是声明,不会起到访问的作用
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www # 这里的名称要和上边的volumeMounts下的名称一致
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs-client" # sc名 称
resources:
requests:
storage: 1Gi
EOF
上述例子中:
- 名为
nginx的 Headless Service 用来控制网络域名。 - 名为
web的 StatefulSet 有一个 Spec,它表明将在独立的 3 个 Pod 副本中启动 nginx 容器。 volumeClaimTemplates将通过 PersistentVolume 制备程序所准备的 PersistentVolumes 来提供稳定的存储。
StatefulSet 的命名需要遵循 DNS 子域名规范。
创建statefulset
$ kubectl apply -f statefulset.yaml
service/nginx created
statefulset.apps/web created
查看sts
$ kubectl get sts
NAME READY AGE
web 3/3 31s
查看pod
可以看到,statefulset创建的pod是以 statefulset名称-序号 命名的
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 28s
web-1 1/1 Running 0 6s
web-2 1/1 Running 0 3s
查看svc,注意Headless Service(也叫无头svc)的 CLUSTER-IP 是空的
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 6m58s
无头svc所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录
pod名称.svc名称.命名空间.svc.cluster.local
启动一个busybox容器测试一下
kubectl run -it --image busybox:1.28.3 test --restart=Never --rm /bin/sh
进入到busybox容器进行ping测试
/ # ping -c1 web-0.nginx.test.svc.cluster.local
PING web-0.nginx.test.svc.cluster.local (100.108.251.2): 56 data bytes
64 bytes from 100.108.251.2: seq=0 ttl=63 time=0.093 ms
--- web-0.nginx.test.svc.cluster.local ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.093/0.093/0.093 ms
进入到busybox容器进行nslookup Headless Service测试
:::caution注意
busybox镜像要使用1.28.x,其余版本会有问题
/ # nslookup nginx
Server: 10.96.0.10
Address: 10.96.0.10:53
** server can't find nginx.test.svc.cluster.local: NXDOMAIN
*** Can't find nginx.svc.cluster.local: No answer
*** Can't find nginx.cluster.local: No answer
*** Can't find nginx.openstacklocal: No answer
*** Can't find nginx.test.svc.cluster.local: No answer
*** Can't find nginx.svc.cluster.local: No answer
*** Can't find nginx.cluster.local: No answer
*** Can't find nginx.openstacklocal: No answer
:::
/ # nslookup nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx
Address 1: 100.108.251.48 web-2.nginx.test.svc.cluster.local
Address 2: 100.108.251.12 web-1.nginx.test.svc.cluster.local
Address 3: 100.108.251.2 web-0.nginx.test.svc.cluster.local
Pod 选择算符
你必须设置 StatefulSet 的 .spec.selector 字段,使之匹配其在 .spec.template.metadata.labels 中设置的标签。 未指定匹配的 Pod 选择算符将在创建 StatefulSet 期间导致验证错误。
卷申领模板
你可以设置 .spec.volumeClaimTemplates, 它可以使用 PersistentVolume 制备程序所准备的 PersistentVolumes 来提供稳定的存储。
最短就绪秒数
特性状态: Kubernetes v1.25 [stable]
.spec.minReadySeconds 是一个可选字段。 它指定新创建的 Pod 应该在没有任何容器崩溃的情况下运行并准备就绪,才能被认为是可用的。 这用于在使用滚动更新策略时检查滚动的进度。 该字段默认为 0(Pod 准备就绪后将被视为可用)。 要了解有关何时认为 Pod 准备就绪的更多信息, 请参阅容器探针。
Pod 标识
StatefulSet Pod 具有唯一的标识,该标识包括顺序标识、稳定的网络标识和稳定的存储。 该标识和 Pod 是绑定的,与该 Pod 调度到哪个节点上无关。
有序索引
对于具有 N 个副本的 StatefulSet,该 StatefulSet 中的每个 Pod 将被分配一个从 0 到 N-1 的整数序号,该序号在此 StatefulSet 上是唯一的。
稳定的网络 ID
StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。 组合主机名的格式为$(StatefulSet 名称)-$(序号)。 上例将会创建三个名称分别为 web-0、web-1、web-2 的 Pod。 StatefulSet 可以使用无头服务控制它的 Pod 的网络域。管理域的这个服务的格式为: $(服务名称).$(名字空间).svc.cluster.local,其中 cluster.local 是集群域。 一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为: $(pod 名称).$(所属 服务的 DNS 域名),其中所属服务由 StatefulSet 的 serviceName 域来设定。
取决于集群域内部 DNS 的配置,有可能无法查询一个刚刚启动的 Pod 的 DNS 命名。 当集群内其他客户端在 Pod 创建完成前发出 Pod 主机名查询时,就会发生这种情况。 负缓存 (在 DNS 中较为常见) 意味着之前失败的查询结果会被记录和重用至少若干秒钟, 即使 Pod 已经正常运行了也是如此。
如果需要在 Pod 被创建之后及时发现它们,可使用以下选项:
- 直接查询 Kubernetes API(比如,利用 watch 机制)而不是依赖于 DNS 查询
- 缩短 Kubernetes DNS 驱动的缓存时长(通常这意味着修改 CoreDNS 的 ConfigMap,目前缓存时长为 30 秒)
正如限制中所述, 你需要负责创建无头服务以便为 Pod 提供网络标识。
下面给出一些选择集群域、服务名、StatefulSet 名、及其怎样影响 StatefulSet 的 Pod 上的 DNS 名称的示例:
| 集群域名 | 服务(名字空间/名字) | StatefulSet(名字空间/名字) | StatefulSet 域名 | Pod DNS | Pod 主机名 |
|---|---|---|---|---|---|
| cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-NaN.nginx.default.svc.cluster.local | web-NaN |
| cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-NaN.nginx.foo.svc.cluster.local | web-NaN |
| kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-NaN.nginx.foo.svc.kube.local | web-NaN |
:::tip说明
集群域会被设置为 cluster.local,除非有其他配置。
:::
稳定的存储
对于 StatefulSet 中定义的每个 VolumeClaimTemplate,每个 Pod 接收到一个 PersistentVolumeClaim。 在上面的 nginx 示例中,每个 Pod 将会得到基于 StorageClass my-storage-class 制备的 1 Gib 的 PersistentVolume。 如果没有声明 StorageClass,就会使用默认的 StorageClass。 当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts 会挂载与其 PersistentVolumeClaims 相关联的 PersistentVolume。 请注意,当 Pod 或者 StatefulSet 被删除时,与 PersistentVolumeClaims 相关联的 PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。
Pod 名称标签
当 StatefulSet 控制器创建 Pod 时, 它会添加一个标签 statefulset.kubernetes.io/pod-name,该标签值设置为 Pod 名称。 这个标签允许你给 StatefulSet 中的特定 Pod 绑定一个 Service。
查看pod yaml文件,可以看到会有 statefulset.kubernetes.io/pod-name: web-0 标 签
$ kubectl get pod web-0 -o yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2022-11-06T09:53:03Z"
generateName: web-
labels:
app: nginx
controller-revision-hash: web-7747f8f7b
statefulset.kubernetes.io/pod-name: web-0
name: web-0
namespace: test
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: StatefulSet
name: web
...