本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
Kubernetes 1.24:StatefulSet 的最大不可用副本数
Kubernetes StatefulSet 自 1.5 版本引入并在 1.9 版本稳定以来,已被广泛用于运行有状态应用。它们提供稳定的 Pod 标识、每个 Pod 的持久化存储以及有序的优雅部署、扩缩和滚动更新。你可以将 StatefulSet 视为运行复杂有状态应用的原子构建块。随着 Kubernetes 的使用越来越广泛,需要 StatefulSet 的场景也越来越多。其中许多场景需要比当前支持的“一次一个 Pod”更新更快的滚动更新,特别是在你为 StatefulSet 使用 OrderedReady
Pod 管理策略时。
以下是一些例子
我正在使用 StatefulSet 来编排一个多实例、基于缓存的应用,其中缓存的规模很大。缓存从冷启动开始,需要相当长的时间才能启动容器。可能还需要执行更多初始启动任务。对此 StatefulSet 进行滚动更新(RollingUpdate)会花费大量时间才能完全更新应用。如果 StatefulSet 支持一次更新多个 Pod,更新速度会快得多。
我的有状态应用由领导者和跟随者,或者一个写入者和多个读取者组成。我有多个读取者或跟随者,我的应用可以容忍多个 Pod 同时宕机。我希望一次更新多个 Pod,以便快速推出新的更新,特别是当我的应用实例数量很大时。请注意,我的应用仍然需要每个 Pod 具有唯一的标识。
为了支持这类场景,Kubernetes 1.24 引入了一个新的 Alpha 功能。在使用这个新功能之前,你必须启用 MaxUnavailableStatefulSet
特性门控。启用后,你可以在 StatefulSet 的 spec
中指定一个新字段 maxUnavailable
。例如:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: default
spec:
podManagementPolicy: OrderedReady # you must set OrderedReady
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
# image changed since publication (previously used registry "k8s.gcr.io")
- image: registry.k8s.io/nginx-slim:0.8
imagePullPolicy: IfNotPresent
name: nginx
updateStrategy:
rollingUpdate:
maxUnavailable: 2 # this is the new alpha field, whose default value is 1
partition: 0
type: RollingUpdate
如果你启用了这个新功能,但没有在 StatefulSet 中为 maxUnavailable
指定值,Kubernetes 会应用默认值 maxUnavailable: 1
。这与未启用新功能时的行为一致。
我将通过一个基于该示例清单的场景来演示此功能的工作原理。我将部署一个 StatefulSet,它有 5 个副本,maxUnavailable
设置为 2,partition
设置为 0。
我可以通过将镜像更改为 registry.k8s.io/nginx-slim:0.9
来触发滚动更新。一旦我启动滚动更新,我可以观察到 Pod 每次更新 2 个,因为当前 maxUnavailable 的值为 2。以下输出显示了一段时间内的情况,并不完整。maxUnavailable 可以是一个绝对数(例如 2),也可以是所需 Pods 的百分比(例如 10%)。绝对数是通过将百分比向上取整到最接近的整数计算得出的。
kubectl get pods --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 85s
web-1 1/1 Running 0 2m6s
web-2 1/1 Running 0 106s
web-3 1/1 Running 0 2m47s
web-4 1/1 Running 0 2m27s
web-4 1/1 Terminating 0 5m43s ----> start terminating 4
web-3 1/1 Terminating 0 6m3s ----> start terminating 3
web-3 0/1 Terminating 0 6m7s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-4 0/1 Terminating 0 5m48s
web-4 0/1 Terminating 0 5m48s
web-3 0/1 ContainerCreating 0 2s
web-3 1/1 Running 0 2s
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 0s
web-4 0/1 ContainerCreating 0 0s
web-4 1/1 Running 0 1s
web-2 1/1 Terminating 0 5m46s ----> start terminating 2 (only after both 4 and 3 are running)
web-1 1/1 Terminating 0 6m6s ----> start terminating 1
web-2 0/1 Terminating 0 5m47s
web-1 0/1 Terminating 0 6m7s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 2s
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
web-0 1/1 Terminating 0 6m6s ----> start terminating 0 (only after 2 and 1 are running)
web-0 0/1 Terminating 0 6m7s
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 1s
请注意,滚动更新一开始,序号最高的两个 Pod 4 和 3 会同时开始终止。序号为 4 和 3 的 Pod 可能会按各自的节奏就绪。一旦 Pod 4 和 3 都就绪,Pod 2 和 1 就会同时开始终止。当 Pod 2 和 1 都运行并就绪后,Pod 0 开始终止。
在 Kubernetes 中,StatefulSet 的更新在更新 Pod 时遵循严格的顺序。在这个例子中,更新从副本 4 开始,然后是副本 3,接着是副本 2,依此类推,一次一个 Pod。当一次只更新一个 Pod 时,3 不可能在 4 之前运行并就绪。当 maxUnavailable
大于 1 时(在示例场景中,我将 maxUnavailable
设置为 2),副本 3 可能会在副本 4 就绪之前就绪并运行——这是可以的。如果你是开发人员,并且将 maxUnavailable
设置为大于 1,你应该知道这种结果是可能发生的,并且你必须确保你的应用能够处理任何可能出现的此类排序问题。当你将 maxUnavailable
设置为大于 1 时,更新顺序在每批 Pod 之间是有保证的。这个保证意味着更新批次 2(副本 2 和 1)中的 Pod 在批次 0(副本 4 和 3)的 Pods 就绪之前不能开始更新。
尽管 Kubernetes 将这些称为**副本(replicas)**,但你的有状态应用可能有不同的视角,StatefulSet 的每个 Pod 可能持有与其他 Pod 完全不同的数据。这里的重点是,StatefulSet 的更新是分批进行的,现在你可以拥有大于 1 的批次大小(作为一个 Alpha 功能)。
另请注意,上述行为是在 podManagementPolicy: OrderedReady
的情况下。如果你将 StatefulSet 定义为 podManagementPolicy: Parallel
,不仅会有 maxUnavailable
数量的副本同时被终止,同样数量的副本也会同时进入 ContainerCreating
阶段。这被称为突发(bursting)。
那么,现在你可能会有很多关于以下方面的问题:
- 当你设置
podManagementPolicy: Parallel
时,行为是怎样的? - 当
partition
设置为非0
的值时,行为又是怎样的?
最好还是亲自尝试一下。这是一个 Alpha 功能,Kubernetes 的贡献者们正在寻求对此功能的反馈。这个功能是否帮助你实现了你的有状态场景?你是否发现了 bug,或者你认为当前的实现行为不直观,可能会破坏应用或让它们措手不及?请提交一个 issue 让我们知道。