当前位置: 首页 > article >正文

使用 client-go 实现 Kubernetes 节点 Drain:详解与实战教程

在 Kubernetes 中使用 client-go 实现 drain 功能涉及多个步骤,需要模仿 kubectl drain 的行为。这包括将节点标记为不可调度(cordon)、驱逐 Pod,并处理 DaemonSet 和不可驱逐 Pod 的逻辑。以下是实现 drain 的主要步骤:


1. 基本思路

drain 的核心步骤包括:

  1. 标记节点为不可调度(cordon)
  2. 列出节点上的所有 Pod
  3. 逐一驱逐可驱逐的 Pod
    • 忽略 DaemonSet 创建的 Pod。
    • 检查 PodDisruptionBudget(PDB)约束。
  4. 等待 Pod 驱逐完成

2. 代码实现

以下是一个基于 client-go 的示例代码,演示如何实现 drain 功能。

package main

import (
	"context"
	"fmt"
	"time"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	// 加载 kubeconfig
	config, err := clientcmd.BuildConfigFromFlags("", "/path/to/kubeconfig")
	if err != nil {
		panic(err)
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err)
	}

	nodeName := "your-node-name"
	err = drainNode(clientset, nodeName)
	if err != nil {
		fmt.Printf("Failed to drain node: %v\n", err)
	} else {
		fmt.Printf("Node %s drained successfully\n", nodeName)
	}
}

// DrainNode 实现 drain 功能
func drainNode(clientset *kubernetes.Clientset, nodeName string) error {
	ctx := context.TODO()

	// Step 1: 将节点标记为不可调度
	if err := cordonNode(clientset, nodeName); err != nil {
		return fmt.Errorf("failed to cordon node: %w", err)
	}

	// Step 2: 获取节点上的所有 Pod
	podList, err := listPodsOnNode(clientset, nodeName)
	if err != nil {
		return fmt.Errorf("failed to list pods on node: %w", err)
	}

	// Step 3: 驱逐所有可驱逐的 Pod
	for _, pod := range podList.Items {
		if isDaemonSetPod(&pod) || isMirrorPod(&pod) {
			// 忽略 DaemonSet 和 Mirror Pod
			continue
		}

		// 驱逐 Pod
		err := evictPod(clientset, &pod)
		if err != nil {
			return fmt.Errorf("failed to evict pod %s: %w", pod.Name, err)
		}
	}

	// Step 4: 等待所有 Pod 被驱逐
	err = wait.PollImmediate(2*time.Second, 5*time.Minute, func() (bool, error) {
		remainingPods, err := listPodsOnNode(clientset, nodeName)
		if err != nil {
			return false, err
		}

		// 检查是否还有非 DaemonSet/Mirror 的 Pod
		for _, pod := range remainingPods.Items {
			if !isDaemonSetPod(&pod) && !isMirrorPod(&pod) {
				return false, nil
			}
		}

		return true, nil
	})
	if err != nil {
		return fmt.Errorf("timeout waiting for pods to be evicted: %w", err)
	}

	return nil
}

// 将节点标记为不可调度
func cordonNode(clientset *kubernetes.Clientset, nodeName string) error {
	ctx := context.TODO()

	node, err := clientset.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	if node.Spec.Unschedulable {
		// 已经是不可调度状态
		return nil
	}

	node.Spec.Unschedulable = true
	_, err = clientset.CoreV1().Nodes().Update(ctx, node, metav1.UpdateOptions{})
	return err
}

// 获取节点上的所有 Pod
func listPodsOnNode(clientset *kubernetes.Clientset, nodeName string) (*v1.PodList, error) {
	ctx := context.TODO()
	podList, err := clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{
		FieldSelector: fmt.Sprintf("spec.nodeName=%s", nodeName),
	})
	if err != nil {
		return nil, err
	}
	return podList, nil
}

