K8s Node Exporter 异常 Write 超时排查实录

0 阅读4分钟

适用人群: Kubernetes 运维工程师、监控运维
适用场景: Prometheus + Node Exporter 监控栈
核心内容: 从内核版本到容器配置,定位 Node Exporter 大量 write 超时的根因


前言

公司网络变更重启服务器后,Prometheus 监控中 3 个 Node 节点掉线。实际上节点服务器正常运行,但 Node Exporter 的 /metrics 接口响应超时,导致 Prometheus 抓取失败。

本文记录了完整的排查过程,最终定位到 内核版本 bug,并给出了临时和永久解决方案。


一、问题现象

1.1 监控表现

  • Prometheus 显示 3 个 Node 节点 DOWN
  • 节点服务器本身运行正常(SSH 可登录、业务正常)
  • Node Exporter Pod 运行状态正常

1.2 手动测试

# 在 Prometheus 服务器上测试
curl -o /dev/null -s -w '%{time_connect}:%{time_starttransfer}:%{time_total}\n' 'http://<NodeIP>:9100/metrics'

结果:

节点time_connecttime_total状态
1630.001s0.005s✅ 正常
1670.001s0.004s✅ 正常
其他0.001s> 30s❌ 超时

关键发现: time_connect 很快(排除网络问题),但 time_total 超长,说明问题在 Node Exporter 处理请求阶段。


二、排查过程

2.1 查看 Pod 日志

kubectl -n monitoring logs -f <node-exporter-pod-name>

发现大量错误:

ts=2023-01-11T06:11:08.189Z caller=stdlib.go:105 level=error
  caller="error encoding and sending metric family: write tcp <内网IP>:<端口>"
  msg="-> <内网IP>:40743: write: broken pipe"

错误含义: Node Exporter 在编码和发送 metrics 数据时,TCP 连接被对端(Prometheus)关闭了(broken pipe)。

2.2 初步排查方向

根据社区经验和资料,可能的原因:

  1. Prometheus 的 scrape_timeout 太短
  2. Node Exporter 资源限制(CPU/内存)
  3. Node Exporter 配置问题
  4. 内核或系统层面问题

2.3 尝试方案一:调大 scrape_timeout

修改 Prometheus 配置,增加抓取超时时间:

# prometheus.yml
scrape_configs:
  - job_name: 'node'
    scrape_timeout: 60s  # 从默认 10s 调大

结果: 问题依旧,不是 scrape_timeout 的问题。

2.4 尝试方案二:调大 Node Exporter 资源限制

检查当前 Node Exporter 的资源配置:

# 当前配置(没有设置 request/limit)
resources: {}

结果: 不是资源限制问题。

2.5 尝试方案三:修改 Node Exporter 配置

参考社区方案,修改 DaemonSet 配置:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
spec:
  template:
    spec:
      containers:
      - name: node-exporter
        args:
        - --web.listen-address=$(HOSTIP):9100
        - --path.procfs=/host/proc
        - --path.sysfs=/host/sys
        - --path.rootfs=/host          # 修改:原来是 /rootfs
        - --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+)($|/)
        - --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$
        - --log.level=debug
        env:
        - name: HOSTIP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        volumeMounts:
        - mountPath: /host/proc
          name: proc
        - mountPath: /host/sys
          name: sys
        - mountPath: /host           # 修改:原来是 /rootfs
          name: rootfs
      volumes:
      - hostPath:
          path: /
          type: Directory
        name: rootfs

观察一天后: 问题依旧,其中一个节点报权限不足。

2.6 尝试方案四:添加 runAsUser

securityContext:
  runAsUser: 0   # 添加:默认是 nobody 用户(UID 65534)

再观察一天: 问题仍然存在,大量 write: broken pipe 错误。


三、根因定位

3.1 关键线索

排查过程中发现一个规律:

节点内核版本状态
节点 A5.4.0-132❌ 异常
节点 B5.4.0-132❌ 异常
节点 C5.4.0-132❌ 异常
节点 D5.15.0✅ 正常
节点 E5.15.0✅ 正常

结论: 所有内核为 5.4.0-132 的节点都有问题,5.15.0 的节点正常。

3.2 社区验证

在 GitHub 上找到相关 Issue:prometheus/node_exporter#2500

该 Issue 描述了相同的问题:Node Exporter 在高并发或大数据量场景下,特定内核版本会出现 write: broken pipe 错误。

根因: Linux 内核 5.4.0-132 版本存在 bug,影响 Node Exporter 的 metrics 数据发送。


四、解决方案

4.1 临时方案:设置 GOMAXPROCS=1

