基于Golang的K8s节点健康检查Agent实现

7次阅读

kubelet 的 /healthz 不够可靠,因其仅检查进程存活,不验证 containerd、cadvisor、磁盘、内存等真实状态;agent 需直连底层探测并严格限制自身资源以防误判。

基于Golang的K8s节点健康检查Agent实现

为什么用 kubelet/healthz 不够可靠

因为 /healthz 只检查 kubelet 进程是否存活,不反映节点真实资源状态或 cAdvisorcontainerd 等关键组件是否可响应。线上常见现象是 /healthz 返回 200,但 Pod 却卡在 ContainerCreatingImagePullBackOff —— 实际是 containerd socket 响应超时或磁盘 inode 耗尽。

Agent 必须绕过 kubelet,直连底层服务做探测:

  • containerd:用 ctr --address /run/containerd/containerd.sock info 验证 socket 可达性与基础响应
  • cAdvisor:访问 http://127.0.0.1:10250/metrics/cadvisor(需 kubelet 开启 --cadvisor-port)确认指标采集通道正常
  • 磁盘健康:读取 /proc/mounts 后对每个挂载点调用 syscall.Statfs 检查 f_bavailf_files
  • 内存压力:解析 /sys/fs/cgroup/memory/kubepods/memory.pressure(cgroup v2)或 /proc/pressure/memory

如何避免 Agent 自身被 OOMKilled 导致误判

Agent 若无内存限制,在节点资源紧张时可能先于业务容器被杀,导致健康检查“假阳性”——上报节点失联,实则只是 Agent 挂了。

必须在 DaemonSet 中硬性约束资源,并关闭 go runtime 的内存抖动:

立即学习go语言免费学习笔记(深入)”;

  • Pod spec 中设置 resources.limits.memory: "32Mi",并配 resources.requests.memory: "16Mi"
  • 启动时加 GOMEMLIMIT=24Mi 环境变量,让 Go runtime 主动触发 GC 避免突破 limit
  • 禁用 debug.SetGCPercent(-1) 类操作;定期用 runtime.ReadMemStats 记录 RSS,超 20Mi 时主动 log 并退出

示例健康检查主循环节选:

for range time.Tick(10 * time.Second) {     if memStats.Sys > 20*1024*1024 {         log.Println("memory usage too high, exiting")         os.Exit(1)     }     // ... 其他探测逻辑 }

node-problem-detector 和自研 Agent 的边界在哪

node-problem-detector 是日志模式驱动的被动检测器,依赖解析 kubeletkernel 日志匹配预设正则。它反应慢(秒级延迟)、漏报多(如瞬时 cgroup v2 pressure spike 不写日志),且无法执行修复动作。

自研 Agent 应聚焦三件事:

  • 主动低开销轮询:所有探测控制在 100ms 内完成,总耗时 per cycle
  • 带上下文的状态聚合:比如 “disk pressure” 不只看 inodes ,还要结合 <code>containerd list images 耗时是否 > 2s
  • 输出结构化事件:往 /dev/stdoutjson 行,字段含 reasonseverity(warning/critical)、component(”containerd”, “rootfs”)

别试图复刻 NPD 的日志解析能力——那是运维侧 pipeline 的事,Agent 只管“此刻能不能干活”。

DaemonSet 中挂载宿主机路径的坑

Agent 需读取 /proc/sys/run/containerd 等路径,但默认 hostPath 挂载会因 SELinux 或 containerd rootless 模式失败。

必须显式声明挂载参数:

  • /proc:用 hostPath.type: DirectoryOrCreate,不可用 Directory(某些系统首次启动时 /proc 下子目录未就绪)
  • /run/containerd:指定 hostPath.type: Socket,否则挂载后文件权限为 0600 但非 root 用户无法访问
  • 若节点启用 cgroup v2,需挂载 /sys/fs/cgroupreadOnly: false(因要读 memory.pressure
  • 务必加 securityContext.privileged: false,靠 capabilities 补足:仅需 ["SYS_ADMIN"](用于 Statfs)和 ["NET_BIND_SERVICE"](若监听本地端口)

错误配置会导致 stat /run/containerd/containerd.sock: no such file or directory —— 实际是挂载失败后路径为空,而非 socket 不存在。

Agent 最容易被忽略的不是探测逻辑,而是它和节点上其他进程共享同一套内核资源视图。比如 Statfs 返回的可用 inodes 数,受 ext4 journal 大小、reserved blocks 影响,而这些值在不同发行版默认不同。不校准就报警,等于每天都在制造噪音。

text=ZqhQzanResources