--- title: 节点关闭 content_type: concept weight: 10 --- 在 Kubernetes 集群中,{{< glossary_tooltip text="节点" term_id="node" >}}可以按计划的体面方式关闭, 也可能因断电或其他某些外部原因被意外关闭。如果节点在关闭之前未被排空,则节点关闭可能会导致工作负载失败。 节点可以**体面关闭**或**非体面关闭**。 ## 节点体面关闭 {#graceful-node-shutdown} {{< feature-state feature_gate_name="GracefulNodeShutdown" >}} kubelet 会尝试检测节点系统关闭事件并终止在节点上运行的所有 Pod。 在节点终止期间,kubelet 保证 Pod 遵从常规的 [Pod 终止流程](/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination), 且不接受新的 Pod(即使这些 Pod 已经绑定到该节点)。 节点体面关闭特性依赖于 systemd,因为它要利用 [systemd 抑制器锁](https://www.freedesktop.org/wiki/Software/systemd/inhibit/)机制, 在给定的期限内延迟节点关闭。 节点体面关闭特性受 `GracefulNodeShutdown` [特性门控](/zh-cn/docs/reference/command-line-tools-reference/feature-gates/)控制, 在 1.21 版本中是默认启用的。 注意,默认情况下,下面描述的两个配置选项,`shutdownGracePeriod` 和 `shutdownGracePeriodCriticalPods` 都是被设置为 0 的,因此不会激活节点体面关闭特性。 要激活此功能特性,这两个选项要适当配置,并设置为非零值。 一旦 systemd 检测到或收到节点关闭的通知,kubelet 就会在节点上设置一个 `NotReady` 状况,并将 `reason` 设置为 `"node is shutting down"`。 kube-scheduler 会重视此状况,不将 Pod 调度到受影响的节点上; 其他第三方调度程序也应当遵循相同的逻辑。这意味着新的 Pod 不会被调度到该节点上, 因此不会有新 Pod 启动。 如果检测到节点关闭正在进行中,kubelet **也会**在 `PodAdmission` 阶段拒绝 Pod,即使是该 Pod 带有 `node.kubernetes.io/not-ready:NoSchedule` 的{{< glossary_tooltip text="容忍度" term_id="toleration" >}},也不会在此节点上启动。 当 kubelet 通过 API 在其 Node 上设置该状况时,kubelet 也开始终止在本地运行的所有 Pod。 在体面关闭过程中,kubelet 分两个阶段来终止 Pod: 1. 终止在节点上运行的常规 Pod。 2. 终止在节点上运行的[关键 Pod](/zh-cn/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/#marking-pod-as-critical)。 节点体面关闭的特性对应两个 [`KubeletConfiguration`](/zh-cn/docs/tasks/administer-cluster/kubelet-config-file/) 选项: - `shutdownGracePeriod`: 指定节点应延迟关闭的总持续时间。这是 Pod 体面终止的时间总和,不区分常规 Pod 还是[关键 Pod](/zh-cn/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/#marking-pod-as-critical)。 - `shutdownGracePeriodCriticalPods`: 在节点关闭期间指定用于终止[关键 Pod](/zh-cn/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/#marking-pod-as-critical) 的持续时间。该值应小于 `shutdownGracePeriod`。 {{< note >}} 在某些情况下,节点终止过程会被系统取消(或者可能由管理员手动取消)。 无论哪种情况下,节点都将返回到 `Ready` 状态。然而,已经开始终止进程的 Pod 将不会被 kubelet 恢复,需要被重新调度。 {{< /note >}} 例如,如果设置了 `shutdownGracePeriod=30s` 和 `shutdownGracePeriodCriticalPods=10s`, 则 kubelet 将延迟 30 秒关闭节点。在关闭期间,将保留前 20(30 - 10)秒用于体面终止常规 Pod, 而保留最后 10 秒用于终止[关键 Pod](/zh-cn/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/#marking-pod-as-critical)。 {{< note >}} 当 Pod 在正常节点关闭期间被驱逐时,它们会被标记为关闭。 运行 `kubectl get pods` 时,被驱逐的 Pod 的状态显示为 `Terminated`。 并且 `kubectl describe pod` 表示 Pod 因节点关闭而被驱逐: ``` Reason: Terminated Message: Pod was terminated in response to imminent node shutdown. ``` {{< /note >}} ### 基于 Pod 优先级的节点体面关闭 {#pod-priority-graceful-node-shutdown} {{< feature-state feature_gate_name="GracefulNodeShutdownBasedOnPodPriority" >}} 为了在节点体面关闭期间提供更多的灵活性,尤其是处理关闭期间的 Pod 排序问题, 节点体面关闭机制能够关注 Pod 的 PriorityClass 设置,前提是你已经在集群中启用了此功能特性。 此特性允许集群管理员基于 Pod 的[优先级类(Priority Class)](/zh-cn/docs/concepts/scheduling-eviction/pod-priority-preemption/#priorityclass) 显式地定义节点体面关闭期间 Pod 的处理顺序。 前文所述的[节点体面关闭](#graceful-node-shutdown)特性能够分两个阶段关闭 Pod, 首先关闭的是非关键的 Pod,之后再处理关键 Pod。 如果需要显式地以更细粒度定义关闭期间 Pod 的处理顺序,需要一定的灵活度, 这时可以使用基于 Pod 优先级的体面关闭机制。 当节点体面关闭能够处理 Pod 优先级时,节点体面关闭的处理可以分为多个阶段, 每个阶段关闭特定优先级类的 Pod。可以配置 kubelet 按确切的阶段处理 Pod, 且每个阶段可以独立设置关闭时间。 假设集群中存在以下自定义的 Pod [优先级类](/zh-cn/docs/concepts/scheduling-eviction/pod-priority-preemption/#priorityclass)。 | Pod 优先级类名称 | Pod 优先级类数值 | |-------------------------|------------------------| |`custom-class-a` | 100000 | |`custom-class-b` | 10000 | |`custom-class-c` | 1000 | |`regular/unset` | 0 | 在 [kubelet 配置](/zh-cn/docs/reference/config-api/kubelet-config.v1beta1/)中, `shutdownGracePeriodByPodPriority` 看起来可能是这样: | Pod 优先级类数值 | 关闭期限 | |------------------------|-----------| | 100000 | 10 秒 | | 10000 | 180 秒 | | 1000 | 120 秒 | | 0 | 60 秒 | 对应的 kubelet 配置 YAML 将会是: ```yaml shutdownGracePeriodByPodPriority: - priority: 100000 shutdownGracePeriodSeconds: 10 - priority: 10000 shutdownGracePeriodSeconds: 180 - priority: 1000 shutdownGracePeriodSeconds: 120 - priority: 0 shutdownGracePeriodSeconds: 60 ``` 上面的表格表明,所有 `priority` 值大于等于 100000 的 Pod 关闭期限只有 10 秒, 所有 `priority` 值介于 10000 和 100000 之间的 Pod 关闭期限是 180 秒, 所有 `priority` 值介于 1000 和 10000 之间的 Pod 关闭期限是 120 秒, 其他所有 Pod 关闭期限是 60 秒。 用户不需要为所有的优先级类都设置数值。例如,你也可以使用下面这种配置: | Pod 优先级类数值 | 关闭期限 | |------------------------|-----------| | 100000 | 300 秒 | | 1000 | 120 秒 | | 0 | 60 秒 | 在上面这个场景中,优先级类为 `custom-class-b` 的 Pod 会与优先级类为 `custom-class-c` 的 Pod 在关闭时按相同期限处理。 如果在特定的范围内不存在 Pod,则 kubelet 不会等待对应优先级范围的 Pod。 kubelet 会直接跳到下一个优先级数值范围进行处理。 如果此功能特性被启用,但没有提供配置数据,则不会出现排序操作。 使用此功能特性需要启用 `GracefulNodeShutdownBasedOnPodPriority` [特性门控](/zh-cn/docs/reference/command-line-tools-reference/feature-gates/), 并将 [kubelet 配置](/zh-cn/docs/reference/config-api/kubelet-config.v1beta1/)中的 `shutdownGracePeriodByPodPriority` 设置为期望的配置, 其中包含 Pod 的优先级类数值以及对应的关闭期限。 {{< note >}} 在节点体面关闭期间考虑 Pod 优先级的能力是作为 Kubernetes v1.23 中的 Alpha 特性引入的。 在 Kubernetes {{< skew currentVersion >}} 中此特性处于 Beta 阶段,默认启用。 {{< /note >}} kubelet 子系统中会生成 `graceful_shutdown_start_time_seconds` 和 `graceful_shutdown_end_time_seconds` 度量指标以便监视节点关闭行为。 ## 处理节点非体面关闭 {#non-graceful-node-shutdown} {{< feature-state feature_gate_name="NodeOutOfServiceVolumeDetach" >}} 节点关闭的操作可能无法被 kubelet 的节点关闭管理器检测到, 或是因为该命令没有触发 kubelet 所使用的抑制器锁机制,或是因为用户错误, 即 ShutdownGracePeriod 和 ShutdownGracePeriodCriticalPod 配置不正确。 请参考以上[节点体面关闭](#graceful-node-shutdown)部分了解更多详细信息。 当某节点关闭但 kubelet 的节点关闭管理器未检测到这一事件时, 在那个已关闭节点上、属于 {{< glossary_tooltip text="StatefulSet" term_id="statefulset" >}} 的 Pod 将停滞于终止状态,并且不能移动到新的运行节点上。 这是因为已关闭节点上的 kubelet 已不存在,亦无法删除 Pod, 因此 StatefulSet 无法创建同名的新 Pod。 如果 Pod 使用了卷,则 VolumeAttachments 无法从原来的已关闭节点上删除, 因此这些 Pod 所使用的卷也无法挂接到新的运行节点上。 最终,那些以 StatefulSet 形式运行的应用无法正常工作。 如果原来的已关闭节点被恢复,kubelet 将删除 Pod,新的 Pod 将被在不同的运行节点上创建。 如果原来的已关闭节点没有被恢复,那些在已关闭节点上的 Pod 将永远滞留在终止状态。 为了缓解上述情况,用户可以手动将具有 `NoExecute` 或 `NoSchedule` 效果的 `node.kubernetes.io/out-of-service` 污点添加到节点上,标记其无法提供服务。 如果 Node 被污点标记为无法提供服务,且节点上的 Pod 没有设置对应的容忍度, 那么这样的 Pod 将被强制删除,并且在节点上被终止的 Pod 将立即进行卷分离操作。 这样就允许那些在无法提供服务节点上的 Pod 能在其他节点上快速恢复。 在非体面关闭期间,Pod 分两个阶段终止: 1. 强制删除没有匹配的 `out-of-service` 容忍度的 Pod。 2. 立即对此类 Pod 执行分离卷操作。 {{< note >}} - 在添加 `node.kubernetes.io/out-of-service` 污点之前, 应该验证节点已经处于关闭或断电状态(而不是在重新启动中)。 - 将 Pod 移动到新节点后,用户需要手动移除停止服务的污点, 并且用户要检查关闭节点是否已恢复,因为该用户是最初添加污点的用户。 {{< /note >}} ### 存储超时强制解除挂接 {#storage-force-detach-on-timeout} 在任何情况下,当 Pod 未能在 6 分钟内删除成功,如果节点当时不健康, Kubernetes 将强制解除挂接正在被卸载的卷。 任何运行在使用了强制解除挂接卷的节点之上的工作负载, 都将违反 [CSI 规范](https://github.com/container-storage-interface/spec/blob/master/spec.md#controllerunpublishvolume), 该规范指出 `ControllerUnpublishVolume` "**必须**在调用卷上的所有 `NodeUnstageVolume` 和 `NodeUnpublishVolume` 执行且成功后被调用"。 在这种情况下,相关节点上的卷可能会遇到数据损坏。 强制存储解除挂接行为是可选的;用户可以选择使用"非体面节点关闭"特性。 可以通过在 `kube-controller-manager` 中设置 `disable-force-detach-on-timeout` 配置字段来禁用超时时存储强制解除挂接。 禁用超时强制解除挂接特性意味着,托管在异常超过 6 分钟的节点上的卷将不会保留其关联的 [VolumeAttachment](/zh-cn/docs/reference/kubernetes-api/config-and-storage-resources/volume-attachment-v1/)。 应用此设置后,仍然关联卷到不健康 Pod 必须通过上述[非体面节点关闭](#non-graceful-node-shutdown)过程进行恢复。 {{< note >}} - 使用[非体面节点关闭](#non-graceful-node-shutdown)过程时必须小心。 - 偏离上述步骤可能会导致数据损坏。 {{< /note >}} ## Windows 体面节点关闭 {#windows-graceful-node-shutdown} {{< feature-state feature_gate_name="WindowsGracefulNodeShutdown" >}} 此服务会使用一个注册的[服务控制处理程序函数](https://learn.microsoft.com/zh-cn/windows/win32/services/service-control-handler-function)将 preshutdown 事件延迟一段时间。 Windows 体面节点关闭是通过 1.32 中作为 Alpha 特性所引入的 `WindowsGracefulNodeShutdown` [特性门控](/zh-cn/docs/reference/command-line-tools-reference/feature-gates/)进行控制的。 Windows 体面节点关闭无法被取消。 如果 kubelet 不是作为 Windows 服务运行,它将不能设置和监控 [Preshutdown](https://learn.microsoft.com/zh-cn/windows/win32/api/winsvc/ns-winsvc-service_preshutdown_info) 事件,对应节点将不得不跑完上述[非体面节点关闭](#non-graceful-node-shutdown)的流程。 在启用 Windows 体面节点关闭特性但 kubelet 未作为 Windows 服务运行的情况下,kubelet 将继续运行而不会失败。 但是,kubelet 将在日志中记录一个错误,表明它需要作为一个 Windows 服务来运行。 ## {{% heading "whatsnext" %}} 了解更多以下信息: - 博客:[非体面节点关闭](/zh-cn/blog/2023/08/16/kubernetes-1-28-non-graceful-node-shutdown-ga/)。 - 集群架构:[节点](/zh-cn/docs/concepts/architecture/nodes/)。