在 Node Exporter 的环境变量中限制 Go 程序的 CPU 使用:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
spec:
  template:
    spec:
      containers:
      - name: node-exporter
        env:
        - name: GOMAXPROCS
          value: "1"    # 添加此环境变量
        - name: HOSTIP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP

原理: 限制 Go 运行时只使用 1 个 OS 线程,避免内核 bug 触发。

4.2 永久方案:升级内核

# Ubuntu/Debian
apt-get update
apt-get install -y linux-image-5.15.0-generic
reboot

或者使用 LTS 内核:

# Ubuntu HWE 内核
apt-get install -y linux-generic-hwe-20.04
reboot

五、验证

5.1 验证临时方案

# 重启 Node Exporter
kubectl -n monitoring rollout restart daemonset node-exporter

# 观察日志
kubectl -n monitoring logs -f <node-exporter-pod> | grep error
# 应该没有新的 write: broken pipe 错误

# 测试抓取
curl -o /dev/null -s -w '%{time_total}\n' 'http://<NodeIP>:9100/metrics'
# 应该在 1 秒内返回

5.2 验证 Prometheus

# 检查 Prometheus Targets
# 访问 Prometheus UI → Status → Targets
# 所有 Node 节点应该显示为 UP

六、完整修复后的 DaemonSet 配置

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
  labels:
    app: node-exporter
spec:
  selector:
    matchLabels:
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
      annotations:
        prometheus.io/scrape: "true"
    spec:
      hostNetwork: true
      hostPID: true
      hostIPC: true
      containers:
      - name: node-exporter
        image: prom/node-exporter:latest
        args:
        - --web.listen-address=$(HOSTIP):9100
        - --path.procfs=/host/proc
        - --path.sysfs=/host/sys
        - --path.rootfs=/host
        - --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+)($|/)
        - --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$
        env:
        - name: HOSTIP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: GOMAXPROCS
          value: "1"  # 临时修复内核 bug
        ports:
        - containerPort: 9100
          hostPort: 9100
          protocol: TCP
        securityContext:
          privileged: true
          runAsUser: 0
        volumeMounts:
        - name: proc
          mountPath: /host/proc
        - name: sys
          mountPath: /host/sys
        - name: rootfs
          mountPath: /host
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      volumes:
      - name: proc
        hostPath:
          path: /proc
      - name: sys
        hostPath:
          path: /sys
      - name: rootfs
        hostPath:
          path: /

七、排查思路总结

1. 确认现象
   ├── Prometheus 显示 Node DOWN
   ├── 节点本身正常
   └── curl 测试确认超时

2. 排除网络
   ├── time_connect 很快 → 网络正常
   └── 问题在 Node Exporter 处理阶段

3. 查看日志
   ├── 大量 write: broken pipe
   └── 说明数据发送阶段失败

4. 尝试常见修复
   ├── 调大 scrape_timeout → 无效
   ├── 调大资源限制 → 无效
   ├── 修改挂载路径 → 无效
   └── 添加 runAsUser → 无效

5. 对比分析
   ├── 对比正常和异常节点的差异
   └── 发现内核版本不同

6. 社区验证
   ├── 搜索 GitHub Issue
   └── 确认是内核 bug

7. 修复
   ├── 临时:GOMAXPROCS=1
   └── 永久:升级内核

八、常见问题 FAQ

Q1:GOMAXPROCS=1 会影响性能吗?

对于 Node Exporter 这种轻量级监控程序,影响很小。它主要是采集和暴露 metrics,不需要大量 CPU 计算。

Q2:如何批量检查集群内核版本?

kubectl get nodes -o wide
# 或者
ansible all -m command -a "uname -r"

Q3:升级内核会影响业务吗?

升级内核需要重启节点。建议:

  • 逐台升级,避免同时重启
  • 业务低峰期操作
  • 先驱逐 Pod:kubectl drain <node> --ignore-daemonsets

Q4:还有其他监控组件有类似问题吗?

某些版本的 cAdvisor、Process Exporter 也可能受内核版本影响。如果遇到类似问题,可以尝试相同的排查思路。


总结

本次 Node Exporter 异常的根因是 Linux 内核 5.4.0-132 版本的 bug,导致 Node Exporter 在发送 metrics 数据时出现 write: broken pipe 错误。

关键排查技巧:

  1. curl -w 精确测量各阶段耗时
  2. 对比正常和异常节点的差异(内核版本、配置等)
  3. 善用 GitHub Issue 搜索社区经验

修复方案:

  • 临时:设置 GOMAXPROCS=1
  • 永久:升级到 5.15.0+ 内核

本文基于 Kubernetes 1.18 + Prometheus 2.x + Node Exporter 1.x 环境整理。