// 检查是否为 DaemonSet 创建的 Pod
func isDaemonSetPod(pod *v1.Pod) bool {
	for _, owner := range pod.OwnerReferences {
		if owner.Kind == "DaemonSet" {
			return true
		}
	}
	return false
}

// 检查是否为 Mirror Pod
func isMirrorPod(pod *v1.Pod) bool {
	_, exists := pod.Annotations["kubernetes.io/config.mirror"]
	return exists
}

// 驱逐 Pod
func evictPod(clientset *kubernetes.Clientset, pod *v1.Pod) error {
	ctx := context.TODO()

	eviction := &v1.Eviction{
		ObjectMeta: metav1.ObjectMeta{
			Name:      pod.Name,
			Namespace: pod.Namespace,
		},
	}
	err := clientset.CoreV1().Pods(pod.Namespace).Evict(ctx, eviction)
	if err != nil {
		return err
	}
	return nil
}

3. 实现细节说明

  1. 节点 Cordon

    • 使用 clientset.CoreV1().Nodes().Update 更新节点的 Unschedulable 字段。
  2. 列出节点上的 Pod

    • 使用 FieldSelector 过滤出运行在目标节点上的 Pod。
  3. DaemonSet 和 Mirror Pod

    • 通过 Pod 的 OwnerReferences 判断是否属于 DaemonSet。
    • 通过检查 kubernetes.io/config.mirror 注解判断是否为 Mirror Pod。
  4. Pod 驱逐

    • 使用 Eviction 对象发起驱逐请求。
    • 需要确保 Pod 不违反 PodDisruptionBudget
  5. 等待驱逐完成

    • 使用 wait.PollImmediate 轮询检查节点上的 Pod 是否已被全部驱逐。

4. 注意事项

  1. 权限要求

    • 执行 cordonevict 操作需要相应的集群权限(如 nodes/updatepods/evict)。
  2. DaemonSet Pod 的处理

    • 默认忽略 DaemonSet Pod,如需强制移除需特殊处理。
  3. 驱逐策略

    • 驱逐操作可能受到 PodDisruptionBudget 的限制,需要确保驱逐不会违反 PDB。
  4. 资源规划

    • 确保其他节点有足够的资源接收被驱逐的 Pod。

以上代码和说明提供了一个完整的 client-go 实现 drain 的方法,适合在自定义工具或自动化运维场景中使用。


http://www.kler.cn/a/418570.html

相关文章:

  • 【青牛科技】2K02 电动工具专用调速电路芯片描述
  • 行列式与线性方程组解的关系
  • 利用Python爬虫精准获取淘宝商品详情的深度解析
  • LabVIEW将TXT文本转换为CSV格式(多行多列)
  • Paper -- 建筑物高度估计 -- 基于深度学习、图像处理和自动地理空间分析的街景图像建筑高度估算
  • 1.1 数据结构的基本概念
  • C_接口函数
  • 特性标记清理:GitHub Actions 来帮忙!
  • colorthief.js(图像中自动提取出主色调、调色板或者平均颜色)源码解析MMCQ算法
  • SnowFlake
  • 如何正确使用 GitHub API 获取特定版本信息:详解错误排查与解决方案
  • Word - 图片右键保存
  • Scala关于成绩的常规操作
  • 华为云云连接+squid进行正向代理上网冲浪
  • 【RabbitMQ 消息列队测试之:调试技巧】
  • 【Python数据分析五十个小案例】使用自然语言处理(NLP)技术分析 Twitter 情感
  • 初级数据结构——邻接表
  • 使用图结构增强RAG架构,一文详解LightRAG
  • docker安装clickhouse(单机版)
  • 解决Qt堆栈窗口内子窗口大小不一致的问题
  • HTML5+JavaScript实现消消乐游戏
  • Flask项目入门—request以及Response
  • C与指针。
  • 深度解析MySQL的刷脏机制
  • 11. 名称空间
  • 深入解析 MySQL 启动方式:`systemctl` 与 `mysqld` 的对比与应用