资讯动态

K8S 节点监控:node-exporter 资源清单与镜像文件部署实践

发布时间:2026/10/22 23:46:11 来源:尧图企业网站定制
简介面向Kubernetes运维与监控实践者这套资料围绕Prometheus生态中的node-exporter组件整理了在K8s集群内完成主机监控部署所需的全部文件。内容涵盖Service、ConfigMap、Deployment等核心YAML清单以及配套告警规则与镜像导出包可直接用于搭建或扩充集群节点监控体系尤其适合已掌握K8s基础操作、希望快速落地监控告警的工程师。包体信息资源共7个文件以5个yaml清单为主辅以1个txt说明文件和1个gz镜像压缩包整体大小约72.83MB。yaml文件负责定义服务暴露、配置挂载与实例调度txt文件提供使用或排错提示镜像包免去联网拉取步骤便于离线环境部署。该资源当前已有621人学习较高的关注度也印证了其在日常监控场景中的实用性。获取后可直接参考清单结构、调整镜像地址或告警阈值减少从零书写配置的时间成本同时通过自带镜像保证版本一致性快速获得一套可运行的主机监控方案。1. 一台节点失联前node-exporter 能让你提前 10 分钟知道部署过 K8S 的人应该都有过这种感觉容器层监控全绿Prometheus 里的 CPU、内存曲线都正常可某天节点突然 NotReady或者磁盘写满导致整个业务 Pod 被驱逐。回头查监控才发现节点主机的磁盘使用率、网络丢包率从来没采集过。K8S 主机这一层的指标靠的是这一课的主角 node-exporter。它由 Prometheus 官方维护安装在每个节点上把 /proc、/sys 里的节点运行数据转换成标准指标暴露在 9100 端口。标题里的“资源清单”就是把它跑起来所需的 DaemonSet / Service 等 YAML 文件“镜像文件”则是从镜像仓库拉到离线集群的容器镜像。无论你是刚接触 Prometheus 的运维还是已经踩过“节点告警查不到数据”的 SRE本文都值得看完。2. 用 DaemonSet 把 node-exporter 铺到每个节点最小资源清单与参数解读2.1 为什么是 DaemonSet hostNetwork而不是 Deployment 加端口转发node-exporter 做的事情是读取宿主机上的 /proc、/sys、/ 根文件系统。如果把它当普通 Deployment 部署容器内看到的是容器自己的 /proc采集出来的数据就变成“容器视角”进程数、内存、磁盘都是错的。所以必须保证集群里每个节点都且只有一个 exporter。正好 DaemonSet 就是为这类需求设计的控制器新节点加入自动调一个 Pod节点删除自动回收。网络模式方面我会毫不犹豫地用 hostNetwork: true。原因有两点第一抓取地址直接是 nodeIP:9100Prometheus 静态配置或服务发现都很直观不需要担心 kube-proxy 转发目标写错第二node-exporter 也要经过 hostNetwork 才能可靠拿宿主机网卡层面的指标比如 node_network_receive_bytes_total如果走容器网络得到的是虚拟网卡的数据容易误导排障。缺点也有9100 端口会在宿主机监听如果你早有一个类似 exporter 占用了这个端口就需要改监听地址这个坑等下单独说。2.2 最小 DaemonSet 资源清单三处挂载必须对下面的 YAML 是我在模拟项目 X 里一直沿用的一套最小清单去掉了很多花哨的容器安全配置只保留核心挂载和参数方便你快速跑通。apiVersion: apps/v1 kind: DaemonSet metadata: name: node-exporter namespace: monitoring spec: selector: matchLabels: app: node-exporter template: metadata: labels: app: node-exporter spec: hostNetwork: true hostPID: true tolerations: - operator: Exists containers: - name: node-exporter image: quay.io/prometheus/node-exporter:latest args: - --path.procfs/host/proc - --path.sysfs/host/sys - --path.rootfs/host/root - --collector.filesystem.mount-points-exclude^/(dev|proc|sys|run|var/lib/docker/.|var/lib/containers/.)($|/) - --collector.filesystem.fs-types-exclude^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|securityfs|squashfs|sysfs|tracefs)$ ports: - name: metrics containerPort: 9100 volumeMounts: - name: proc mountPath: /host/proc readOnly: true - name: sys mountPath: /host/sys readOnly: true - name: root mountPath: /host/root mountPropagation: HostToContainer readOnly: true resources: requests: cpu: 50m memory: 128Mi limits: cpu: 200m memory: 256Mi volumes: - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys - name: root hostPath: path: /上面的 args 参数需要逐个说清楚。--path.procfs 和 --path.sysfs 指定 node-exporter 去宿主机的 /proc、/sys 读取数据所以挂载时把它们映射到容器的 /host/proc 和 /host/sys。--path.rootfs 指向宿主机根目录的映射它用于文件系统各挂载点、磁盘使用率这类指标。如果你用的是较新版本还需要把宿主机根目录挂载进来并设置 mountPropagation: HostToContainer否则容器内看不见宿主机后续新增的挂载点比如临时插入的磁盘这属于那种“数据偶尔少一块”的玄学问题实际上就是挂载传播没配。后面的两条 --collector 参数是必须吃饭的家伙。第一条 mount-points-exclude 把 /dev、/proc、/sys、/run 以及容器运行时的工作目录排除掉第二条 fs-types-exclude 把 overlay、tmpfs、autofs 等伪文件系统排除掉。不排除会怎样你的磁盘指标表里会出现成千上万个 overlay 挂载点就是每个容器层都当成一个独立磁盘告警没法写。tolerations 用 operator: Exists 表示容忍所有污点这样 master / control-plane 节点也能被调度保证全节点覆盖。hostPID: true 是为了拿到宿主机所有进程的信息比如进程状态、运行数量如果不开node-exporter 只能看到容器内的进程。2.3 Service 要不要建Headless 方式与端口命名很多教程会在 DaemonSet 旁边配一个 Service。我的习惯是如果没有用 prometheus-operator 的 ServiceMonitor这个 Service 不是必须的直接让 Prometheus 配节点 IP 列表即可如果要用 ServiceMonitor那就需要一个 Service而且要建 Headless。apiVersion: v1 kind: Service metadata: name: node-exporter namespace: monitoring labels: app: node-exporter spec: clusterIP: None selector: app: node-exporter ports: - name: metrics port: 9100 targetPort: 9100clusterIP: None 会让这个 Service 不加虚拟 IPDNS 查询直接返回后端 Pod IP 列表这样 Prometheus 用 kubernetes_sd_configs 做服务发现时拿到的就是一个个真实 Pod IP配合后面讲的 relabel 就能把 instance 稳定成节点 IP。port 的 name 我固定写 metrics这个名字必须和 ServiceMonitor 的 endpoint.port 完全一致否则 operator 模式下 target 永远是 0。2.4 资源配额与调度容忍度、优先类、防止抢占node-exporter 本身很轻默认 collector 全开时内存占用在 40~80MB 左右CPU 也就偶尔波动到几十毫核。我给的 requests/limits 是经过多集群验证的值其中 memory limit 256Mi 能让它在极端大节点上不至于被 OOMKill而 cpu limit 200m 又能防止它出问题时拖垮节点。如果集群比较大还建议给 node-exporter 加一个 priorityClassName 或直接将它在集群中的 PodDisruptionBudget 做一下保护。实际上 DaemonSet 的 Pod 不经过 eviction 一般不会被驱逐但节点维护 drain 时它会重新调度这没关系。真正要注意的是别把 tolerations 写成只容忍某个指定 key否则节点被打了别的污点这个节点就漏采了。我有一次就踩过节点node-role.kubernetes.io/control-plane污点比较隐蔽某个节点一直 NotReady 却没有任何 node-exporter 的 target就是因为容忍列表没写全所以这里直接写operator: Exists一劳永逸。3. 镜像文件从拉取、导出到私有仓库的一次性落地3.1 拉取镜像并确认 digest别在版本号上留隐患镜像文件这个标题要解决的其实是两类问题一是集群节点能不能按期从外部镜像仓库拉到镜像二是离线内网环境怎么把这个镜像搬进去。先看在有网机器上拉取。docker pull quay.io/prometheus/node-exporter:latest docker images --digests | grep node-exporter第一行命令正常执行完后第二行能看到这个镜像的 digest形如 sha256:xxxx。我建议生产环境固定使用 digest 而不是 taglatest 会被后续版本刷新下次 ImagePullPolicy 为 Always 时集群可能自动升级到不兼容版本导致指标名变化、告警规则失效。正确做法是把 DaemonSet 里的 image 字段写成quay.io/prometheus/node-exportersha256:你的digest这样镜像内容就永远锁死了。还有一个细节拉取时尽量选一个和你本机系统架构匹配的镜像。node-exporter 官方仓库支持 multi-arch默认拉取当前平台对应的架构。但如果你在 x86 管理机上拉镜像再传到多架构混合的离线集群就可能出现镜像架构不对、Pod 起不来的问题。常见做法是在拉取时用--platform linux/amd64或linux/arm64显式指定然后按节点架构分别放置镜像。3.2 docker save / load 搬运离线环境离线环境没有外网访问权限最直接的搬运办法是把镜像保存成 tar 包拷到内网机器上再加载。docker save -o node-exporter.tar quay.io/prometheus/node-exporter:latest ls -lh node-exporter.tar scp node-exporter.tar useroffline-host:/tmp/ # 或通过内网传输工具 docker load -i node-exporter.tar docker run --rm quay.io/prometheus/node-exporter:latest --versiondocker save 默认把整个镜像所有层打包后面看到的大小通常有二三十MB内网拷贝不慢但要注意传输工具的完整性。如果文件太大且网络不稳定可以用docker save | gzip再压一道或者分卷压缩传输但分卷会引入额外的合并步骤反而容易漏掉。我更推荐直接在目标机器上逐节点 docker load因为 kubelet 使用的容器运行时如果是 containerd也可以用 ctr 命令加载但很多环境没装 ctr 客户端还是 docker load 最稳。加载完建议立即运行一次docker run --rm ... --version确认镜像可执行避免把损坏的 tar 包一路传到了生产。3.3 推到私有仓库并在 K8S 中直接引用如果内网已有 Harbor / Nexus 之类的私有镜像仓库更好的方式是把镜像推送到那里让所有 kubelet 从私有仓库拉取省去逐节点 load。docker tag quay.io/prometheus/node-exporter:latest registry.example.com/monitoring/node-exporter:latest docker push registry.example.com/monitoring/node-exporter:latest然后修改 DaemonSet 的 image 字段为私有仓库地址。注意如果私有仓库启用了认证需要提前在命名空间里创建 imagePullSecret并在 DaemonSet 的 spec.template.spec.imagePullSecrets 里引用否则 Pod 会停留在 ImagePullBackOff。imagePullSecrets: - name: regcred3.4 imagePullPolicy 的三种选择与离线后翻车现场与镜像文件直接相关的还有一个 K8S 参数imagePullPolicy。三种取值的区别是Always 每次启动都尝试拉取IfNotPresent 本地有就使用Never 只使用本地镜像没有就报错。默认规则坑人如果 tag 是 latest即使你没写 imagePullPolicykubelet 也会按 Always 处理。离线环境踩坑最多的就在这你明明已经在节点上 docker load 了镜像DaemonSet 也在运行但节点重启后 Pod 调度到另一个没 load 过的节点或者 kubelet 重新拉取时 Pull直接 ImagePullBackOff。因为离线环境既没有私有仓库也没有外网只能用 Never 或 IfNotPresent。我建议如果整个集群都 offline在 DaemonSet 里显式写imagePullPolicy: IfNotPresent并保证每个节点都导入镜像如果能保证所有节点都预先打了镜像写 Never 更严格杜绝任何意外拉取尝试。另一个和镜像相关的细节是镜像大小占用的节点磁盘空间。node-exporter 镜像本身不大但 DaemonSet 每个节点都要存一份算上被遗弃的旧镜像偶尔会占用几百 MB。在磁盘紧张的节点上记得定期执行docker image prune清理悬空镜像这条血泪经验来自一次节点 /var/lib/docker 分区被写满的翻车经历。4. 让 Prometheus 抓取到 node-exporter三套接入方式与标签处理4.1 静态配置节点少时最直接如果你的集群只有三五台节点不需要引入任何服务发现机制直接在 Prometheus 配置文件的 scrape_configs 里写死目标即可。scrape_configs: - job_name: node-exporter static_configs: - targets: - 10.0.0.11:9100 - 10.0.0.12:9100 - 10.0.0.13:9100 labels: env: prod这种方式最稳定不受 K8S API 状态影响。缺点也明显节点扩缩容时需要同步维护 targets 列表。如果节点数量超过十台建议改用文件发现把 targets 写进一个 YAML 文件Prometheus 在挂载的 ConfigMap 更新后自动加载。不过对本文场景先静态配置跑通再说把注意力放在后面要解决的标签和端口问题上。4.2 基于 Pod 的服务发现节点规模大了再用节点多了以后最稳的动态方案是让 Prometheus 直接通过 K8S 接口发现 node-exporter 对应的 Pod然后通过 relabel 规则把 Pod 地址转换成目标地址。scrape_configs: - job_name: kubernetes-node-exporter kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_label_app] regex: node-exporter action: keep - source_labels: [__address__] action: replace regex: (.):\d replacement: $1:9100 target_label: __address__这段配置需要 Prometheus 所在 Pod 的 ServiceAccount 具备 pods list/watch 权限通常你会给 Prometheus 配一个 ClusterRole 才能让服务发现生效。第一段 relabel 只保留带有 appnode-exporter 标签的 Pod第二段是把原始 Pod 地址的端口替换成 9100。注意如果 Pod 没有用 hostNetwork那么原始address就是一个容器 IP 加一个动态端口外层再用 Service 做转发这种方式不仅多一跳而且 instance 标签会变成容器 IP对排障很不友好。所以这篇文章里的 DaemonSet 统一用 hostNetwork这是前面设计决定的。4.3 ServiceMonitorprometheus-operator 下最省心如果这套监控是 prometheus-operator 搭起来的你根本不用写 scrape_configs而是创建一个 ServiceMonitor 对象operator 会自动把对应的服务发现同步到 Prometheus。apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: node-exporter namespace: monitoring spec: jobLabel: app endpoints: - port: metrics interval: 30s selector: matchLabels: app: node-exporter namespaceSelector: matchNames: - monitoring这段配置的作用是告诉 operator注意 monitoring 命名空间里带 appnode-exporter 标签的 Service并在它的 metrics 端口抓取数据。使用它的前提是上一步那个 Service 的 port name 必须叫 metrics这里的endpoints.port: metrics指的就是这个。如果 Service 的 label 和 ServiceMonitor 的 selector 匹配不上target 为空这是新手必踩。4.4 relabel 的关键instance 标签到底保留了谁抓取回来的数据标签里最常用的就是 instance。很多同事拿到 node-exporter 数据后第一件事是想按节点名筛发现 instance 是 IP:9100而节点名存在 node_uname_info 的 nodename 标签里。我的建议是保留 instance 为节点 IP不要试图覆盖它因为 Prometheus 的 up 指标以及很多告警规则都依赖 instance 作为唯一标识。如果你希望告警通知里显示的是节点名可以在抓取时额外加一个自定义标签。- source_labels: [instance] action: replace regex: ([\d.]):\d replacement: $1 target_label: node_ip这段 relabel 把 instance 中的纯 IP 复制成了新标签 node_ip方便后续 alertmanager 路由时引用同时也不破坏原有的 instance。如果你真的想把 instance 改成节点名可以去拉取 node_uname_info 生成一个新的标签映射但那样会让每个 node-exporter 目标的 instance 随着节点变化而变化历史数据序列会断开不推荐。5. 避坑node-exporter 监控落地中的 6 个高频翻车点5.1 CrashLoopBackOff挂载 rootfs 后报 permission denied现象DaemonSet 创建后Pod 反复重启describe 看到容器日志里有mount /host/root/proc: permission denied之类的字样。原因宿主机根目录挂载进容器时涉及宿主机挂载点重新关联普通非特权容器在较严格的 K8S 环境里没有权限执行挂载传播操作。解决在容器段增加securityContext: { privileged: true }或者只保留必需的 /host/proc 和 /host/sys 挂载不挂 /host/root同时去掉 --path.rootfs 参数。如果你只需要磁盘使用率rootfs 是必须的所以一般最终都会开特权。注意这个特权只会放大到该 DaemonSet 的 Pod不会影响其他工作负载不需要过度紧张。5.2 9100 端口被宿主机进程占用现象Pod 状态显示 Running但容器日志里出现listen tcp :9100: bind: address already in use且 Prometheus 抓取失败。原因hostNetwork: true 让 exporter 直接监听了宿主机网络栈如果节点上原本有另一个 node-exporter 或 tomcat 之类进程占用了 9100就会冲突。解决在 args 里增加--web.listen-address:9101同时容器端口、Service 端口都同步改成 9101。这里更要提的是有些云厂商的节点初始化脚本会偷偷跑一个 node-exporter 占 9100排查时先ss -lntp | grep 9100看清楚占用者再决定是杀进程还是换端口。5.3 磁盘指标数量爆炸现象查询 node_filesystem_avail_bytes 时返回几千个时间序列其中大量 mountpoint 是/var/lib/docker/overlay2/xxx/merged告警阈值根本没法按节点写。原因没有配置排除规则node-exporter 把所有挂载点都暴露出来overlay、tmpfs、squashfs 这些文件系统都是容器运行时的内部视图。解决把 2.2 节里的--collector.filesystem.mount-points-exclude和--collector.filesystem.fs-types-exclude两项参数加上并让 DaemonSet 滚动重启。加完后可用count(node_filesystem_avail_bytes) by (instance)验证一个节点只剩几个合理条目比如 /、/boot、/data。这个坑可以说是我见过的 node-exporter 部署里最频繁的翻车点十个人里九个会踩。5.4 Prometheus 抓取 connection refused现象Target 页面看到 node-exporter 目标的 State 是 Down错误信息是connection refused进入 Pod 容器看 exporter 本身没报错。原因最常见的是三种DaemonSet 没有配 hostNetworkPod IP 是容器网的 10.244.x.x 而节点防火墙只放行节点 IP要么是 Service 定位错误要么节点安全组没放行 9100 入方向。解决先用kubectl get pod -o wide -n monitoring | grep node-exporter拿到 Pod IP然后在 Prometheus 所在机器上curl http://节点IP:9100/metrics和curl http://PodIP:9100/metrics两边对比立刻能判断是网络隔离问题还是调度问题。如果是 Pod IP 可通而节点 IP 不通十有八九是 hostNetwork 没开回到 DaemonSet 修改。5.5 ServiceMonitor 创建后 target 一个都没有现象prometheus-operator 环境里ServiceMonitor 没有报错但 Prometheus 的 Targets 页面里 node-exporter 服务一个 target 都没有。原因三处不匹配一是 Service 的 labels 与 ServiceMonitor 的 selector 不一致二是 Service 的 port name 与 endpoints.port 不一致三是 namespaceSelector 没把 Service 所在的命名空间列进来。解决逐步检查。kubectl get svc -n monitoring --show-labels查看 Service 标签kubectl get servicemonitor -n monitoring -o yaml查看 selector 和 endpoints.port再确认两者都在 monitoring 命名空间。大多数时候只是把app: node-exporter写成了app: node_exporter差一个下划线target 就没了这类字符串匹配问题往往要靠kubectl describe才发现。5.6 node-exporter 的 CPU 占用异常高现象节点 load 不高但 node-exporter 进程 CPU 占用持续超过 1 核甚至导致节点卡顿。原因默认启用全部 collector某些 collector 在进程数极大的节点上需要反复遍历 /proc比如 process collector、threaded 相关指标另外如果宿主机的 /sys 文件特别多或存在慢存储sysfs 数据读取也会拖慢。解决先用curl http://127.0.0.1:9100/metrics | head -50看打开了哪些 collector然后按需关闭args 里加--no-collector.process或--no-collector.softnet等。不过不建议你一上来关太多先给 node-exporter 提资源 limit比如 CPU 改成 500m真扛不住再逐个关闭。node-exporter 是单进程模型内部其实是串行采集的这也是为什么资源 limit 设置不合理时它可能被 throttle表现成抓取超时。6. 验证与进阶从「能出图」到「能告警」的收尾习惯6.1 三个 PromQL 验证节点监控是否干净配置全部完成后先在 Prometheus 的 Graph 页面跑三条查询。第一条验证节点数量和覆盖情况count(count(node_uname_info) by (instance))结果应该等于集群节点数大于或小于都说明 DaemonSet 调度或服务发现有偏差。第二条验证磁盘指标node_filesystem_avail_bytes{fstype!~tmpfs|overlay,mountpoint/} / 1024 / 1024能看到每个节点的根分区剩余 MB。第三条验证负载与 CPU 关系node_load1 / count(node_cpu_seconds_total{modesystem}) by (instance)比值超过 1 说明节点已经处于饱和边缘。这三条查询没问题就说明抓取、标签、指标完整性都正常。6.2 节点重启与宕机告警规则示例监控最终要落到告警。我常用两条基础规则一是节点失联二是检测异常重启。节点失联直接看 up 指标持续 5 分钟为 0 就说明这个 exporter 目标丢掉了。重启检测可以用time() - node_boot_time_seconds 600node_boot_time_seconds 记录的是节点开机的时间戳用当前时间减去它结果小于 600 秒就说明节点最近 10 分钟内重启过。这两条规则建议配合告警路由区分处理失联通常是节点宕机或网络隔离比较紧急重启则可能是被 OOM 或者内核问题优先级可以放低一点。6.3 用 node_uname_info 核对版本号与内核信息最后分享一个容易被忽略的小技巧node_uname_info 指标本身携带了内核版本、操作系统、节点名的标签。比如node_uname_info{nodenamenode-01}的 release 标签就是该节点的 kernel 版本。当你要批量升级内核或者排查某些节点内核不一致时直接在 Prometheus 里跑一句node_uname_info就能列出全集群的节点名和内核版本比登录每台机器敲 uname 高效得多。我平时习惯把这条查询存到 Grafana 的一个变量面板里node-exporter 告警带出来的 instance 不好认时用它关联节点名。回到实践本身node-exporter 的价值不在部署那一刻而在于节点真正出问题时你手里有没有足够长的历史数据。我曾经因为只关注容器指标、没有提前部署 node-exporter某次节点磁盘被日志写满业务 Pod 全部被驱逐事后连故障曲线都拼不完整。从那以后我把 node-exporter 的 DaemonSet 和镜像文件当作所有 Prometheus 集群的基线之一每次搭完先验证前面三条 PromQL再写告警规则。这个顺序值得你保持希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