ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

K8s节点下线全指南:从风险评估到排水清退与避坑实践

K8s节点下线全指南:从风险评估到排水清退与避坑实践 K8s节点下线听起来就是一行kubectl delete node的事但真正在生产环境操作过的人都知道这可能是集群运维里最容易翻车的动作之一。一台节点背后往往挂着几十上百个Pod处理不当轻则服务抖动、P1告警轰炸重则数据丢失、集群脑裂。这篇文章就围绕“K8s节点下线”这件事把我自己的标准操作流程、参数选择逻辑和踩过的坑完整梳理一遍希望你下次做节点下线时能少走弯路。这篇内容适合正在维护生产集群的运维、SRE也适合刚接触K8s、准备把集群运维职责接手的开发同学。整套流程我会分成四个阶段来讲下线前的风险评估、节点排水与业务迁移、节点清退与集群清理、以及常见问题排查。每个阶段我都会给出具体的命令、参数解读和判断依据尽量做到可以直接照着操作。1. 节点下线前的风险评估与准备1.1 先想清楚这次下线要解决什么问题节点下线不是一个孤立动作而是运维变更的一部分。常见的下线场景大概有这么几类第一类是硬件故障或老化替换。比如磁盘出现坏道、内存持续报错、风扇异响这类节点的下线通常带有紧迫性但你反而要提醒自己越急越容易出错。第二类是机房迁移或机柜调整节点还在但位置要变这种情况下“下线”更多是“退服—搬迁—重新上线”的中间态。第三类是成本优化或资源收缩业务量下降需要把多余的节点还给资源池。第四类是节点升级或架构调整比如操作系统大版本升级、内核参数变更没法原地完成只能先下线再重建。不同场景下你的操作节奏是完全不一样的。硬件故障可能允许你缩短排空等待时间但业务迁移类操作必须给足优雅终止窗口。这里要先纠正一个常见误区节点下线不等于kubectl delete node。一个完整的过程应该分为三步第一步是“排水”让节点上的负载安全迁移到其他节点第二步是“清退”把节点对象从集群里删掉第三步是“物理处置”把机器从资源池里彻底退出包括停服务、断电、重置系统甚至注销监控和云资源。三个步骤缺一不可顺序也不能颠倒。如果你先删节点对象再排水那这台机器上还在运行的Pod会全部卡在Terminating状态最后只能靠强杀解决生产事故基本就是这么来的。还有一个容易被忽略的点如果集群开了自动注册机制比如云厂商的节点自愈、kubeadm的重复join操作节点删掉之后可能很快又自己加回来了。这个问题我会在第四部分详细讲但你在准备阶段就要先排查这一点否则后面的操作完全是白费功夫。1.2 体检这台节点上面跑着哪些不该断电的东西在动手之前必须先搞清楚这台节点上到底运行着什么。我的习惯是先跑一组命令把节点负载的完整画像拉出来# 查看节点上的全部Pod跨所有命名空间 kubectl get pods --all-namespaces --field-selector spec.nodeNamenode-name -o wide # 查看节点资源水位 kubectl describe node node-name # 查看节点标签、污点、注解 kubectl get node node-name -o yaml特别要注意下面几类负载第一个是DaemonSet。这类Pod由DaemonSet控制器管理每个节点上都会有一个比如日志采集、监控Agent、网络插件。它们在下线过程中不会被drain驱逐处理方式后面会讲。第二个是静态Pod和镜像Pod。如果你用kubeadm搭建集群kube-apiserver、kube-scheduler这些核心组件很可能以静态Pod方式运行在控制面节点上。这类Pod由节点上的kubelet直接管理不在API Server的Pod列表里drain的时候需要额外处理。第三个是挂载了本地存储的Pod。如果是云盘通常可以跨节点重新挂载但如果是hostPath、emptyDir或者local PV数据是绑定在这台机器上的Pod被驱逐后数据未必能跟着走。这块必须在排水之前就看清楚否则数据丢了就真丢了。第四个是有状态应用。比如数据库、消息队列、搜索引擎这类工作负载它们的滚动迁移往往需要业务层面配合不能单纯依靠K8s的驱逐机制。你需要确认业务方有没有支持跨节点迁移还是说需要走备份恢复。另外我还建议在下线前看一眼监控面板上这台节点的历史指标CPU、内存、磁盘IO、网络流量、QPS、错误率。重点不是看当前值而是看近一周的峰值和波动规律确认这台节点是否承担了某些流量入口或者批处理任务。如果这台节点上有业务高峰时段的定时任务你的变更窗口就要避开那个时段。1.3 影响面评估业务能不能接受这台机器消失风险评估的核心问题只有一个这台节点上的业务在它消失之后还能不能正常对外提供服务判断依据主要是三点PodDisruptionBudgetPDB、副本数、以及节点亲和性配置。PDB是K8s提供的自愿中断保护机制。它定义了在自愿中断比如drain节点时某个应用最多允许多少个Pod不可用。例如一个应用有3个副本PDB设了minAvailable: 2那drain时最多只允许1个副本被驱逐。如果PDB设得太严格比如副本数只有1minAvailable也是1那drain会被卡住因为任何一个Pod被驱逐都会违反约束。副本数比较容易检查直接看Deployment的replicas和当前Available副本数即可。这里要特别强调反亲和性如果应用的3个副本全部调度到了这台即将下线的节点上那再多副本也没用节点一排水整个应用就全没了。检查一下Pod的拓扑分布确认其他节点上还能接住负载。存储方面要做两手准备第一确认PVC使用的是哪类StorageClass是否支持跨节点挂载第二对确实无法迁移的数据做好备份或者明确告知业务方“这台节点上的本地数据将无法恢复”获得业务方的书面确认。这不是走形式而是出了问题之后你能站住脚的依据。最后变更窗口和通知。生产环境的节点下线属于高风险变更建议走变更审批流程提前在群里通知业务方和研发明确变更时间、影响范围、回滚方案。特别是涉及数据库、核心链路服务的节点一定要和业务方确认业务低峰期的时间窗口。我自己的习惯是变更窗口、预计耗时、紧急回滚联系人这三项必须写清楚否则宁可不做。2. 节点排水与业务迁移2.1 为什么必须先cordon再drain准备阶段完成后就进入正式操作。第一步不是drain而是cordon。cordon和drain的区别我用一个比方来解释cordon相当于在节点门口挂上“暂停售票”的牌子新的Pod不会再调度上来但已经在里面的Pod照常运行drain则是进一步清场把里面的Pod一个个请出去。如果你直接drain节点在排水过程中仍然可能接收新Pod一边排一边进相当于一边疏散一边放人进来永远排不干净。实际操作中cordon还有个更大的好处它给了你一个后悔和观察的缓冲期。执行cordon之后你可以先观察一段时间确认不会有新的Pod调度上来也确认当前负载没有异常再决定是否继续执行drain。万一发现这台节点其实承担了某些关键任务你随时可以kubectl uncordon node-name恢复调度不会有任何损失。# 标记节点不可调度 kubectl cordon node-name # 确认节点状态变为 SchedulingDisabled kubectl get node node-name执行cordon后节点状态会多出一个SchedulingDisabled标签表示New Pod不会调度上来。这时候你仍然可以正常访问节点上的已有Pod业务不受影响。观察几分钟到十几分钟确认没有异常再进入下一步。2.2 drain参数逐一拆解drain是节点下线流程里最核心的一步也是最容易出问题的一步。我的标准命令如下kubectl drain node-name --ignore-daemonsets --delete-emptydir-data --force --grace-period60 --timeout15m这几个参数不是随便加的每个都有明确的用途。--ignore-daemonsets忽略DaemonSet管理的Pod。因为DaemonSet的特性就是每节点必须有一个驱逐了它马上又会在原节点重建只会无限卡住。但这不代表DaemonSet不受影响驱逐过程中它会短暂中断然后在节点上重启。对于日志Agent、监控Agent这类组件短暂重启通常没有影响但对于网络插件比如Calico的Pod要谨慎网络插件重启可能会造成短暂网络抖动。--delete-emptydir-data允许删除emptyDir卷的数据。emptyDir的生命周期与Pod绑定Pod没了数据也就没了。这个参数等于确认“我接受这个后果”。如果业务在emptyDir里放了重要缓存数据排水前要提前做好协商。--force强制驱逐无控制器管理的Pod。集群里偶尔会有一些裸Pod没有Deployment、StatefulSet等控制器管理如果遇到这类Poddrain默认会拒绝操作提示你Pod不是由ReplicationController等管理的。--force就是告诉K8s“我知道这些Pod没有控制器兜底驱逐后不会自动重建但我接受这个风险”。用这个参数之前务必确认这些裸Pod不是你有意跑到这台节点上的关键任务。--grace-period60给Pod优雅终止的时间单位是秒。K8s默认是30秒我习惯调大到60秒给业务留出更充分的清理时间。需要注意的是这个参数的含义是“最大等待时间”如果应用在5秒内处理完退出流程kubelet不会傻等60秒。--timeout15mdrain命令整体的超时时间。如果15分钟内还没完成命令会报错返回。这个参数是为了避免你在终端前干等一整天尤其是当PDB约束导致驱逐卡住时timeout能让你尽快发现问题。参数选好之后我建议不要直接在命令行执行而是先保存一份命令到变更记录里。生产环境所有变更都应该有据可查出了问题也方便复盘。2.3 排水过程中盯着这些状态drain命令跑起来之后不要干等着它结束。排水是一个异步过程命令只是发起了驱逐请求真正执行驱逐和重建的是kubelet和控制器管理器。你需要另开一个终端盯着状态。# 实时观察Pod状态 kubectl get pods --all-namespaces -o wide | grep node-name # 观察终端节点状态 kubectl get node node-name排水过程中Pod会经历一系列状态变化。正常驱逐流程是先给Pod打上Terminating标记然后根据terminationGracePeriodSeconds等待容器里主进程优雅退出退出成功后再删除Pod对象最后在其他节点上拉起新Pod。你观察到的理想状态是这台节点上的Pod数量逐渐减少其他节点上的Pod数量同步增加最终这台节点上只剩下DaemonSet的Pod和由kubelet管理的static Pod。如果发现Pod一直处于Pending状态说明其他节点上没有足够的资源或者不满足调度约束。这时候不要急着继续先去看Pending的原因常见的包括资源不足、亲和性规则不满足、PVC无法跨节点挂载。可以用kubectl describe pod pod-name查看调度器报错信息。这里还要说一个生产环境经常遇到的业务类型——有状态应用比如Redis集群、Elasticsearch、Kafka。这类应用的Pod被驱逐后新Pod在其他节点启动但数据是否完整取决于业务自身的数据复制机制。如果业务没有配置跨节点数据副本单纯靠K8s驱逐等于把数据丢了。因此对这类应用我强烈建议在drain之前先与业务方沟通确认是否需要先在业务层面做数据迁移或缩容操作再执行K8s层面的驱逐。排水过程中一旦发现业务异常怎么办很多人会慌想着马上uncordon。这里有个原则要记住uncordon不能恢复已经被驱逐的Pod它只是让节点恢复可调度已消失的Pod不会自己回来。正确做法是先保证业务恢复再评估是否继续下线。如果你的业务指标在排水后迅速恶化且新Pod无法正常拉起优先暂停下线流程把被驱逐的Pod拉回或用其他方式恢复服务同时通知相关方共同排查。宁可本次下线失败也不要让业务长时间不可用。3. 节点清退与集群清理3.1 删除节点对象的正确时机与操作当drain执行完毕节点上除了DaemonSet和静态Pod之外已经没有其他负载了。这时候才轮到kubectl delete node。但在执行删除之前有一个关键操作经常被忽略停掉节点上的kubelet和容器运行时。为什么因为kubelet本质上是一个独立进程它会持续向API Server上报心跳。节点对象被删除后如果kubelet还在运行并反复尝试注册自己某些环境下会导致节点对象“复活”。虽然这不是所有K8s环境都会出现的现象但在生产环境里这个操作顺序能避免绝大部分诡异问题。建议的操作顺序是# 1. 在待下线节点上执行停止kubelet并禁止开机自启 systemctl stop kubelet systemctl disable kubelet # 2. 如果容器运行时也可以停一并停掉 systemctl stop containerd # 或 docker视运行时而定 # 3. 回到操作终端删除节点对象 kubectl delete node node-name节点对象删除后API Server不会再维护这个节点的心跳调度器也不会向其调度Pod。此时节点如果还活着它只是集群外的一台“孤儿机器”不会再对集群产生任何影响。3.2 集群内部的二次清理很多人以为删掉node对象就结束了其实节点下线后集群内部还可能残留一些影子数据。这些残留不会立刻引发问题但会在后续运维中埋雷。第一类残留是EndpointSlice。如果某个Service的Endpoints还引用着这台节点的Pod IP理论上控制器会异步清理但偶尔会出现延迟或残留。建议检查一下# 查看是否有指向该节点IP的endpoint kubectl get endpointslices --all-namespaces -o wide | grep node-ip第二类残留是Lease对象。Kubelet节点心跳会创建Lease记录正常情况下节点对象删除后Lease也会被回收。但残留在etcd里不影响运行只是强迫症看着难受。确认没有其他依赖的话可以忽略。第三类残留是ConfigMap和Secret里记录的节点信息。比如kube-proxy的配置、kubeadm-config里记录的控制面节点列表。这些不会因为节点删除而自动更新需要手动清理或等待下次集群维护时处理。如果是控制面节点下线要重点检查kubeadm-config里的controlPlaneEndpoint配置。第四类是etcd成员清理。这部分主要针对控制面节点下线单独拿出来说。如果下线的节点曾经是etcd集群成员必须显式将它从etcd成员列表里移除否则etcd集群一直认为那个成员还存在写入的quorum计算也会把这个不存在的节点算进去极端情况下会导致etcd集群脑裂或写入失败。# 查看etcd成员列表 etcdctl --endpointsetcd-endpoint --cacertca --certcert --keykey member list # 移除指定成员 etcdctl member remove member-id同时如果控制面节点在负载均衡器后面比如Nginx、云负载均衡或Keepalived别忘了把它从负载均衡后端摘除。否则即使节点已下线流量还是会打到这台机器上表现为连接超时或拒绝。3.3 节点自身的资源回收与监控摘除集群清理干净后最后一步是处理节点本身的物理资源。如果这台节点是云主机释放或回收前要再次确认上面没有需要保留的数据。数据盘的处理策略要事先定义好是随实例释放还是先打快照再释放。如果是物理机建议重装系统或至少清空磁盘敏感数据防止后续分配给其他业务时出现数据泄露。监控和告警系统的清理很容易被忽略但影响很大。节点下线后Prometheus的node_exporter target如果还留在这里会让监控页面上出现一个永远无数据的Down实例不仅难看还会触发误报。处理方式包括# 在Prometheus配置里移除或注释该target # 或者在服务发现层把该节点从consul/文件列表中摘除告警规则里如果有针对该节点的表达式比如“节点内存使用率超过90%”“节点磁盘空间不足”也要一并更新。Grafana面板里的主机列表中该节点如果作为变量值存在同样需要清理。这一步容易被忽略但恰恰是体现运维细致程度的地方。我见过不止一次节点下线后一个月告警还在半夜响就是因为部署人员只删了node对象没管监控侧。另外如果这台节点上配置过自定义标签或污点并且这些配置是手动设置的记得在集群侧清理掉。大部分标签会随节点对象一起消失但如果你曾经在集群里保存过单独的配置清单比如GitOps仓库里的节点配置文件要同步删除否则下次集群巡检会发现配置漂移。4. 常见问题与排查实录4.1 drain卡住不动怎么判断卡点drain命令卡住是节点下线时最常遇到的问题。表现是命令长时间停在某个Pod上没有任何进展直到timeout报错。最常见的原因有三个。第一个是PDB约束。前面提到过如果应用设置了minAvailable且当前可用副本数已经等于甚至低于这个值drain会一直等待直到timeout。判断方法很简单看驱逐信息里是否提示“disrupted budget”。处理方法一般是先评估业务容忍度临时调低PDB约束等流量迁移过去后再恢复。注意修改PDB本身也是一次变更要谨慎操作并记录。第二个是裸Pod或无控制器Pod。如果你的集群里存在没有ReplicaSet、StatefulSet等控制器管理的Poddrain默认会拒绝并提示需要--force。处理方法我讲过先确认这些Pod是否可以丢弃再决定是否加--force。千万不要无脑加force很多业务方会在集群里用裸Pod跑一些临时任务丢掉可能会导致数据丢失。第三个是emptyDir卷。如果Pod挂在emptyDirdrain会提示数据会被删除除非你加了--delete-emptydir-data。这个参数我一般是加上的因为它只是“确认删除emptyDir数据”并不影响PVC。真正的风险点在前置评估阶段也就是你必须确认这个emptyDir里没有不可重建的数据。还有一个比较隐蔽的卡点是源自本地存储的PVC调度冲突。某些业务的PVC绑定到这台节点上的local PV卷Pod被驱逐后新Pod调度到其他节点但PVC依然指向原来的local PV导致新Pod一直ContainerCreating。这种情况下drain本身可能顺利完成但你会在之后发现一堆Pod起不来。处理方法是在drain之前先确认所有PVC都能跨节点工作不能的就提前处理。排查这些问题的核心命令是kubectl describe pod pod-name。它会显示Pod当前状态、最近事件、PDB拦截信息等关键内容。遇到卡住先别着急逐个Pod看describe输出比盲猜快得多。4.2 节点删除后“复活”这是我开头提到的自动注册问题。节点对象删了过一会儿又冒出来而且新节点对象上的状态可能是Ready也可能是NotReady。这是什么原因常见场景有两种。第一种是云厂商的节点组或自愈机制在起作用。比如阿里云ACK的节点池、AWS EKS的托管节点组它们会自动维持节点数量。你手动删了节点对象节点组发现数量不够立刻拉起新节点并自动加入集群。这种情况下你手动删除是没有意义的正确做法是在云控制台或通过Infrastructure as Code工具修改节点池的期望数量。第二种场景是kubelet仍在运行且有自动注册机制比如auto join脚本。虽然我建议你下线前停掉kubelet但如果你忘记做这一步或者机器上的脚本在重启后会自动执行kubeadm join那么节点对象的“复活”几乎是必然的。处理办法分两步第一步到节点上停掉kubelet和开机自启第二步找到并禁用自动join的机制比如systemd服务里的ExecStart脚本、cron定时任务、云厂商的用户数据脚本。确保没有这些“复活”源头之后再删除节点对象。这个顺序和我在3.1里说的完全一致先停服务再删对象。4.3 驱逐后Pod在新节点上起不来drain成功后Pod被驱赶到其他节点结果一堆Pod卡在Pending或ContainerCreating这也是我见过很多次的情况。Pending最常见的原因是资源不足或调度约束。新节点上CPU、内存不够或者Pod的nodeSelector、亲和性规则只匹配了下线的那台节点导致调度器找不到合适的归宿。查看事件信息里调度器的报错比如0/5 nodes are available基本就能确认了。处理方式要么扩容集群要么调整调度约束要么接受现实——这台节点上跑的Pod只能等集群扩容后再恢复。ContainerCreating常见的原因是镜像拉取失败或存储卷问题。特别是大镜像如果新节点上没有缓存而镜像仓库带宽又有限拉取超时很正常。这时候可以检查节点上的kubelet日志确认是拉取超时还是鉴权失败。存储问题往往更麻烦如果PVC绑定了local PV而源数据在下线节点上新节点根本挂载不上。这类问题只能在drain前通过业务侧的数据副本机制规避真到了驱逐后才发现基本只能接受数据丢失的后果。另外有一种情况特别坑Pod被驱逐后新Pod启动成功但服务注册中心里的旧实例没有及时下线导致了一段时间的流量错误路由。这个问题虽然不完全是K8s层面的事但作为运维你要有心理预期。解决方式是在变更前与服务注册中心或网关团队做好联动确保下线流程会通知注册中心摘除实例。4.4 节点下线后的验证清单整个流程结束后建议按下面这个清单做一次验证防止留尾巴# 1. 确认节点已删除 kubectl get node node-name # 应该报NotFound # 2. 确认所有Pod都处于Running或Succeeded状态 kubectl get pods -A | grep -v Running | grep -v Completed # 3. 确认集群核心组件健康 kubectl get pods -n kube-system -o wide # 4. 检查etcd健康如果涉及控制面节点 etcdctl endpoint health --cluster # 5. 确认监控里该节点target已摘除 # 6. 确认告警规则里没有该节点的残留我在实际运维里吃过不少亏要说体会最深的一点节点下线这件事七八成的风险都集中在“排水之前”。只要你把前置评估做透了把该备份的数据备份了把该和业务方确认的都确认了后面的操作其实都是按部就班。反过来如果前置没做好任何一步都可能变成事故现场。最后再分享一个我自己的习惯每次节点下线完成后我会把本次操作的所有命令、参数、修改过的配置、遇到的问题整理成一篇简短的变更总结存档到团队的运维文档里。这不仅仅是合规要求更重要的是当你下次再遇到类似场景时可以直接翻出上一次的记录哪些坑绕过去了、哪些参数需要调整一目了然。长期积累下来这些记录比任何K8s教程都更贴合你的实际环境。
返回列表