如何使用Javascript Kubernetes Client实现K8s Node优雅排空?
使用@kubernetes/client-node优雅移除K8s节点的完整流程
要实现K8s节点的优雅移除,核心流程是标记节点不可调度(cordon)→ 驱逐节点上的Pod(drain)→ 删除节点(可选),下面是用@kubernetes/client-node实现的完整代码示例:
1. 依赖准备
确保已经安装客户端库:
npm install @kubernetes/client-node
2. 完整代码实现
const k8s = require('@kubernetes/client-node'); // 初始化K8s客户端配置 const kc = new k8s.KubeConfig(); kc.loadFromDefault(); // 从默认位置加载配置(~/.kube/config或环境变量) const coreV1Api = kc.makeApiClient(k8s.CoreV1Api); const policyV1Api = kc.makeApiClient(k8s.PolicyV1Api); /** * 标记节点为不可调度(cordon) * @param {string} nodeName 目标节点名称 */ async function cordonNode(nodeName) { try { const node = await coreV1Api.readNode(nodeName); // 修改spec.unschedulable为true node.body.spec.unschedulable = true; await coreV1Api.replaceNode(nodeName, node.body); console.log(`节点 ${nodeName} 已标记为不可调度`); } catch (err) { console.error(`Cordon节点失败: ${err.message}`); throw err; } } /** * 驱逐节点上的Pod(drain) * @param {string} nodeName 目标节点名称 * @param {object} options 驱逐选项,比如是否忽略DaemonSet、删除本地存储Pod等 */ async function drainNode(nodeName, options = {}) { const { ignoreDaemonSets = true, deleteLocalStoragePods = false, gracePeriodSeconds = 30 } = options; try { // 获取节点上所有非DaemonSet的Pod const podList = await coreV1Api.listPodForAllNamespaces(undefined, undefined, undefined, `spec.nodeName=${nodeName}`); for (const pod of podList.body.items) { const podName = pod.metadata.name; const namespace = pod.metadata.namespace; // 跳过DaemonSet管理的Pod(如果设置ignoreDaemonSets) if (ignoreDaemonSets && pod.metadata.ownerReferences?.some(ref => ref.kind === 'DaemonSet')) { console.log(`跳过DaemonSet Pod: ${namespace}/${podName}`); continue; } // 跳过本地存储Pod(如果未设置删除) if (!deleteLocalStoragePods && pod.spec.volumes?.some(vol => vol.hostPath)) { console.log(`跳过带本地存储的Pod: ${namespace}/${podName}`); continue; } // 创建Eviction对象 const eviction = { apiVersion: 'policy/v1', kind: 'Eviction', metadata: { name: podName, namespace: namespace }, spec: { gracePeriodSeconds: gracePeriodSeconds } }; // 执行驱逐 await policyV1Api.createNamespacedEviction(namespace, eviction); console.log(`已驱逐Pod: ${namespace}/${podName}`); } console.log(`节点 ${nodeName} 上的可驱逐Pod已处理完成`); } catch (err) { console.error(`Drain节点失败: ${err.message}`); throw err; } } /** * 删除节点(可选,根据需求执行) * @param {string} nodeName 目标节点名称 */ async function deleteNode(nodeName) { try { await coreV1Api.deleteNode(nodeName); console.log(`节点 ${nodeName} 已删除`); } catch (err) { console.error(`删除节点失败: ${err.message}`); throw err; } } // 执行完整流程的示例 async function gracefulRemoveNode(nodeName) { try { await cordonNode(nodeName); await drainNode(nodeName, { deleteLocalStoragePods: false }); // 如果需要删除节点,解开下面的注释 // await deleteNode(nodeName); console.log(`节点 ${nodeName} 优雅移除流程完成`); } catch (err) { console.error(`优雅移除节点失败: ${err.message}`); } } // 替换为你的目标节点名称 gracefulRemoveNode('your-node-name');
关键说明
- Cordon节点:通过修改Node的
spec.unschedulable字段,阻止新Pod调度到该节点。 - Drain节点:遍历节点上的Pod,对符合条件的Pod调用Eviction API实现优雅驱逐。注意:
- 默认跳过DaemonSet管理的Pod(因为DaemonSet会自动重新调度到其他节点)
- 默认保留带本地存储的Pod(避免数据丢失,如需删除可设置
deleteLocalStoragePods: true)
- 删除节点:如果需要彻底移除节点资源,最后执行删除操作,否则节点会保留在集群中(状态为NotReady)。
内容的提问来源于stack exchange,提问作者Carlo Corradini
相关产品推荐
相关产品推荐

