Node Problem Detector (NPD) 完全指南
一、NPD简介
1.1 什么是Node Problem Detector?
Node Problem Detector (NPD) 是一个Kubernetes官方的节点问题检测工具,旨在使集群管理堆栈中的上游层能够看到各种节点问题。它作为一个守护进程在每个节点上运行,检测节点问题并将其报告给Kubernetes API Server。
1.2 为什么需要NPD?
在Kubernetes集群中,大量节点问题可能会影响节点上运行的Pod,例如:
- 基础设施守护进程问题:NTP服务关闭、SSH服务异常
- 硬件问题:CPU、内存或磁盘损坏、网络故障
- 内核问题:内核死锁、文件系统损坏、内核崩溃
- 容器运行时问题:Docker/Containerd守护进程无响应
- Kubernetes组件问题:Kubelet频繁重启、Kube-proxy异常
在NPD出现之前,这些问题对于集群管理堆栈中的上游层是不可见的,因此Kubernetes会继续将Pod调度到有问题的节点上,导致应用不稳定。
NPD的引入解决了这个问题,它能够:
- 实时检测节点层面的各种问题
- 将问题报告给Kubernetes API Server
- 触发自动化的故障处理流程
- 提高集群的稳定性和可靠性
1.3 NPD的应用场景
NPD已经被广泛应用于生产环境中:
- GKE (Google Kubernetes Engine):作为默认启用的Kubernetes Addon运行
- AKS (Azure Kubernetes Service):作为Linux Extension的一部分默认启用
- 企业自建集群:用于提升集群的故障检测和自动恢复能力
1.4 部署方式
NPD支持多种部署方式:
- DaemonSet部署:作为Kubernetes DaemonSet在每个节点上运行(推荐)
- 独立部署:作为独立的守护进程运行,适用于非Kubernetes环境
- 混合部署:结合两种方式,满足不同场景需求
二、核心概念和架构
2.1 Problem API
NPD使用两种方式向Kubernetes API Server报告问题:
NodeCondition
用于报告导致节点无法用于Pod的永久性问题,这些条件会直接影响节点的调度状态。
常见的NodeCondition类型:
KernelDeadlock:内核死锁ReadonlyFilesystem:文件系统只读FrequentKubeletRestart:Kubelet频繁重启FrequentDockerRestart:Docker频繁重启FrequentContainerdRestart:Containerd频繁重启KubeletUnhealthy:Kubelet不健康ContainerRuntimeUnhealthy:容器运行时不健康
Event
用于报告对Pod影响有限但具有参考意义的临时性问题。这些事件不会直接影响节点的调度状态,但可以用于故障诊断和趋势分析。
2.2 架构设计
NPD采用模块化的架构设计,主要包含以下几个核心组件:
┌─────────────────────────────────────────────────────────────┐│ Node Problem Detector │├─────────────────────────────────────────────────────────────┤│ Problem Daemons (问题守护进程) ││ ├─ SystemLogMonitor (系统日志监控) ││ ├─ SystemStatsMonitor (系统统计监控) ││ ├─ CustomPluginMonitor (自定义插件监控) ││ └─ HealthChecker (健康检查) │├─────────────────────────────────────────────────────────────┤│ Exporters (导出器) ││ ├─ Kubernetes Exporter (Kubernetes API) ││ ├─ Prometheus Exporter (Prometheus指标) ││ └─ Stackdriver Exporter (Google Cloud监控) │└─────────────────────────────────────────────────────────────┘2.3 工作流程
- 问题检测:Problem Daemons持续监控节点的各种状态和日志
- 问题分析:根据预定义的规则分析检测到的问题
- 问题报告:通过Exporters将问题报告到目标系统
- 问题处理:Kubernetes或其他系统根据报告的问题采取相应措施
- 状态更新:持续跟踪问题的状态变化
三、Problem Daemons详解
3.1 SystemLogMonitor (系统日志监控)
功能说明
系统日志监控器监视系统日志并根据预定义的规则报告问题和指标。它能够检测内核级别的错误、系统服务的异常等。
支持的配置类型
| 配置类型 | 说明 | 示例文件 |
|---|---|---|
| filelog | 文件日志监控 | kernel-monitor-filelog.json |
| kmsg | 内核消息监控 | kernel-monitor.json |
| kernel | 内核监控 | kernel-monitor-counter.json |
| abrt | ABRT (Automatic Bug Reporting Tool) 监控 | systemd-monitor-counter.json |
| systemd | Systemd服务监控 | systemd-monitor-counter.json |
检测的问题类型
KernelDeadlock:内核死锁ReadonlyFilesystem:文件系统变为只读FrequentKubeletRestart:Kubelet频繁重启FrequentDockerRestart:Docker频繁重启FrequentContainerdRestart:Containerd频繁重启
配置示例
{ "plugin": "kmsg", "logPath": "/dev/kmsg", "lookback": "5m", "bufferSize": 10, "source": "kernel-monitor", "conditions": [ { "type": "KernelDeadlock", "reason": "KernelDeadlock", "message": "kernel: BUG: soft lockup - CPU#%d stuck for %us! [%s:%d]" } ], "metrics": [ { "name": "kernel_deadlock_total", "help": "Number of kernel deadlocks detected", "labels": ["reason"] } ]}3.2 SystemStatsMonitor (系统统计监控)
功能说明
系统统计监控器用于收集各种与健康相关的系统统计信息作为指标,包括CPU使用率、内存使用情况、磁盘IO等。
监控指标
- CPU使用率
- 内存使用率
- 磁盘使用率和IO
- 网络流量
- 系统负载
配置示例
{ "plugin": "system-stats-monitor", "metrics": [ { "name": "node_cpu_usage_percentage", "help": "CPU usage percentage", "type": "gauge" }, { "name": "node_memory_usage_percentage", "help": "Memory usage percentage", "type": "gauge" } ]}3.3 CustomPluginMonitor (自定义插件监控)
功能说明
自定义插件监控器允许用户编写自定义的检查脚本来监控特定的问题,提供了极大的灵活性。
使用场景
- 监控特定的应用程序状态
- 检查自定义的服务健康状态
- 集成第三方的监控工具
- 实现业务特定的故障检测逻辑
配置示例
{ "plugin": "custom", "pluginConfigFile": "/etc/kubernetes/node-problem-detector/custom-plugin-config.json", "metrics": [ { "name": "custom_plugin_check_total", "help": "Number of custom plugin checks", "labels": ["result"] } ]}插件脚本示例
#!/bin/bash# NTP问题检测脚本
# 检查NTP服务状态if ! systemctl is-active --quiet ntpd; then echo "NTP service is not running" exit 1fi
# 检查NTP同步状态ntpq -p | grep "*"if [ $? -ne 0 ]; then echo "NTP is not synchronized" exit 1fi
echo "NTP is healthy"exit 03.4 HealthChecker (健康检查)
功能说明
健康检查器用于检查Kubelet和容器运行时的健康状况,确保Kubernetes核心组件的正常运行。
检测的问题类型
KubeletUnhealthy:Kubelet不健康ContainerRuntimeUnhealthy:容器运行时不健康
检查机制
- 定期检查Kubelet的健康端点
- 监控容器运行时的状态
- 检查关键服务的运行时间
- 分析相关日志的错误模式
配置选项
| 配置项 | 说明 | 默认值 |
|---|---|---|
| component | 组件名称 | - |
| service | 服务名称 | - |
| enableRepair | 是否启用自动修复 | false |
| healthCheckTimeout | 健康检查超时时间 | 30s |
| coolDownTime | 修复后的冷却时间 | 5m |
四、Exporters详解
4.1 Kubernetes Exporter
功能说明
Kubernetes Exporter向Kubernetes API Server报告节点问题:
- 临时问题报告为Event
- 永久问题报告为NodeCondition
工作原理
- 接收来自Problem Daemons的状态信息
- 根据问题的严重程度决定报告方式
- 调用Kubernetes API更新Node状态或创建Event
- 处理API调用的错误和重试
配置参数
| 参数 | 说明 | 默认值 |
|---|---|---|
| —enable-k8s-exporter | 是否启用Kubernetes导出器 | true |
| —apiserver-override | 自定义API Server地址 | - |
| —address | HTTP服务绑定地址 | 127.0.0.1 |
| —port | HTTP服务绑定端口 | 20256 |
HTTP端点
/healthz:健康检查端点/conditions:当前节点条件/debug/pprof:性能分析端点
4.2 Prometheus Exporter
功能说明
Prometheus Exporter将节点问题和指标本地报告为Prometheus指标,便于与Prometheus监控系统集成。
指标类型
- Counter:计数器类型,记录问题发生的次数
- Gauge:量规类型,记录当前问题的状态
配置参数
| 参数 | 说明 | 默认值 |
|---|---|---|
| —prometheus-address | Prometheus抓取端点地址 | 127.0.0.1 |
| —prometheus-port | Prometheus抓取端点端口 | 20257 |
指标示例
# HELP node_problem_detector_problem_total Number of times a specific type of problem have occurred.# TYPE node_problem_detector_problem_total counternode_problem_detector_problem_total{reason="KernelDeadlock"} 1node_problem_detector_problem_total{reason="ReadonlyFilesystem"} 0
# HELP node_problem_detector_problem_gauge Whether a specific type of problem is affecting node or not.# TYPE node_problem_detector_problem_gauge gaugenode_problem_detector_problem_gauge{type="KernelDeadlock",reason="KernelDeadlock"} 1node_problem_detector_problem_gauge{type="ReadonlyFilesystem",reason="ReadonlyFilesystem"} 04.3 Stackdriver Exporter
功能说明
Stackdriver Exporter向Google Cloud Stackdriver Monitoring API报告节点问题和指标,适用于GKE环境。
配置示例
{ "project_id": "your-project-id", "monitored_resource_type": "gke_instance", "monitored_resource_labels": { "project_id": "your-project-id", "location": "us-central1-a", "cluster_name": "your-cluster", "instance_id": "node-1" }}五、代码结构分析
5.1 项目结构
node-problem-detector/├── pkg/ # 核心代码包│ ├── custompluginmonitor/ # 自定义插件监控│ ├── exporters/ # 导出器实现│ │ ├── k8sexporter/ # Kubernetes导出器│ │ ├── prometheusexporter/ # Prometheus导出器│ │ └── stackdriver/ # Stackdriver导出器│ ├── healthchecker/ # 健康检查│ ├── logcounter/ # 日志计数器│ ├── problemdaemon/ # 问题守护进程框架│ ├── problemdetector/ # 问题检测器核心│ ├── problemmetrics/ # 问题指标管理│ ├── systemlogmonitor/ # 系统日志监控│ ├── systemstatsmonitor/ # 系统统计监控│ ├── types/ # 类型定义│ ├── util/ # 工具函数│ └── version/ # 版本信息├── cmd/ # 命令行工具├── config/ # 配置文件├── deployment/ # 部署文件├── test/ # 测试文件└── Makefile # 构建脚本5.2 核心组件分析
5.2.1 ProblemDaemon (问题守护进程)
Register函数
// 注册问题守护进程工厂方法func Register(problemDaemonType types.ProblemDaemonType, handler types.ProblemDaemonHandler) { handlers[problemDaemonType] = handler}NewProblemDaemons函数
// 根据配置创建所有问题守护进程func NewProblemDaemons(monitorConfigPaths types.ProblemDaemonConfigPathMap) []types.Monitor { problemDaemonMap := make(map[string]types.Monitor)
for problemDaemonType, configs := range monitorConfigPaths { for _, config := range *configs { if _, ok := problemDaemonMap[config]; ok { // 跳过重复配置 klog.Warningf("Duplicated problem daemon configuration %q", config) continue } problemDaemonMap[config] = handlers[problemDaemonType].CreateProblemDaemonOrDie(config) } }
problemDaemons := []types.Monitor{} for _, problemDaemon := range problemDaemonMap { problemDaemons = append(problemDaemons, problemDaemon) } return problemDaemons}5.2.2 ProblemDetector (问题检测器)
Run函数
// 启动问题检测器func (p *problemDetector) Run(ctx context.Context) error { // 启动所有监控器 var chans []<-chan *types.Status failureCount := 0
for _, m := range p.monitors { ch, err := m.Start() if err != nil { klog.Errorf("Failed to start problem daemon %v: %v", m, err) failureCount++ continue } if ch != nil { chans = append(chans, ch) } }
allMonitors := p.monitors
if len(allMonitors) == failureCount { return fmt.Errorf("no problem daemon is successfully setup") }
defer func() { for _, m := range allMonitors { m.Stop() } }()
ch := groupChannel(chans) klog.Info("Problem detector started")
for { select { case <-ctx.Done(): return nil case status := <-ch: for _, exporter := range p.exporters { exporter.ExportProblems(status) } } }}5.2.3 ProblemMetricsManager (问题指标管理器)
SetProblemGauge函数
// 设置问题量规的值func (pmm *ProblemMetricsManager) SetProblemGauge(problemType string, reason string, value bool) error { if pmm.problemGauge == nil { return errors.New("problem gauge is being set before initialized.") }
pmm.problemTypeToReasonMutex.Lock() defer pmm.problemTypeToReasonMutex.Unlock()
// 清除之前的原因,确保每个问题类型在任何时刻都最多只有一个原因被设置为1 if lastReason, ok := pmm.problemTypeToReason[problemType]; ok { err := pmm.problemGauge.Record(map[string]string{"type": problemType, "reason": lastReason}, 0) if err != nil { return fmt.Errorf("failed to clear previous reason %q for type %q: %v", problemType, lastReason, err) } }
pmm.problemTypeToReason[problemType] = reason
var valueInt int64 if value { valueInt = 1 } return pmm.problemGauge.Record(map[string]string{"type": problemType, "reason": reason}, valueInt)}5.3 HealthChecker (健康检查器)
healthChecker结构体
type healthChecker struct { component string service string enableRepair bool healthCheckFunc func() (bool, error) repairFunc func() uptimeFunc func() (time.Duration, error) crictlPath string healthCheckTimeout time.Duration coolDownTime time.Duration loopBackTime time.Duration logPatternsToCheck map[string]int}字段说明
component:被检查的Kubernetes组件名称service:服务名称或标识符enableRepair:是否启用自动修复healthCheckFunc:健康检查函数repairFunc:修复函数uptimeFunc:获取服务运行时间的函数healthCheckTimeout:健康检查超时时间coolDownTime:修复后的冷却时间loopBackTime:日志回溯时间logPatternsToCheck:需要检查的日志模式
六、部署和配置
6.1 搭建Kind集群
基于kind搭建测试集群快速创建一个可用的测试集群。
配置:
kind: ClusterapiVersion: kind.x-k8s.io/v1alpha4networking: apiServerAddress: "192.168.1.16"nodes:- role: control-plane extraPortMappings: - containerPort: 6443 hostPort: 6443 listenAddress: "192.168.1.16" protocol: tcp- role: control-plane- role: control-plane- role: worker- role: worker创建高可用集群命令:
sudo kind create cluster --config=huari.yaml --name huari-test --image kindest/node:v1.34.0 --retain; sudo kind export logs --name huari-test切换kubectl上下文:
sudo kubectl cluster-info --context kind-huari-test查看信息:
# 查看集群节点sudo kubectl get nodes
# 查看集群全部的podsudo kubectl get pods -A -owide删除集群:
sudo kind delete cluster --name huari-test6.2 Helm部署
# 添加Helm仓库helm repo add deliveryhero https://charts.deliveryhero.io/
# 安装NPDhelm install node-problem-detector deliveryhero/node-problem-detector \ --namespace kube-system \ --create-namespace
# 安装NPD(代理)helm upgrade node-problem-detector deliveryhero/node-problem-detector \ --namespace kube-system \ --reuse-values \ --set image.repository=m.daocloud.io/k8s.gcr.io/node-problem-detector/node-problem-detector \ --set image.tag=v0.8.156.3 配置参数
启动参数
| 参数 | 说明 | 默认值 |
|---|---|---|
| —version | 打印版本信息 | - |
| —hostname-override | 自定义节点名称 | - |
| —config.system-log-monitor | 系统日志监控配置文件路径 | - |
| —config.system-stats-monitor | 系统统计监控配置文件路径 | - |
| —config.custom-plugin-monitor | 自定义插件监控配置文件路径 | - |
| —enable-k8s-exporter | 启用Kubernetes导出器 | true |
| —prometheus-address | Prometheus抓取端点地址 | 127.0.0.1 |
| —prometheus-port | Prometheus抓取端点端口 | 20257 |
七、使用和测试
7.1 基本使用
查看NPD日志
# 查看NPD Pod日志kubectl logs -n kube-system -l app=node-problem-detector -f
# 查看特定节点的NPD日志kubectl logs -n kube-system node-problem-detector-xxxxx -f查看节点状态
# 查看节点条件kubectl describe node <node-name>
# 查看节点事件kubectl get events --field-selector involvedObject.name=<node-name>查看Prometheus指标
# 启动kubectl proxykubectl proxy --port=8080
# 访问Prometheus端点curl http://<node-ip>:20257/metrics7.2 测试NPD功能
测试内核死锁检测
# 在一个终端中监控事件kubectl get events -w
# 在节点上注入测试消息sudo sh -c "echo 'kernel: BUG: unable to handle kernel NULL pointer dereference at TESTING' >> /dev/kmsg"测试Docker挂起检测
# 在节点上注入测试消息sudo sh -c "echo 'kernel: INFO: task docker:20744 blocked for more than 120 seconds.' >> /dev/kmsg"八、Remedy Systems (补救系统)
8.1 概述
Remedy Systems是一个或多个旨在尝试解决NPD检测到的问题的过程。它们会观察NPD发出的事件和/或节点状况,并采取措施使Kubernetes集群恢复健康状态。
8.2 常见的Remedy Systems
8.2.1 Draino
功能:根据标签和节点条件自动排空Kubernetes节点。
工作原理:
- 监控节点条件和标签
- 匹配特定条件的节点被标记为不可调度
- 在可配置的时间后执行排空操作
- 可以与Cluster Autoscaler结合使用自动终止节点
使用场景:
- 自动处理硬件故障节点
- 节点维护自动化
- 成本优化(自动终止空闲节点)
8.2.2 Descheduler
功能:取消调度违反NoSchedule污点的Pod。
工作原理:
- 监控节点的污点状态
- 驱逐违反NoSchedule污点的Pod
- 确保Pod调度到健康的节点
要求:
- 启用Kubernetes调度器的TaintNodesByCondition功能
8.2.3 Cluster Autoscaler
功能:自动扩展Kubernetes集群的节点数量。
工作原理:
- 监控Pod的调度状态
- 当有Pod无法调度时,自动增加节点
- 当节点空闲时,自动减少节点
与NPD集成:
- 自动终止被排空的节点
- 替换不健康的节点
GitHub:https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler
8.2.4 mediK8S
功能:基于Node Health Check Operator (NHC)构建的自动修复系统。
工作原理:
- 监控节点状况
- 使用修复API将修复委托给外部修复程序
- 支持有条件的修复和手动暂停
GitHub:https://github.com/medik8s
8.2.5 Poison-Pill
功能:重新启动节点并确保所有有状态的工作负载都得到重新安排。
工作原理:
- 检测到严重问题时触发
- 执行节点重启操作
- 确保工作负载的重新调度
8.2.6 Cluster API MachineHealthCheck
功能:负责修复不健康的机器。
工作原理:
- 监控Machine对象的健康状态
- 自动替换不健康的Machine
- 与Cluster API集成
文档:https://cluster-api.sigs.k8s.io/developer/architecture/controllers/machine-health-check
8.3 集成方案
方案一:NPD + Draino + Cluster Autoscaler
NPD检测问题 → 标记节点为不可调度 → Draino排空节点 → Cluster Autoscaler终止节点 → 自动创建新节点方案二:NPD + Descheduler
NPD检测问题 → 设置节点污点 → Descheduler驱逐Pod → Pod重新调度到健康节点方案三:NPD + mediK8S
NPD检测问题 → NHC监控状态 → mediK8S执行修复 → Poison-Pill重启节点附录
部分信息可能已经过时









