StatefulSet

StatefulSet 运行一组 Pod,并为每个 Pod 维护一个固定的身份。这对于需要持久存储或稳定且唯一的网络身份的应用非常有用。

StatefulSet 是用于管理有状态应用的负载 API 对象。

管理一组 Pod 的部署和扩缩,并保证这些 Pod 的顺序和唯一性

Deployment 一样,StatefulSet 管理基于相同容器 Spec 的 Pod。与 Deployment 不同的是,StatefulSet 为其每个 Pod 维护一个固定的身份。这些 Pod 是从相同的 Spec 创建的,但不可互换:每个 Pod 都具有持久的标识符,无论如何重新调度,它都会保持此标识符。

如果要使用存储卷为工作负载提供持久性,可以将 StatefulSet 用作解决方案的一部分。尽管 StatefulSet 中的单个 Pod 容易发生故障,但持久的 Pod 标识符使得更容易将现有卷与替换故障 Pod 的新 Pod 进行匹配。

使用 StatefulSet

StatefulSet 对于需要以下一项或多项功能的应用非常有用。

  • 稳定、唯一的网络标识符。
  • 稳定、持久的存储。
  • 有序、优雅的部署和扩缩。
  • 有序、自动化的滚动更新。

在上面,稳定与 Pod (重新)调度的持久性同义。如果应用不需要任何稳定的标识符或有序的部署、删除或扩缩,则应该使用提供无状态副本集合的工作负载对象来部署应用。 DeploymentReplicaSet 可能更适合你的无状态需求。

限制

  • 给定 Pod 的存储必须基于所请求的 存储类(storage class)PersistentVolume Provisioner 进行供应(示例在此),或者由管理员预先供应。
  • 删除和/或缩减 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

在以上示例中

  • 一个名为 nginx 的 Headless Service 用于控制网络域。
  • 名为 web 的 StatefulSet 有一个 Spec,指示将在唯一的 Pod 中启动 3 个 nginx 容器副本。
  • volumeClaimTemplates 将使用由 PersistentVolume Provisioner 供应的 PersistentVolumes 提供稳定的存储。

StatefulSet 对象的名称必须是有效的 DNS 标签

Pod Selector

必须将 StatefulSet 的 .spec.selector 字段设置为与其 .spec.template.metadata.labels 的标签相匹配。未能指定匹配的 Pod Selector 将导致在创建 StatefulSet 时出现验证错误。

卷申领模板 (Volume Claim Templates)

可以设置 .spec.volumeClaimTemplates 字段来创建 PersistentVolumeClaim。这将为 StatefulSet 提供稳定的存储,如果满足以下任一条件:

  • 为卷申领指定的 StorageClass 设置为使用动态供应,或者
  • 集群中已经包含具有正确 StorageClass 和足够可用存储空间的 PersistentVolume。

最小就绪秒数

特性状态: Kubernetes v1.25 [stable]

.spec.minReadySeconds 是一个可选字段,它指定新创建的 Pod 运行并就绪而没有任何容器崩溃的最小秒数,这样才算可用。在使用滚动更新策略时,这用于检查上线进度。此字段默认为 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] (enabled by default: 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),其中 governing service 由 StatefulSet 上的 serviceName 字段定义。

根据集群中 DNS 的配置方式,可能无法立即查找新运行的 Pod 的 DNS 名称。当集群中的其他客户端在 Pod 创建之前已经发送了对 Pod 主机名的查询时,可能会发生此行为。负缓存(DNS 中的正常现象)意味着即使 Pod 正在运行,之前失败查找的结果也会被记住并重用,至少持续几秒钟。

如果需要在 Pod 创建后立即发现它们,有几种选择:

  • 直接查询 Kubernetes API(例如,使用 watch),而不是依赖 DNS 查找。
  • 减少 Kubernetes DNS 提供者中的缓存时间(通常这意味着编辑 CoreDNS 的 ConfigMap,CoreDNS 当前缓存 30 秒)。

限制部分所述,你有责任创建负责 Pod 网络身份的 Headless Service

以下是一些集群域、Service 名称、StatefulSet 名称以及它们如何影响 StatefulSet 的 Pod DNS 名称的示例。

集群域 (Cluster Domain)Service (命名空间/名称)StatefulSet (命名空间/名称)StatefulSet 域Pod DNSPod 主机名
cluster.localdefault/nginxdefault/webnginx.default.svc.cluster.localweb-{0..N-1}.nginx.default.svc.cluster.localweb-{0..N-1}
cluster.localfoo/nginxfoo/webnginx.foo.svc.cluster.localweb-{0..N-1}.nginx.foo.svc.cluster.localweb-{0..N-1}
kube.localfoo/nginxfoo/webnginx.foo.svc.kube.localweb-{0..N-1}.nginx.foo.svc.kube.localweb-{0..N-1}

稳定存储

对于 StatefulSet 中定义的每个 VolumeClaimTemplate 条目,每个 Pod 都会获得一个 PersistentVolumeClaim。在上面的 nginx 示例中,每个 Pod 都会获得一个 StorageClass 为 my-storage-class 且已供应 1 GiB 存储空间的 PersistentVolume。如果没有指定 StorageClass,则将使用默认的 StorageClass。当 Pod 被(重新)调度到节点上时,其 volumeMounts 将挂载与其 PersistentVolume Claim 关联的 PersistentVolumes。请注意,与 Pod 的 PersistentVolume Claim 关联的 PersistentVolumes 在 Pod 或 StatefulSet 被删除时不会被删除。这必须手动完成。

Pod 名称标签

当 StatefulSet 控制器创建 Pod 时,它会添加一个标签 statefulset.kubernetes.io/pod-name,该标签的值设置为 Pod 的名称。此标签允许将 Service 附加到 StatefulSet 中的特定 Pod。

