这篇文章已超过一年。较旧的文章可能包含过时内容。请检查页面中的信息自发布以来是否已不再准确。
Kubernetes 1.24: StatefulSet 的最大不可用副本数
Kubernetes StatefulSets 自 1.5 版本引入并在 1.9 版本中达到稳定以来,已被广泛用于运行有状态应用。它们提供稳定的 Pod 身份、每个 Pod 持久存储以及有序的平滑部署、伸缩和滚动更新。你可以将 StatefulSet 视为运行复杂有状态应用的基本构建块。随着 Kubernetes 的使用日益增长,需要 StatefulSets 的场景也越来越多。许多这些场景要求比当前支持的“一次一个 Pod”更新模式(在使用 StatefulSet 的 OrderedReady
Pod 管理策略时)更快的滚动更新。
以下是一些示例:
我正在使用 StatefulSet 来编排一个基于缓存的多实例应用,该应用的缓存很大。缓存开始时是冷的,需要相当长的时间才能使容器启动。可能还需要其他一些初始启动任务。对这个 StatefulSet 进行滚动更新会花费大量时间才能完全更新应用。如果 StatefulSet 支持一次更新多个 Pod,将显著加快更新速度。
我的有状态应用由主副本和从副本(或一个写入者和多个读取者)组成。我有多个读取者或从副本,并且我的应用可以容忍多个 Pod 同时宕机。我希望一次更新该应用的多个 Pod,以便快速推出新更新,尤其是在我的应用实例数量很多的情况下。请注意,我的应用仍然需要每个 Pod 具有唯一身份。
为了支持这些场景,Kubernetes 1.24 包含了一个新的 Alpha 特性。在使用新特性之前,必须启用 MaxUnavailableStatefulSet
feature flag。启用后,你可以指定一个新字段 maxUnavailable
,它是 StatefulSet 的 spec
的一部分。例如:
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
。这与你不启用新特性时看到的行为一致。
我将基于该示例清单演示一个场景来展示此特性如何工作。我将部署一个有 5 个副本的 StatefulSet,并将 maxUnavailable
设置为 2,partition
设置为 0。
我可以通过将镜像更改为 registry.k8s.io/nginx-slim:0.9
来触发滚动更新。启动滚动更新后,我将观察 Pods 每次更新 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
请注意,滚动更新一开始,4 和 3(序数最高的两个 Pod)就同时开始终止。序数为 4 和 3 的 Pods 可能以自己的速度准备就绪。一旦 Pods 4 和 3 都准备就绪,Pods 2 和 1 同时开始终止。当 Pods 2 和 1 都运行并准备就绪时,Pod 0 开始终止。
在 Kubernetes 中,更新 StatefulSets 时遵循严格的 Pod 更新顺序。在这个示例中,更新从副本 4 开始,然后副本 3,然后副本 2,依此类推,一次一个 Pod。一次一个 Pod 时,3 不可能在 4 之前就已运行并准备就绪。当 maxUnavailable
大于 1(在示例场景中我将 maxUnavailable
设置为 2)时,副本 3 可能在副本 4 准备就绪之前就已准备并运行——这是允许的。如果你是开发者,并将 maxUnavailable
设置为大于 1,你应该知道这种情况是可能发生的,并且必须确保你的应用能够处理由此可能引起的排序问题(如果存在)。当你将 maxUnavailable
设置为大于 1 时,在每个批次更新的 Pod 之间保证顺序。这个保证意味着第二批次更新的 Pods(副本 2 和 1)不能开始更新,直到第一批次(副本 4 和 3)的 Pods 准备就绪。
尽管 Kubernetes 将这些称为*副本*,但你的有状态应用可能有不同的视图,并且 StatefulSet 的每个 Pod 可能持有完全不同的数据。这里的重点是,StatefulSet 的更新是分批进行的,你现在可以拥有大于 1 的批次大小(作为 Alpha 特性)。
另请注意,上述行为是在 podManagementPolicy: OrderedReady
下的。如果你将 StatefulSet 定义为 podManagementPolicy: Parallel
,不仅 maxUnavailable
数量的副本会同时终止;maxUnavailable
数量的副本也会同时进入 ContainerCreating
阶段。这称为突发 (bursting)。
那么,你现在可能有很多疑问,例如:-
- 将
podManagementPolicy
设置为Parallel
时的行为是什么? - 将
partition
设置为非0
值时的行为是什么?
最好亲手尝试一下。这是一个 Alpha 特性,Kubernetes 贡献者正在寻求关于此特性的反馈。这是否帮助你实现了你的有状态应用场景?你是否发现了 Bug?你是否认为已实现的行为不直观,可能会破坏应用或让你感到意外?请提交 issue 让我们知道。