在 Kubernetes 集群中,节点可以以计划中的优雅方式关闭,也可能因为断电或其他外部原因意外关闭。如果节点在关闭前没有执行驱逐(drain),节点关闭可能会导致工作负载失败。节点关闭可以是优雅的,也可以是非优雅的。
Debian 的 unattended-upgrades 软件包在其常规配置下会与节点的优雅关闭发生冲突。如果您使用了 unattended-upgrades 的默认配置(它会自定义服务器关闭的宽限期),那么 kubelet 将无法获得正确处理关闭事件所需的锁。
如果 shutdownGracePeriod 的值大于 30 秒,就会发生这种情况。要避免此问题,可以通过将 /etc/systemd/logind.conf.d/unattended-upgrades-logind-maxdelay.conf 创建为指向 /dev/null 的符号链接,从而禁用部分 unattended-upgrades 配置。
有关更多详细信息,请参阅 logind.conf 文档。
kubelet 会尝试检测节点系统的关闭,并终止在节点上运行的 Pod。
Kubelet 确保 Pod 在节点关闭期间遵循正常的 Pod 终止过程。在节点关闭期间,kubelet 不会接受新的 Pod(即使这些 Pod 已经绑定到该节点)。
Kubernetes v1.21 [beta] (默认启用)在 Linux 上,优雅的节点关闭特性由 GracefulNodeShutdown 特性门控控制,该门控在 1.21 版本中默认启用。
Kubernetes v1.34 [beta] (默认启用)在 Windows 上,优雅的节点关闭特性由 WindowsGracefulNodeShutdown 特性门控控制,该特性在 1.32 版本中作为 alpha 特性引入。在 Kubernetes 1.34 中,该特性已达到 Beta 阶段并默认启用。
Windows 优雅节点关闭无法取消。
如果 kubelet 不是作为 Windows 服务运行,它将无法设置和监控 Preshutdown 事件,节点将必须通过上述的非优雅节点关闭流程。
在启用了 Windows 优雅节点关闭特性,但 kubelet 未作为 Windows 服务运行的情况下,kubelet 将继续运行而不是失败。但是,它会记录一条错误,指示需要以 Windows 服务形式运行。
请注意,默认情况下,下述两个配置选项 shutdownGracePeriod 和 shutdownGracePeriodCriticalPods 均设置为零,因此不会激活优雅的节点关闭功能。要激活该特性,两个选项都应进行适当配置并设置为非零值。
一旦 kubelet 被通知节点将要关闭,它会为节点设置一个 NotReady 条件,并将 reason 设置为 "node is shutting down"。kube-scheduler 会遵循此条件,不会将任何 Pod 调度到受影响的节点上;预计其他第三方调度程序也会遵循相同的逻辑。这意味着新 Pod 不会被调度到该节点上,因此也不会启动。
在检测到正在进行的节点关闭时,kubelet 也会在 PodAdmission 阶段拒绝 Pod,因此即使是具有针对 node.kubernetes.io/not-ready:NoSchedule 的容忍度的 Pod 也不会在那里启动。
当 kubelet 通过 API 在其节点上设置该条件时,kubelet 还会开始终止本地运行的所有 Pod。
在优雅关闭期间,kubelet 分两个阶段终止 Pod:
优雅的节点关闭特性通过两个 KubeletConfiguration 选项进行配置:
shutdownGracePeriod:
指定节点应延迟关闭的总持续时间。这是常规 Pod 和关键 Pod 终止的总宽限期。
shutdownGracePeriodCriticalPods:
指定在节点关闭期间用于终止关键 Pod 的持续时间。此值应小于 shutdownGracePeriod。
Ready 状态。然而,已经开始终止过程的 Pod 不会被 kubelet 恢复,需要重新调度。例如,如果 shutdownGracePeriod=30s 且 shutdownGracePeriodCriticalPods=10s,kubelet 将把节点关闭延迟 30 秒。在关闭期间,前 20 秒 (30-10) 将保留用于优雅地终止常规 Pod,后 10 秒将保留用于终止关键 Pod。
当 Pod 在优雅节点关闭期间被驱逐时,它们被标记为已关闭。运行 kubectl get pods 会将已驱逐 Pod 的状态显示为 Terminated。kubectl describe pod 则会指明该 Pod 是因为节点关闭而被驱逐。
Reason: Terminated
Message: Pod was terminated in response to imminent node shutdown.
Kubernetes v1.24 [beta] (默认启用)为了在优雅的节点关闭期间提供更灵活的 Pod 关闭顺序,如果您的集群启用了该特性,则优雅的节点关闭将遵循 Pod 的 PriorityClass。此特性允许集群管理员根据优先级类明确定义优雅节点关闭期间 Pod 的关闭顺序。
优雅的节点关闭特性(如上所述)分两个阶段关闭 Pod:非关键 Pod,然后是关键 Pod。如果需要更细粒度地明确定义关闭期间的 Pod 顺序,可以使用基于 Pod 优先级的优雅关闭。
当优雅节点关闭遵循 Pod 优先级时,可以分多个阶段执行优雅节点关闭,每个阶段关闭特定优先级类的 Pod。可以为 kubelet 配置具体的阶段以及每个阶段的关闭时间。
假设集群中存在以下自定义 Pod 优先级类:
| Pod 优先级类名称 | Pod 优先级类值 |
|---|---|
custom-class-a | 100000 |
custom-class-b | 10000 |
custom-class-c | 1000 |
regular/unset | 0 |
在 kubelet 配置中,shutdownGracePeriodByPodPriority 的设置可能如下所示:
| Pod 优先级类值 | 关闭周期 |
|---|---|
| 100000 | 10 秒 |
| 10000 | 180 秒 |
| 1000 | 120 秒 |
| 0 | 60 秒 |
对应的 kubelet YAML 配置如下:
shutdownGracePeriodByPodPriority:
- priority: 100000
shutdownGracePeriodSeconds: 10
- priority: 10000
shutdownGracePeriodSeconds: 180
- priority: 1000
shutdownGracePeriodSeconds: 120
- priority: 0
shutdownGracePeriodSeconds: 60
上表意味着任何 priority 值 >= 100000 的 Pod 将仅获得 10 秒的关闭时间,任何值 >= 10000 且 < 100000 的 Pod 将获得 180 秒,任何值 >= 1000 且 < 10000 的 Pod 将获得 120 秒。最后,所有其他 Pod 将获得 60 秒的关闭时间。
无需指定所有类对应的值。例如,您可以改为使用以下设置:
| Pod 优先级类值 | 关闭周期 |
|---|---|
| 100000 | 300 秒 |
| 1000 | 120 秒 |
| 0 | 60 秒 |
在上述情况下,具有 custom-class-b 的 Pod 将与 custom-class-c 的 Pod 进入同一个关闭桶(bucket)。
如果特定范围内没有 Pod,则 kubelet 不会等待该优先级范围内的 Pod,而是会立即跳到下一个优先级类值范围。
如果启用了此特性但未提供配置,则不会采取任何排序操作。
使用此特性需要启用 GracefulNodeShutdownBasedOnPodPriority 特性门控,并在 kubelet 配置中将 ShutdownGracePeriodByPodPriority 设置为包含 Pod 优先级类值及其各自关闭周期的所需配置。
指标 graceful_shutdown_start_time_seconds 和 graceful_shutdown_end_time_seconds 在 kubelet 子系统中发布,用于监控节点关闭。
Kubernetes v1.28 [stable] (默认启用)kubelet 的节点关闭管理器(Node Shutdown Manager)可能无法检测到节点关闭操作,原因要么是该命令未触发 kubelet 使用的抑制锁机制,要么是因为用户错误(例如,ShutdownGracePeriod 和 ShutdownGracePeriodCriticalPods 配置不当)。有关详细信息,请参阅上面的优雅的节点关闭部分。
当节点关闭但未被 kubelet 的节点关闭管理器检测到时,属于 StatefulSet 的 Pod 将会卡在关闭节点上的终止状态,且无法移动到新的运行节点。这是因为关闭节点上的 kubelet 不可用,无法删除这些 Pod,因此 StatefulSet 无法创建同名的新 Pod。如果这些 Pod 使用了卷,VolumeAttachments 将不会从原始关闭节点上删除,因此这些 Pod 使用的卷无法挂载到新的运行节点上。结果是,在该 StatefulSet 上运行的应用程序将无法正常工作。如果原始关闭节点恢复,kubelet 将删除这些 Pod,并会在不同的运行节点上创建新 Pod。如果原始关闭节点不再恢复,这些 Pod 将永远卡在关闭节点上的终止状态。
为了缓解上述情况,用户可以手动为节点添加 node.kubernetes.io/out-of-service 污点(设置 NoExecute 或 NoSchedule 效果),将其标记为“服务外(out-of-service)”。如果节点被标记为具有此污点,则如果节点上不存在匹配的容忍度,节点上的 Pod 将被强制删除,并且正在终止的 Pod 的卷卸载操作将立即发生。这使得服务外节点上的 Pod 能够迅速在其他节点上恢复。
在非优雅关闭期间,Pod 分两个阶段终止:
out-of-service 容忍度的 Pod。node.kubernetes.io/out-of-service 污点之前,应确认节点确实处于关闭或断电状态(而非正在重启过程中)。在任何 Pod 删除未在 6 分钟内成功的场景下,如果节点此时处于不健康状态,Kubernetes 将强制卸载正在卸载的卷。任何仍在运行并使用强制卸载卷的工作负载都将导致违反 CSI 规范,该规范指出 ControllerUnpublishVolume “必须在卷上的所有 NodeUnstageVolume 和 NodeUnpublishVolume 被调用并成功后调用”。在这种情况下,该节点上的卷可能会遭遇数据损坏。
强制存储卸载行为是可选的;用户可以选择使用“非优雅节点关闭”特性。
可以通过设置 kube-controller-manager 中的 disable-force-detach-on-timeout 配置字段来禁用“超时强制存储卸载”。禁用该特性意味着,如果卷所在的节点处于不健康状态超过 6 分钟,其关联的 VolumeAttachment 将不会被删除。
应用此设置后,仍然挂载了卷的不健康 Pod 必须通过上述的非优雅节点关闭流程进行恢复。
了解更多信息