Pod 索引标签

特性状态: Kubernetes v1.32 [stable] (enabled by default: 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 应用扩缩操作之前,其所有前驱必须处于 Running 和 Ready 状态。
  • 在终止 Pod 之前,其所有后继必须完全关闭。

StatefulSet 不应指定 pod.Spec.TerminationGracePeriodSeconds 为 0。这种做法是不安全的,强烈不建议这样做。有关进一步说明,请参阅强制删除 StatefulSet Pod

创建上述 nginx 示例时,将按 web-0、web-1、web-2 的顺序部署三个 Pod。web-1 不会在 web-0 处于Running 和 Ready 状态之前部署,web-2 不会在 web-1 处于 Running 和 Ready 状态之前部署。如果 web-0 在 web-1 处于 Running 和 Ready 状态后、但在 web-2 启动之前发生故障,则 web-2 将不会启动,直到 web-0 成功重新启动并处于 Running 和 Ready 状态为止。

如果用户通过 Patch StatefulSet 将 replicas=1 来缩减已部署的示例,则 web-2 将首先终止。web-1 不会在 web-2 完全关闭并删除之前终止。如果 web-0 在 web-2 已终止并完全关闭后但在 web-1 终止之前发生故障,则 web-1 将不会终止,直到 web-0 处于 Running 和 Ready 状态为止。

Pod 管理策略

StatefulSet 允许你通过 .spec.podManagementPolicy 字段在保留其唯一性和身份保证的同时放宽其顺序保证。

OrderedReady 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 数

特性状态: Kubernetes v1.24 [alpha]

通过指定 .spec.updateStrategy.rollingUpdate.maxUnavailable 字段,可以控制更新期间不可用的 Pod 的最大数量。该值可以是绝对数字(例如,5)或期望 Pod 数的百分比(例如,10%)。绝对数字是通过百分比值向上取整计算得出。此字段不能为 0。默认设置为 1。

此字段适用于范围 0replicas - 1 中的所有 Pod。如果在范围 0replicas - 1 中有任何不可用的 Pod,它将被计入 maxUnavailable

强制回滚

当使用默认的 Pod 管理策略OrderedReady)进行滚动更新时,可能会陷入损坏状态,需要手动干预进行修复

如果将 Pod 模板更新为永远不会达到 Running 和 Ready 状态的配置(例如,由于错误的二进制文件或应用级配置错误),StatefulSet 将停止上线并等待。

在此状态下,仅将 Pod 模板恢复到良好配置是不够的。由于存在已知问题,StatefulSet 将继续等待损坏的 Pod 变为 Ready(这永远不会发生),然后才会尝试将其恢复到工作配置。(Preserved link)

恢复模板后,还必须删除 StatefulSet 已经尝试使用不良配置运行的任何 Pod。然后,StatefulSet 将开始使用已恢复的模板重新创建 Pod。

PersistentVolumeClaim 保留策略

特性状态: Kubernetes v1.32 [stable] (enabled by default: true)

可选字段 .spec.persistentVolumeClaimRetentionPolicy 控制在 StatefulSet 生命周期中是否以及如何删除 PVC。必须在 API 服务器和控制器管理器上启用 StatefulSetAutoDeletePVC 特性门控 才能使用此字段。启用后,可以为每个 StatefulSet 配置两个策略:

whenDeleted
配置在 StatefulSet 被删除时应用的卷保留行为
whenScaled
配置在 StatefulSet 的副本数减少时应用的卷保留行为;例如,缩减 StatefulSet 时。

对于可以配置的每个策略,可以将值设置为 DeleteRetain

Delete
对于受策略影响的每个 Pod,根据 StatefulSet volumeClaimTemplate 创建的 PVC 将被删除。使用 whenDeleted 策略,来自 volumeClaimTemplate 的所有 PVC 在其 Pod 被删除后都会被删除。使用 whenScaled 策略,只有与正在缩减的 Pod 副本对应的 PVC 会在其 Pod 被删除后被删除。
Retain (默认)
当 Pod 被删除时,来自 volumeClaimTemplate 的 PVC 不受影响。这是此新特性之前的行为。

请记住,这些策略仅在由于 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 的所有者。这会导致仅在被淘汰的 Pod 终止后,这些 PVC 才会被垃圾收集。

这意味着如果控制器崩溃并重启,在根据策略适当更新其所有者引用之前,不会删除任何 Pod。如果在控制器停止运行时强制删除了一个待删除的 Pod,那么其所有者引用可能已设置,也可能未设置,具体取决于控制器何时崩溃。更新所有者引用可能需要多个协调周期,因此一些待删除的 Pod 可能已设置了所有者引用,而另一些则没有。因此,我们建议等待控制器重新启动,它会在终止 Pod 之前验证所有者引用。如果无法做到这一点,操作员应验证 PVC 上的所有者引用,以确保当 Pod 被强制删除时,预期的对象也会被删除。

副本数

.spec.replicas 是一个可选字段,它指定了期望的 Pod 数量。默认为 1。

如果你手动扩缩了 StatefulSet,例如通过 kubectl scale statefulset statefulset --replicas=X,然后你基于 manifest 更新了该 StatefulSet(例如:通过运行 kubectl apply -f statefulset.yaml),那么应用该 manifest 将会覆盖你之前手动执行的扩缩操作。

如果一个 HorizontalPodAutoscaler (或任何类似的水平扩缩 API) 正在管理 StatefulSet 的扩缩,请不要设置 .spec.replicas。相反,让 Kubernetes 控制平面自动管理 .spec.replicas 字段。

接下来

最后修改时间:太平洋标准时间 2024 年 10 月 22 日 上午 10:38:添加 KEP 4017 PodIndexLabel GA 的文档 (e73dcd94c6)