Kubernetes 节点摘除指南
Kubernetes 节点摘除指南
介绍 (Introduction)
在 Kubernetes 集群的生命周期管理中,移除节点是一个常见的操作。原因可能包括:执行节点维护(如操作系统升级、硬件更换)、集群扩缩容、节点故障等。简单粗暴地直接关闭或删除节点可能导致在该节点上运行的应用 Pod 意外终止,影响服务的可用性。
Kubernetes 提供了一套标准的、优雅的节点摘除流程,旨在最大限度地减少对应用 Pod 的影响。这个流程包括三个主要阶段:锁定 (Cordon)、驱逐 (Drain) 和 删除 (Delete)。本指南将详细介绍这三个步骤以及相关的实践操作。
引言 (Foreword/Motivation)
Kubernetes 集群是动态变化的,节点可能会因为各种原因加入或离开。当一个节点需要离线时,我们希望其上运行的应用 Pod 能够被安全地迁移到集群中的其他可用节点上,而不是突然死亡。这种优雅的节点移除是保障应用高可用性和无中断维护的关键。
通过遵循标准的摘除流程,我们可以确保:
- 新的 Pod 不会被调度到即将移除的节点上。
- 正在运行的 Pod 会被逐步、安全地终止,并由 Kubernetes 的控制器(如 Deployment, StatefulSet)在其他健康节点上重新创建。
- 考虑到应用的可用性预算 (PodDisruptionBudget, PDB),避免一次性驱逐太多 Pod 导致服务中断。
- 集群状态干净,不再引用已不存在的节点。
技术背景 (Technical Background)
- 节点 (Node): Kubernetes 集群中的工作机器,可以是物理机或虚拟机。Pod 运行在节点上。
- 调度器 (Scheduler): Kubernetes 控制平面组件,负责监测新创建的 Pod,并为其选择合适的节点进行运行。
- 控制器管理器 (Controller Manager): Kubernetes 控制平面组件,包含多个控制器,如 Deployment Controller, StatefulSet Controller, Node Controller, EndpointSlice Controller 等。它们负责维护集群的状态。在节点摘除过程中,特别是驱逐 Pod 时,Node Controller 和其他工作负载控制器协同工作。
- PodDisruptionBudget (PDB): 一个 API 对象,定义了在自愿中断(如节点维护、升级)期间,一个应用副本集中可以同时有多少个 Pod 不可用。
kubectl drain
命令会尊重 PDB。 - DaemonSet: 一种特殊的工作负载,确保在集群中的 所有 或 部分 节点上运行一个 Pod 的副本。DaemonSet Pod 通常不应被
drain
驱逐,因为它们需要运行在节点本地。
应用使用场景 (Application Scenarios)
优雅的 Kubernetes 节点摘除流程适用于以下场景:
- 计划内节点维护: 对节点进行操作系统补丁升级、内核更新、硬件升级或更换。
- 集群扩缩容 (缩容): 当集群的计算资源过剩时,移除部分节点以节约成本。
- 更换不健康的节点: 当某个节点出现硬件故障或软件问题,但在完全失效前仍能响应部分请求时,可以先优雅地将其上的 Pod 迁移。
- Kubernetes 版本升级: 在进行集群版本升级时,可能需要逐个节点进行排空和升级。
原理解释 (Principle Explanation)
节点优雅摘除的核心思想是逐步转移节点上的工作负载,而不是立即停止它们。
- 锁定 (Cordon):
- 原理: 将节点的调度状态设置为
SchedulingDisabled
。 - 目的: 告知 Kubernetes 调度器,不要再将新的 Pod 调度到这个节点上。这确保了在开始迁移现有 Pod 之前,不会有新的工作负载被添加到该节点。节点上已有的 Pod 会继续运行。
- 原理: 将节点的调度状态设置为
- 驱逐 (Drain):
- 原理: 这是一个复杂的过程,由
kubectl drain
命令触发,并通过 Kubernetes 控制平面协调完成。它会尝试从节点上删除 所有 非豁免的 Pod。 - 过程:
kubectl drain
命令会向 Kubernetes API Server 发送删除请求,请求删除目标节点上的 Pod。控制器管理器接收到这些删除请求,会尝试在集群中的其他可用节点上重新创建这些 Pod(如果它们是由 Deployment, StatefulSet, ReplicaSet 等控制器管理的)。 - 尊重 PDB: 在驱逐过程中,控制器管理器会检查受影响的 Pod 是否属于某个设置了 PDB 的应用。如果驱逐会导致该应用同时不可用的副本数超过 PDB 允许的范围,控制器管理器会暂停驱逐,直到其他节点上的 Pod 被成功创建并变为可用状态。
- 处理特殊 Pod: 默认情况下,
drain
命令不会驱逐由 DaemonSet 管理的 Pod,因为这些 Pod 通常需要运行在节点本地,它们会在其他节点上自动启动。drain
命令通常也需要特别处理或忽略使用emptyDir
卷的 Pod,因为emptyDir
卷的数据与 Pod 的生命周期绑定,驱逐会导致数据丢失。 - 目的: 将节点上的所有工作负载安全、有序地迁移到集群中的其他健康节点上。
- 原理: 这是一个复杂的过程,由
- 删除 (Delete):
- 原理: 从 Kubernetes API Server 中删除代表该节点的 Node 对象。
- 目的: 告知 Kubernetes 控制平面该节点已不再是集群的一部分。调度器不再考虑该节点,控制器管理器也不再尝试管理该节点上的任何资源。
- 底层基础设施清理:
- 原理: 这是 Kubernetes 外部的操作。根据你的集群类型,可能是关闭虚拟机、物理服务器,或在云服务商控制台/API 中终止实例。
- 目的: 停止为该节点支付资源费用,释放底层资源。
核心特性 (Core Features - of the process)
- 优雅性: 逐步迁移 Pod,避免服务中断。
- 自动化:
kubectl drain
命令自动化执行 Pod 驱逐过程。 - 策略遵循: 尊重 PodDisruptionBudget,保障应用可用性。
- 逐步操作: 先锁定,后驱逐,最后删除,每一步都有明确的目的。
- 需要清理底层资源: Kubernetes 本身不负责关闭或删除虚拟机/物理机。
原理流程图以及原理解释 (Principle Flowchart and Explanation)
(此处无法直接生成图形,用文字描述流程图)
图示:Kubernetes 节点优雅摘除流程
+---------------------+ +---------------------+ +---------------------+
| 识别待移除节点 | ----> | kubectl cordon | ----> | 节点状态: SchedulingDisabled |
| | | <node-name> | | (阻止新 Pod 调度) |
+---------------------+ +---------------------+ +---------------------+
^ |
| 等待 Pod 迁移 |
| v
+---------------------+ +---------------------+
| 应用高可用性 (PDB) | <---- | kubectl drain |
| (防止同时中断过多 Pod)| | <node-name> |
+---------------------+ | (驱逐现有 Pod) |
+---------------------+
^ |
| Pods 在其他节点重建 |
v | 等待所有 Pod 驱逐
+---------------------+ +---------------------+
| Pod 在新节点运行 | <---- | 所有 Pod 已驱逐 |
+---------------------+ | (除了 DaemonSets) |
^ |
| v
+--------------------------+ +---------------------+ +---------------------+
| 物理机/虚拟机下电/删除 | <---- | kubectl delete | ----> | 节点对象从 K8s API |
| (云平台/虚拟化平台操作) | | node <node-name> | | 中移除 |
+--------------------------+ +---------------------+ +---------------------+
原理解释:
- 识别节点: 确定需要移除的节点名称。
kubectl cordon
: 对节点执行cordon
操作,将其标记为不可调度 (SchedulingDisabled
)。调度器将不再考虑该节点作为新的 Pod 运行目标。kubectl drain
: 执行drain
操作。此命令的核心是调用 Kubernetes API 驱逐节点上的 Pod。- 控制器管理器接收到驱逐请求,并协调 Pod 的终止和在其他节点上的重建。
- 等待 Pod 迁移: 这个过程不是瞬时的,特别是对于设置了 PDB 的应用,
drain
命令会等待,直到满足 PDB 的要求,或者直到 Pod 在其他地方成功运行。 - 忽略 DaemonSets:
drain
通常会忽略 DaemonSet Pod,因为它们会在其他节点上自动补偿性创建。 - 处理 emptyDir: 默认情况下
drain
可能无法处理使用emptyDir
的 Pod,因为它不知道数据的重要性。通常需要额外的标志--delete-emptydir-data
(如果确认数据可以丢弃)或--force
。 - 所有 Pod 已驱逐:
drain
命令成功完成后,除了被忽略的 Pod,目标节点上将不再运行任何工作负载 Pod。
kubectl delete node
: 从 Kubernetes API 中删除该节点的表示。此时 Kubernetes 集群不再知道这个节点的存在。- 物理机/虚拟机下电/删除: 在 Kubernetes API 中删除节点对象后,才能安全地关闭或删除底层物理机或虚拟机实例。这个步骤是在 Kubernetes 外部进行的。
核心特性 (Core Features)
(同上,此处略)
环境准备 (Environment Setup)
- 一个运行中的 Kubernetes 集群,至少包含一个控制平面节点和至少两个工作节点(以便 Pod 可以迁移)。
- 已安装并配置好的
kubectl
命令行工具,拥有足够的权限(通常是集群管理员权限)来修改节点和删除 Pod。 - 集群中有一些 Deployment 或 StatefulSet 部署的应用 Pod 正在运行,其中一部分 Pod 运行在待移除的节点上。
不同场景下详细代码实现 & 代码示例实现 (Detailed Code Examples & Code Sample Implementation)
假设我们要移除的节点名称是 worker-node-1
。
-
列出集群中的节点:
kubectl get nodes
输出示例:
NAME STATUS ROLES AGE VERSION master-node-1 Ready master 2d1h v1.28.5 worker-node-1 Ready <none> 2d v1.28.5 worker-node-2 Ready <none> 2d v1.28.5
-
锁定节点 (Cordon): 将节点标记为不可调度。
kubectl cordon worker-node-1
输出示例:
node/worker-node-1 cordoned
再次检查节点状态,会看到
SchedulingDisabled
。kubectl get nodes
输出示例:
NAME STATUS ROLES AGE VERSION master-node-1 Ready master 2d1h v1.28.5 worker-node-1 Ready,SchedulingDisabled <none> 2d v1.28.5 worker-node-2 Ready <none> 2d v1.28.5
-
驱逐节点 (Drain): 驱逐节点上的所有 Pod。
# 这是最常用的 drain 命令,忽略 DaemonSet 和 emptyDir 卷,并强制删除未受控制器管理的 Pod kubectl drain worker-node-1 --ignore-daemonsets --delete-emptydir-data --force
命令参数说明:
worker-node-1
: 待驱逐的节点名称。--ignore-daemonsets
: 忽略 DaemonSet Pod,不尝试驱逐它们。这通常是期望的行为。--delete-emptydir-data
: 删除使用emptyDir
卷的 Pod。注意:这将导致emptyDir
卷中的数据丢失。 仅在确认emptyDir
中的数据不重要时使用。--force
: 强制删除未由 ReplicationController, ReplicaSet, Deployment, DaemonSet, StatefulSet 或 Job 等控制器管理的 Pod。对于不受控制器管理的单体 Pod,没有--force
将无法驱逐。--grace-period=<seconds>
: 给 Pod 优雅终止的时间,默认为Pod.Spec.TerminationGracePeriodSeconds
,通常为 30 秒。--timeout=<duration>
: 等待 Pod 驱逐完成的总超时时间,默认为 0 (无限等待)。建议设置一个合理的超时时间,例如--timeout=5m
(等待 5 分钟),防止无限期等待。
执行
drain
命令后,终端会输出正在驱逐的 Pod 列表。输出示例:
node/worker-node-1 cordoned # 如果之前没cordon,drain会自动执行cordon evicting pod "my-app-pod-xxx" evicting pod "another-app-pod-yyy" ... node/worker-node-1 evicted
如果在驱逐过程中遇到 PDB 限制或其他问题,
drain
命令可能会暂停或报错。你可以根据提示处理,或使用--force
(不推荐在生产环境随意使用,除非确定后果)。 -
删除节点对象 (Delete): 在
drain
命令成功完成后,节点上已无应用 Pod。现在可以从 Kubernetes API 中删除该节点对象。kubectl delete node worker-node-1
输出示例:
node "worker-node-1" deleted
再次检查节点列表,
worker-node-1
将不再显示。kubectl get nodes
-
清理底层基础设施 (Kubernetes 外部操作):
- 云环境: 登录云服务商的控制台或使用其 CLI/API,找到对应于
worker-node-1
的虚拟机实例,执行终止或删除操作。 - 自建集群: 登录到物理服务器或虚拟机管理平台,安全地关闭操作系统,然后执行下电操作或删除虚拟机。
- 云环境: 登录云服务商的控制台或使用其 CLI/API,找到对应于
运行结果 (Execution Results)
运行上述命令后,你将观察到:
kubectl cordon
后,kubectl get nodes
输出中,目标节点状态显示SchedulingDisabled
。kubectl drain
运行时,终端输出显示正在驱逐 Pod 的列表。- 在另一个终端中运行
kubectl get pods -n <namespace> -o wide
可以看到目标节点上的 Pod 逐渐进入Terminating
状态,并在其他节点上出现新的 Pod 副本进入ContainerCreating
或Running
状态。 kubectl drain
成功完成后,终端输出节点已驱逐的消息。kubectl get pods -n <namespace> -o wide
不再显示目标节点上的应用 Pod (DaemonSet Pod 除外)。kubectl delete node
后,kubectl get nodes
输出中不再包含被删除的节点。- (根据实际操作)底层虚拟机/物理机被成功关闭或删除。
测试步骤以及详细代码 (Testing Steps and Detailed Code)
测试节点摘除过程是否成功且优雅,可以从以下几个方面进行:
-
验证节点状态变化: 按照上述部署步骤,在执行
cordon
,drain
,delete
命令后,分别使用kubectl get nodes
验证节点状态是否正确流转(Ready -> Ready,SchedulingDisabled -> 不存在)。 -
验证 Pod 迁移:
- 步骤: 在执行
drain
命令前,使用kubectl get pods -o wide
或kubectl get pods --all-namespaces -o wide
查看所有 Pod 及其所在的节点,确认待移除节点上有运行中的 Pod。执行drain
命令过程中和完成后,再次查看 Pod 列表,验证这些 Pod 是否已从目标节点消失,并在其他节点上成功启动新的副本。 - 命令:
# Drain 前查看 Pod 分布 kubectl get pods --all-namespaces -o wide | grep worker-node-1 # 执行 drain 命令 (如上所示) # Drain 后检查目标节点上是否还有应用 Pod (DaemonSet 除外) kubectl get pods --all-namespaces -o wide | grep worker-node-1 # 期望除了 DaemonSet Pod,没有其他应用 Pod # 检查被迁移的 Pod 是否在其他节点成功运行 # 例如,如果有一个名为 my-app-deployment 的 Deployment kubectl get deployment my-app-deployment -o yaml | grep replicas # 验证期望的副本数 kubectl get pods -l app=my-app -o wide # 验证运行中的 Pod 数量和所在节点
- 步骤: 在执行
-
验证应用可用性 (如果在生产环境且有 PDB):
- 步骤: 如果你的应用设置了 PDB,且有多个副本,可以在执行
drain
命令过程中,持续地访问应用的服务端点。观察服务是否中断或错误率是否显著上升。 - 自动化: 编写一个脚本或使用监控工具,每隔几秒钟向应用的服务端点发起请求,记录请求的成功率和响应时间。在
drain
过程前后对比数据。 - 示例脚本 (概念性 - Bash 简单连续访问):
运行脚本,然后执行#!/bin/bash SERVICE_URL="http://<你的服务IP或域名>:<端口>" # 替换为你的服务地址 COUNT=0 SUCCESS_COUNT=0 while true; do COUNT=$((COUNT+1)) HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $SERVICE_URL) if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then echo "$(date) - Request $COUNT: SUCCESS (HTTP $HTTP_CODE)" SUCCESS_COUNT=$((SUCCESS_COUNT+1)) else echo "$(date) - Request $COUNT: FAILURE (HTTP $HTTP_CODE)" fi # 可以根据需要添加延迟 # sleep 0.1 done # 在另一个终端执行 drain 命令,观察这个脚本的输出
kubectl drain
命令。理想情况下,即使有 Pod 被驱逐和迁移,脚本的输出应该持续显示成功请求(取决于 PDB 设置和迁移速度)。
- 步骤: 如果你的应用设置了 PDB,且有多个副本,可以在执行
-
验证节点对象删除: 确认执行
kubectl delete node
后,节点不再出现在kubectl get nodes
的输出中。 -
验证底层资源清理: 登录云平台控制台或虚拟化平台,确认对应的虚拟机或物理机已经被关闭或删除。
部署场景 (Deployment Scenarios)
节点摘除流程是 Kubernetes 运维的标准实践,广泛应用于:
- 云上托管 Kubernetes 服务: 在 AWS EKS, GKE, AKS, 阿里云 ACK 等服务中,通常通过控制台或云服务商的 CLI/API 来触发节点池的缩容操作,底层会自动执行优雅的节点摘除流程。但理解这些步骤有助于故障排查。
- 自建 Kubernetes 集群 (kubeadm): 对于使用 kubeadm 搭建的集群,需要手动执行
kubectl cordon
,kubectl drain
,kubectl delete node
命令。 - 自动化运维脚本: 将节点摘除流程集成到自动化运维脚本或 CI/CD 流水线中,用于自动化的集群扩缩容或维护操作。
- 使用集群 Autoscaler: 集群 Autoscaler 在缩容时会自动执行节点的优雅排空和删除操作。
疑难解答 (Troubleshooting)
-
kubectl drain
命令卡住或失败:- 原因:
- PodDisruptionBudget (PDB) 阻止了驱逐。
- 存在不受控制器管理的 Pod 且未使用
--force
。 - 存在使用
emptyDir
卷的 Pod 且未使用--delete-emptydir-data
或--force
。 - Pod 无法正常终止或迁移(例如,应用本身的问题、其他节点资源不足)。
- DaemonSet Pod 未被忽略。
- 排查:
- 查看
drain
命令的输出,它通常会指示哪个 Pod 导致了问题。 - 检查相关 Pod 的状态 (
kubectl get pod <pod-name> -o yaml
) 和 Events (kubectl describe pod <pod-name>
),了解为什么它无法终止或迁移。 - 检查相关 Deployment/StatefulSet 等控制器的状态,看是否有副本启动失败。
- 检查 PDB 状态 (
kubectl get pdb --all-namespaces
),确认是哪个 PDB 阻止了驱逐。如果必要,可能需要临时修改或删除 PDB (风险操作)。 - 如果确认数据不重要且 Pod 不受控制器管理,可以尝试添加
--force
和--delete-emptydir-data
标志(再次强调需谨慎)。 - 确保其他节点有足够的资源来接收被迁移的 Pod。
- 查看
- 原因:
-
节点状态显示
NotReady
或无法访问:- 原因: 在尝试优雅摘除前,节点已经出现故障或网络中断。
- 排查: 如果节点已经物理失效或网络不通,优雅
drain
可能无法执行。在这种情况下,你可能只能跳过drain
步骤(因为已经无法驱逐其上的 Pod),直接使用kubectl delete node --force
强制删除节点对象,然后在其他节点上重新创建 Pod。对于 StatefuleSet,可能需要手动干预以处理数据卷。
-
删除节点后 Pod 未在其他节点启动:
- 原因: 原本运行在已删除节点上的 Pod 不受控制器管理;或者其他节点资源不足;或者存在调度限制。
- 排查: 检查 Pod 的创建方式。如果它是一个裸 Pod,你需要手动在其他节点上创建它(通常推荐使用控制器管理 Pod)。检查其他节点的资源使用和调度限制 (
kubectl describe node <other-node-name>
)。检查 Pod 的 Events,看调度器为什么没有将其调度到其他节点。
未来展望 (Future Outlook)
- 更智能的自动缩容: 集群 Autoscaler 将进一步优化其节点摘除策略,更好地处理 StatefuleSet 和复杂的 PDB。
- 增强的 StatefuleSet 支持: 改进 StatefulSet 在节点故障或摘除场景下的数据卷处理和迁移能力。
- 与底层基础设施的深度集成: 云服务商提供的 Kubernetes 服务可能会提供更无缝、更自动化的节点生命周期管理,包括底层的资源清理。
技术趋势与挑战 (Technology Trends and Challenges)
技术趋势:
- 声明式运维: 将节点生命周期管理也尽可能声明式化。
- 自动化与智能化: 利用自动化工具和 AI/ML 提升集群运维效率。
- 不可变基础设施: 减少对运行中节点的直接操作,更多地通过替换节点来更新。
挑战:
- 处理有状态工作负载: 安全、可靠地迁移带有持久化存储(特别是 ReadWriteOnce 模式)的 Pod 依然是挑战。
- PDB 的复杂性: 正确配置和管理 PDB 以平衡可用性和资源利用率。
- 处理非预期故障: 如何在节点突然失效的情况下快速恢复服务,而优雅摘除流程主要针对计划内操作。
- 多云和混合云环境下的节点管理: 在不同的基础设施提供商之间标准化节点管理流程。
- 教育和最佳实践推广: 确保开发者和运维人员理解并遵循优雅的节点摘除流程。
总结 (Conclusion)
优雅地摘除 Kubernetes 节点是保障应用高可用性和执行集群维护的关键步骤。通过遵循 锁定 (Cordon)、驱逐 (Drain) 和 删除 (Delete) 的标准流程,您可以安全地将节点上的工作负载迁移到集群中的其他节点上,最大限度地减少对服务的影响。理解每个步骤的目的、kubectl drain
命令的参数以及如何处理潜在问题(如 PDB, emptyDir
, 非受控 Pod)至关重要。将这些步骤集成到你的日常运维和自动化流程中,可以显著提升集群的可靠性和管理效率。最后,不要忘记在 Kubernetes 外部清理底层的物理机或虚拟机资源。
- 点赞
- 收藏
- 关注作者
评论(0)