有状态副本集
StatefulSet 是用于管理有状态应用程序的工作负载 API 对象。
管理一组 Pod 的部署和扩缩,**并提供这些 Pod 的排序和唯一性保证**。
与 Deployment 类似,StatefulSet 管理基于相同容器规约的 Pod。与 Deployment 不同,StatefulSet 为每个 Pod 维护一个固定的身份。这些 Pod 从相同的规约创建,但不能互换:每个 Pod 都有一个持久标识符,即使重新调度,它也会保持该标识符。
如果你想使用存储卷为工作负载提供持久性,可以将 StatefulSet 作为解决方案的一部分。尽管 StatefulSet 中的单个 Pod 可能会发生故障,但持久的 Pod 标识符使得将现有卷与替代任何已失败 Pod 的新 Pod 进行匹配变得更容易。
使用 StatefulSet
StatefulSet 对于需要以下一个或多个功能的应用程序很有价值。
- 稳定的、唯一的网络标识符。
- 稳定的、持久的存储。
- 有序、优雅的部署和扩缩。
- 有序、自动的滚动更新。
在上述内容中,稳定意味着在 Pod (重新)调度期间的持久性。如果应用程序不需要任何稳定标识符或有序部署、删除或扩缩,则应使用提供一组无状态副本的工作负载对象来部署应用程序。Deployment 或 ReplicaSet 可能更适合你的无状态需求。
限制
- 给定 Pod 的存储必须由 PersistentVolume Provisioner (此处提供示例)根据请求的 **Storage Class** 进行提供,或由管理员预先提供。
- 删除和/或缩减 StatefulSet 不会删除与 StatefulSet 关联的卷。这样做是为了确保数据安全,这通常比自动清除所有相关的 StatefulSet 资源更有价值。
- StatefulSet 当前需要一个 Headless Service 来负责 Pod 的网络身份。你负责创建此 Service。
- 当 StatefulSet 被删除时,StatefulSet 不提供任何 Pod 终止的保证。为了实现 StatefulSet 中 Pod 的有序且优雅终止,可以在删除之前将 StatefulSet 缩减到 0。
- 当使用默认的 Pod 管理策略(`OrderedReady`)进行滚动更新时,可能会进入需要手动干预才能修复的损坏状态。
组件
以下示例演示了 StatefulSet 的组件。
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 # has to match .spec.template.metadata.labels
serviceName: "nginx"
replicas: 3 # by default is 1
minReadySeconds: 10 # by default is 0
template:
metadata:
labels:
app: nginx # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.24
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
注意
为简单起见,此示例使用 `ReadWriteOnce` 访问模式。对于生产用途,Kubernetes 项目建议改用 `ReadWriteOncePod` 访问模式。在上面的示例中
- 一个名为 `nginx` 的 Headless Service 用于控制网络域。
- 名为 `web` 的 StatefulSet 有一个 Spec,它指示将在唯一的 Pod 中启动 3 个 nginx 容器副本。
- `volumeClaimTemplates` 将使用 PersistentVolume Provisioner 提供的 PersistentVolume 提供稳定的存储。
StatefulSet 对象的名称必须是有效的 DNS 标签。
Pod 选择器
你必须将 StatefulSet 的 `。spec.selector` 字段设置为与其 `。spec.template.metadata.labels` 的标签匹配。如果未指定匹配的 Pod 选择器,则在创建 StatefulSet 期间将导致验证错误。
卷声明模板
您可以设置 `。spec.volumeClaimTemplates` 字段来创建 PersistentVolumeClaim。这将为 StatefulSet 提供稳定的存储,如果
- 卷声明指定的 StorageClass 配置为使用 动态供应,或者
- 集群中已经包含一个具有正确 StorageClass 和足够可用存储空间的 PersistentVolume。
最小就绪秒数
Kubernetes v1.25 [稳定]
`。spec.minReadySeconds` 是一个可选字段,指定新创建的 Pod 在其任何容器未崩溃的情况下应运行并就绪的最小秒数,以便将其视为可用。这用于在使用 滚动更新 策略时检查 rollout 的进展。此字段默认为 0(Pod 在就绪后立即被视为可用)。要了解 Pod 何时被视为就绪,请参阅 容器探针。
Pod 身份
StatefulSet Pod 具有唯一的身份,包括序号、稳定的网络身份和稳定的存储。无论 Pod 重新调度到哪个节点,该身份都保持不变。
序号索引
对于具有 N 个副本的 StatefulSet,StatefulSet 中的每个 Pod 都将分配一个整数序号,该序号在整个集合中是唯一的。默认情况下,Pod 将被分配从 0 到 N-1 的序号。StatefulSet 控制器还将添加一个带有此索引的 Pod 标签:`apps.kubernetes.io/pod-index`。
起始序号
Kubernetes v1.31 [stable]
(默认启用:true)`。spec.ordinals` 是一个可选字段,允许您配置分配给每个 Pod 的整数序号。它默认为 nil。在该字段中,您可以配置以下选项
- `。spec.ordinals.start`:如果设置了 `。spec.ordinals.start` 字段,则 Pod 将被分配从 `。spec.ordinals.start` 到 `。spec.ordinals.start + 。spec.replicas - 1` 的序号。
稳定的网络 ID
StatefulSet 中的每个 Pod 都从 StatefulSet 的名称和 Pod 的序号派生其主机名。构造的主机名模式为 `$(statefulset name)-$(ordinal)`。上面的示例将创建三个名为 `web-0,web-1,web-2` 的 Pod。StatefulSet 可以使用 Headless Service 来控制其 Pod 的域。此 Service 管理的域的形式为:`$(service name).$(namespace).svc.cluster.local`,其中 "cluster.local" 是集群域。每个 Pod 创建时,都会获得一个匹配的 DNS 子域,其形式为:`$(podname).$(governing service domain)`,其中管理服务由 StatefulSet 上的 `serviceName` 字段定义。
根据集群中 DNS 的配置方式,你可能无法立即查找新运行的 Pod 的 DNS 名称。当集群中的其他客户端在 Pod 创建之前已经发送了针对 Pod 主机名的查询时,可能会发生此行为。负缓存(在 DNS 中正常)意味着即使在 Pod 运行后,也会记住并重用以前失败的查找结果,至少持续几秒钟。
如果你需要在 Pod 创建后立即发现它们,有以下几种选择
- 直接查询 Kubernetes API(例如,使用 watch),而不是依赖 DNS 查找。
- 减少 Kubernetes DNS 提供商的缓存时间(通常这意味着编辑 CoreDNS 的配置映射,CoreDNS 当前缓存 30 秒)。
正如限制部分所述,你负责创建负责 Pod 网络身份的无头服务。
以下是一些集群域、服务名称、StatefulSet 名称及其如何影响 StatefulSet Pod 的 DNS 名称的示例。
集群域 | 服务 (命名空间/名称) | StatefulSet (命名空间/名称) | StatefulSet 域 | Pod DNS | Pod 主机名 |
---|---|---|---|---|---|
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`。稳定的存储
对于 StatefulSet 中定义的每个 VolumeClaimTemplate 条目,每个 Pod 都会收到一个 PersistentVolumeClaim。在上面的 nginx 示例中,每个 Pod 都会收到一个 PersistentVolume,其 StorageClass 为 `my-storage-class`,并提供 1 GiB 的存储空间。如果未指定 StorageClass,则将使用默认的 StorageClass。当 Pod (重新)调度到节点时,其 `volumeMounts` 将挂载与其 PersistentVolume Claims 关联的 PersistentVolume。请注意,当 Pod 或 StatefulSet 被删除时,与 Pod 的 PersistentVolume Claims 关联的 PersistentVolume 不会被删除。这必须手动完成。
Pod 名称标签
当 StatefulSet 控制器创建 Pod 时,它会添加一个标签 `statefulset.kubernetes.io/pod-name`,该标签设置为 Pod 的名称。此标签允许您将 Service 附加到 StatefulSet 中的特定 Pod。
Pod 索引标签
Kubernetes v1.32 [stable]
(默认启用:true)当 StatefulSet 控制器创建 Pod 时,新 Pod 会被标记为 `apps.kubernetes.io/pod-index`。此标签的值是 Pod 的序数索引。此标签允许您将流量路由到特定的 Pod 索引,使用 Pod 索引标签筛选日志/指标等。请注意,对于此功能,功能门 `PodIndexLabel` 默认启用并锁定,为了禁用它,用户必须使用服务器模拟版本 v1.31。
部署和扩缩保证
- 对于具有 N 个副本的 StatefulSet,当 Pod 部署时,它们按 {0..N-1} 的顺序依次创建。
- 当 Pod 被删除时,它们按 {N-1..0} 的逆序终止。
- 在对 Pod 应用扩缩操作之前,其所有前驱必须处于运行和就绪状态。
- 在 Pod 终止之前,其所有后继必须完全关闭。
StatefulSet 不应将 `pod.Spec.TerminationGracePeriodSeconds` 设置为 0。这种做法不安全,强烈不建议。有关更多解释,请参阅 强制删除 StatefulSet Pod。
当创建上述 nginx 示例时,将按 web-0、web-1、web-2 的顺序部署三个 Pod。web-1 在 web-0 运行并就绪 之前不会部署,web-2 在 web-1 运行并就绪之前不会部署。如果 web-0 在 web-1 运行并就绪之后但在 web-2 启动之前失败,则 web-2 不会启动,直到 web-0 成功重新启动并变为运行并就绪。
如果用户通过修补 StatefulSet 将 `replicas=1` 来扩缩已部署的示例,则 web-2 将首先终止。web-1 在 web-2 完全关闭并删除之前不会终止。如果 web-0 在 web-2 已终止并完全关闭之后但在 web-1 终止之前失败,则 web-1 不会在 web-0 运行并就绪之前终止。
Pod 管理策略
StatefulSet 允许您通过其 `。spec.podManagementPolicy` 字段放宽其排序保证,同时保留其唯一性和身份保证。
有序就绪 Pod 管理
`OrderedReady` Pod 管理是 StatefulSet 的默认设置。它实现了上述行为。
并行 Pod 管理
`Parallel` Pod 管理指示 StatefulSet 控制器并行启动或终止所有 Pod,并且在启动或终止另一个 Pod 之前,不等待 Pod 变为 Running 和 Ready 或完全终止。此选项仅影响扩缩操作的行为。更新不受影响。
更新策略
StatefulSet 的 `。spec.updateStrategy` 字段允许您配置和禁用 StatefulSet 中 Pod 的容器、标签、资源请求/限制和注解的自动滚动更新。有两个可能的值
OnDelete
- 当 StatefulSet 的 `。spec.updateStrategy.type` 设置为 `OnDelete` 时,StatefulSet 控制器不会自动更新 StatefulSet 中的 Pod。用户必须手动删除 Pod,才能使控制器创建反映 StatefulSet 的 `。spec.template` 所做修改的新 Pod。
RollingUpdate
- `RollingUpdate` 更新策略实现 StatefulSet 中 Pod 的自动化滚动更新。这是默认的更新策略。
滚动更新
当 StatefulSet 的 `。spec.updateStrategy.type` 设置为 `RollingUpdate` 时,StatefulSet 控制器将删除并重新创建 StatefulSet 中的每个 Pod。它将按照 Pod 终止的相同顺序(从最大序数到最小序数)进行,每次更新一个 Pod。
Kubernetes 控制平面在更新其前身之前,会等待已更新的 Pod 变为 Running 和 Ready。如果你已设置 `。spec.minReadySeconds`(参见最小就绪秒数),控制平面会在 Pod 变为就绪后额外等待该时间量,然后才会继续进行。
分区滚动更新
通过指定 `。spec.updateStrategy.rollingUpdate.partition`,可以对 `RollingUpdate` 更新策略进行分区。如果指定了分区,则当 StatefulSet 的 `。spec.template` 更新时,所有序数大于或等于分区的 Pod 都将更新。所有序数小于分区的 Pod 将不会更新,并且即使它们被删除,也将以以前的版本重新创建。如果 StatefulSet 的 `。spec.updateStrategy.rollingUpdate.partition` 大于其 `。spec.replicas`,则对其 `。spec.template` 的更新将不会传播到其 Pod。在大多数情况下,你不需要使用分区,但如果你想分阶段更新、推出金丝雀版本或执行分阶段推出,它们会很有用。
最大不可用 Pod 数
你可以通过指定 `。spec.updateStrategy.rollingUpdate.maxUnavailable` 字段来控制更新期间最大不可用的 Pod 数量。该值可以是绝对数字(例如,`5`)或所需 Pod 数量的百分比(例如,`10%`)。绝对数字由百分比值向上取整计算。此字段不能为 0。默认设置为 1。
此字段适用于 `0` 到 `replicas - 1` 范围内的所有 Pod。如果 `0` 到 `replicas - 1` 范围内有任何不可用的 Pod,它将被计入 `maxUnavailable`。
注意
`maxUnavailable` 字段处于 Alpha 阶段,只有在启用 `MaxUnavailableStatefulSet` 功能门 的 API 服务器上运行的 API 服务器才会遵守它。强制回滚
当使用默认的 Pod 管理策略(`OrderedReady`)进行滚动更新时,可能会进入需要手动干预才能修复的损坏状态。
如果你将 Pod 模板更新到永远不会变为 Running 和 Ready 的配置(例如,由于糟糕的二进制文件或应用程序级配置错误),StatefulSet 将停止推出并等待。
在这种状态下,仅仅将 Pod 模板恢复到良好配置是不够的。由于存在一个已知问题,StatefulSet 将继续等待损坏的 Pod 变为 Ready(这永远不会发生),然后才会尝试将其恢复到工作配置。
恢复模板后,您还必须删除 StatefulSet 已尝试使用错误配置运行的任何 Pod。然后,StatefulSet 将开始使用恢复的模板重新创建 Pod。
PersistentVolumeClaim 保留
Kubernetes v1.32 [stable]
(默认启用:true)可选的 `。spec.persistentVolumeClaimRetentionPolicy` 字段控制在 StatefulSet 的生命周期中是否以及如何删除 PVC。您必须在 API 服务器和控制器管理器上启用 `StatefulSetAutoDeletePVC` 功能门 才能使用此字段。启用后,您可以为每个 StatefulSet 配置两种策略
whenDeleted
- 配置当 StatefulSet 被删除时应用的卷保留行为
whenScaled
- 配置当 StatefulSet 的副本计数减少时应用的卷保留行为;例如,当缩减集合时。
对于您可以配置的每个策略,您可以将值设置为 `Delete` 或 `Retain`。
删除
- 从 StatefulSet `volumeClaimTemplate` 创建的 PVC 会因受策略影响的每个 Pod 而被删除。使用 `whenDeleted` 策略,所有来自 `volumeClaimTemplate` 的 PVC 在其 Pod 被删除后都会被删除。使用 `whenScaled` 策略,只有与正在缩减的 Pod 副本对应的 PVC 在其 Pod 被删除后才会被删除。
- `Retain`(默认)
- 从 `volumeClaimTemplate` 创建的 PVC 在其 Pod 被删除时不受影响。这是此新功能之前的行为。
请记住,这些策略**仅**适用于因 StatefulSet 被删除或缩减而移除 Pod 时。例如,如果与 StatefulSet 关联的 Pod 因节点故障而失败,并且控制平面创建了一个替换 Pod,则 StatefulSet 会保留现有的 PVC。现有卷不受影响,集群会将其附加到新 Pod 即将启动的节点上。
策略的默认值为 `Retain`,与此新功能之前的 StatefulSet 行为一致。
这是一个示例策略。
apiVersion: apps/v1
kind: StatefulSet
...
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain
whenScaled: Delete
...
StatefulSet 控制器为其 PVC 添加所有者引用,然后这些 PVC 在 Pod 终止后由垃圾收集器删除。这使得 Pod 可以在 PVC 删除(以及根据保留策略,在底层 PV 和卷删除之前)之前干净地卸载所有卷。当您将 `whenDeleted` 策略设置为 `Delete` 时,与该 StatefulSet 关联的所有 PVC 都会放置对 StatefulSet 实例的所有者引用。
`whenScaled` 策略必须仅在 Pod 缩减时删除 PVC,而不是在 Pod 因其他原因删除时删除。在协调时,StatefulSet 控制器会将其期望的副本计数与集群中实际存在的 Pod 进行比较。任何 ID 大于副本计数的 StatefulSet Pod 都将被谴责并标记为删除。如果 `whenScaled` 策略是 `Delete`,则被谴责的 Pod 在删除之前,首先会被设置为关联的 StatefulSet 模板 PVC 的所有者。这会导致 PVC 在仅被谴责的 Pod 终止后被垃圾回收。
这意味着如果控制器崩溃并重启,在所有者引用根据策略得到适当更新之前,任何 Pod 都不会被删除。如果一个被谴责的 Pod 在控制器停机期间被强制删除,所有者引用可能已经设置,也可能尚未设置,具体取决于控制器何时崩溃。更新所有者引用可能需要几个协调循环,因此一些被谴责的 Pod 可能已经设置了所有者引用,而另一些则可能没有。因此,我们建议等待控制器恢复,这将验证所有者引用,然后再终止 Pod。如果不可能,操作员应验证 PVC 上的所有者引用,以确保在强制删除 Pod 时删除预期对象。
副本
`。spec.replicas` 是一个可选字段,指定所需的 Pod 数量。它默认为 1。
如果您手动扩缩部署,例如通过 `kubectl scale statefulset statefulset --replicas=X`,然后根据清单更新该 StatefulSet(例如:通过运行 `kubectl apply -f statefulset.yaml`),则应用该清单将覆盖您之前手动执行的扩缩操作。
如果 HorizontalPodAutoscaler(或任何类似的水平扩缩 API)正在管理 StatefulSet 的扩缩,请不要设置 `。spec.replicas`。相反,让 Kubernetes 控制平面 自动管理 `。spec.replicas` 字段。
下一步
- 了解Pod。
- 了解如何使用 StatefulSet
- 遵循部署有状态应用程序的示例。
- 遵循使用 StatefulSet 部署 Cassandra 的示例。
- 遵循运行复制有状态应用程序的示例。
- 了解如何扩缩 StatefulSet。
- 了解删除 StatefulSet 时涉及哪些内容。
- 了解如何配置 Pod 以使用卷进行存储。
- 了解如何配置 Pod 以使用 PersistentVolume 进行存储。
- `StatefulSet` 是 Kubernetes REST API 中的一个顶级资源。阅读 StatefulSet 对象定义以了解有状态集合的 API。
- 了解 PodDisruptionBudget 以及如何使用它来管理中断期间的应用程序可用性。