Windows 服务器监控实战:wmi_exporter + Prometheus + Grafana 完整落地链路 + 5 个真实故障
适用场景:需要把 Windows 服务器(IIS、SQL Server、文件服务器、域控)接入 Prometheus 监控体系的团队。一句话结论:Windows 接入的难点不在 Prometheus,而在 wmi_exporter 的 collector 开关、服务权限和 9182 端口连通性。
干运维这些年,我的 Prometheus 里清一色是 Linux 节点,Windows 服务器一直是"监控黑户"。不是不想接,是每次查资料都被一句"装个 wmi_exporter 就行"打发,等真装完,发现端口不通、指标为空、告警乱报——中文圈把 Windows 监控讲得太薄了,真正能落地的链路没人给全。
这周我把三台 Windows 服务器(一台 IIS 应用、一台 SQL Server、一台文件服务器)接进了监控。踩了五个坑,全部记下来。这篇不是"安装教程",是"从零到告警生效"的完整链路,外加 5 个我亲手处理的真实故障。
一、为什么 Windows 是监控体系的"失管区"
先说痛点。Linux 有 node_exporter,文档多、教程全,接进 Prometheus 十分钟搞定。Windows 呢?wmi_exporter 能装,但它依赖 WMI(Windows Management Instrumentation),WMI 查询是要吃系统资源的——不是你想开哪个 collector 就能开哪个。
更麻烦的是三点:
- Windows 防火墙默认拦入站,9182 端口装上也不通
- 服务账户权限:wmi_exporter 默认用 LocalSystem,访问某些 WMI 类(比如 IIS、MSFT_* 类)会因权限不足返回空
- collector 开关全在注册表,不是改个配置文件就完事,很多人装完默认状态跑,结果 process 类指标全没有
我见过最典型的翻车:同事装完 wmi_exporter,curl localhost:9182/metrics 有输出,高兴地加进 Prometheus,结果 Grafana 面板全是 No data——因为看板模板要的 windows_* 指标,默认 collector 压根没采集。安装完成只是 5%,剩下 95% 在下面。
二、wmi_exporter 与 collector 清单:哪些默认开、哪些有坑
wmi_exporter 的 collector 是按需加载的,默认只开一小部分。先记清楚这张表,后面所有配置都围绕它:
| Collector | 默认 | 采集内容 | 性能/权限坑 |
|---|---|---|---|
| cpu | 开 | CPU 各模式时间 | 无 |
| cs | 开 | 计算机系统(主机名、核数) | 无 |
| logical_disk | 开 | 磁盘空间、队列、读写时间 | 含空闲盘/虚拟盘,易误报 |
| net | 开 | 网络收发字节/包 | 无 |
| os | 开 | 内存可用、系统信息 | 无 |
| service | 开 | 服务运行状态 | 默认全量服务,量大 |
| system | 开 | 进程数、线程数、运行时长 | 无 |
| process | 关 | 按进程:句柄、线程、CPU | 高基数元凶,慎开 |
| iis | 关 | IIS 站点请求、流量 | 需管理员权限 + IIS 已装 |
| msft_* | 关 | Hyper-V/存储等 MSFT 类 | 需显式开启 + 权限 |
关键认知:process 和 iis 默认是关的。process 关是因为它按进程粒度输出几十个指标,一台 500 进程的服务器能产生上万时间序列,Prometheus 内存直接飙升(见故障五);iis 关是因为它要求管理员权限且有额外开销。默认开的那 7 个够看主机健康,别一上来全开。
三、安装方式一:MSI 安装 + 服务配置
MSI 是官方推荐的装法,服务由安装器自动注册。但很多人装完发现指标不全,因为collector 要在安装时通过属性传进去:
# 下载后执行(0.16.0 为例,按实际版本改)
wmi_exporter-0.16.0-amd64.msi ^
ENABLED_COLLECTORS="cpu,cs,logical_disk,net,os,service,system" ^
IIS_ENABLED="true"
注意 ENABLED_COLLECTORS 是逗号分隔、不含空格。漏掉 iis 或 process 就是默认状态,后面指标缺一截。
装完验证服务与端口:
sc.exe query wmi_exporter # 服务状态应为 RUNNING
Get-NetTCPConnection -LocalPort 9182 -State Listen # 应有一条 LISTEN
Invoke-WebRequest http://localhost:9182/metrics | Select-Object -First 5
如果服务没起或端口没监听,查两个地方:一是服务账户(默认 LocalSystem,别乱改),二是注册表 collector 配置:
# 查看已启用的 collector(注册表是 wmi_exporter 的"配置文件")
reg query "HKLM\SOFTWARE\wmi_exporter" /v ENABLED_COLLECTORS
改 collector 后要重启服务才生效:
reg add "HKLM\SOFTWARE\wmi_exporter" /v ENABLED_COLLECTORS /t REG_SZ ^
/d "cpu,cs,logical_disk,net,os,service,system,process" /f
Restart-Service wmi_exporter
四、安装方式二:zip 解压 + NSSM 自建服务
不想装 MSI、或要绿色部署时,用 zip 版 + NSSM。zip 版不会自动建服务,这是最大的坑——很多人解压直接跑 wmi_exporter.exe,一关窗口进程就没了。
# 1. 解压到 C:\wmi_exporter,用 NSSM 注册为服务
C:\nssm\nssm.exe install wmi_exporter "C:\wmi_exporter\wmi_exporter.exe"
# 2. collector 通过启动参数传入(zip 版没有安装属性,只能走参数)
C:\nssm\nssm.exe set wmi_exporter AppParameters ^
"--collectors.enabled=cpu,cs,logical_disk,net,os,service,system"
# 3. 启动并设为开机自启
C:\nssm\nssm.exe start wmi_exporter
C:\nssm\nssm.exe set wmi_exporter Start SERVICE_AUTO_START
如果不想用 NSSM,sc.exe create 也能建,但 NSSM 的好处是崩溃自动重启,监控进程不能裸奔。用 sc.exe 建的话记得加 binPath 并配好恢复选项。
一个对比结论:批量部署建议 MSI(能用 GPO/脚本推、自带服务),单机或测试环境用 zip + NSSM(可控、可带参数)。两者采集能力完全一样,别纠结。
五、防火墙放行 9182:从本机通到远端通
本机 curl localhost:9182 通,不代表远端能通。Windows 防火墙默认拦入站,这是"装完不通"的第一大元凶:
# 放行 9182 入站(按 TCP 端口)
netsh advfirewall firewall add rule name="wmi_exporter" ^
dir=in action=allow protocol=TCP localport=9182
# 放行指定程序(更精确,但注意 exe 路径)
netsh advfirewall firewall add rule name="wmi_exporter_exe" ^
dir=in action=allow program="C:\wmi_exporter\wmi_exporter.exe"
两个坑:一是如果机器在域环境,除了本机防火墙,可能还有域防火墙策略在中间拦;二是防火墙有"域/专用/公用"三个配置文件,规则要加到对应配置文件(profile=domain,private)。验证从 Prometheus 服务器侧:
TcpTestSucceeded : True 才是真的通。不通就 netsh advfirewall firewall show rule name="wmi_exporter" 查规则是否在正确 profile。
六、Prometheus 抓取配置
连通后写进 prometheus.yml。Windows 和 Linux 的抓取大同小异,但建议给 Windows 单独一个 job 并加标签,方便区分:
scrape_configs:
- job_name: windows-servers
scrape_interval: 15s
static_configs:
- targets:
- '192.168.1.10:9182' # IIS 应用
- '192.168.1.11:9182' # SQL Server
- '192.168.1.12:9182' # 文件服务器
labels:
os: 'windows'
env: 'prod'
relabel_configs:
# 把 IP 单独抽成 instance,方便 Grafana 按主机过滤
- source_labels: [__address__]
regex: '([^:]+):.*'
target_label: instance
replacement: '${1}'
改完 prometheus.yml 用 promtool check config 校验,再热加载:
验证抓取是否成功:
# 确认 target 是 UP
curl 'http://localhost:9090/api/v1/targets' | grep -o '"health":"[a-z]*"' | sort | uniq -c
# 确认 windows_ 指标进来了
curl 'http://localhost:9090/api/v1/query?query=up{job="windows-servers"}'
七、Grafana 看板落地
指标进来了,接下来是看板。别手搓,直接导入社区模板,这是效率最高的路:
- Grafana → Dashboards → Import → 输入模板 ID(搜 "Windows")
- 选数据源(你的 Prometheus)
- 右上角选 instance,切到对应 Windows 主机
最大的坑:社区多数 Windows 模板(如 10467)默认匹配 node_* 前缀,而 wmi_exporter 输出的是 windows_*。导入后如果面板 No data,去面板的 Query 里把 node_ 批量替换成 windows_。我自己做的四个核心面板:
| 面板 | PromQL(替换前缀后) |
|---|---|
| CPU 使用率 | 100 - avg(rate(windows_cpu_time_total{mode="idle"}[5m])) * 100 |
| 内存可用 % | windows_os_physical_memory_free_bytes / windows_os_visible_memory_bytes * 100 |
| 磁盘使用 % | (windows_logical_disk_size_bytes - windows_logical_disk_free_bytes) / windows_logical_disk_size_bytes * 100 |
| 网络流量 | sum(rate(windows_net_bytes_received_total[5m]) + rate(windows_net_bytes_sent_total[5m])) |
八、Windows 关键指标表 + PromQL
监控 Windows,先分清"哪些指标真正反映故障"。附一张我用得最顺手的表,每行给 PromQL:
| 监控项 | 指标 | PromQL | 正常参考 |
|---|---|---|---|
| CPU | cpu_time_total | 100 - avg(rate(windows_cpu_time_total{mode="idle"}[5m])) * 100 |
< 80% |
| 内存可用字节 | os_physical_memory_free_bytes | windows_os_physical_memory_free_bytes |
> 总内存 20% |
| 磁盘队列 | logical_disk_queue_length | windows_logical_disk_queue_length |
持续 > 2 警惕 |
| 磁盘忙时比 | logical_disk_*_seconds_total | avg(rate(windows_logical_disk_read_seconds_total[5m]) + rate(windows_logical_disk_write_seconds_total[5m])) |
< 80% |
| 句柄数 | process_handles | sum(windows_process_handles) |
看趋势 |
| 进程数 | system_processes | windows_system_processes |
看趋势 |
| 网络流量 | net_bytes_*_total | rate(windows_net_bytes_received_total[5m]) + rate(windows_net_bytes_sent_total[5m]) |
按带宽 |
一个关键提醒:Windows 没有 Linux 那种"load average",取而代之的是 windows_system_processor_queue_length(处理器队列长度)。队列长度持续超过核数,才是 Windows 的"CPU 排队"信号,别拿 Linux 的 load 套。
九、告警规则:Windows 专用 PromQL
告警规则和 Linux 类似,但阈值和采集口径要按 Windows 调。尤其磁盘,Windows 的 logical_disk 会包含虚拟盘、只读盘,直接 queue_length > 2 会误报(见故障四):
groups:
- name: windows-server
rules:
- alert: WindowsCpuHigh
expr: 100 - avg(rate(windows_cpu_time_total{mode="idle"}[5m])) * 100 > 90
for: 5m
labels: { severity: critical }
annotations:
summary: "Windows CPU 过高 {{ $labels.instance }}"
- alert: WindowsMemoryLow
expr: windows_os_physical_memory_free_bytes / windows_os_visible_memory_bytes * 100 < 10
for: 10m
labels: { severity: warning }
annotations:
summary: "Windows 内存可用低于 10% {{ $labels.instance }}"
- alert: WindowsDiskQueueHigh
expr: |
windows_logical_disk_queue_length{volume=~"C:.*"}
and on(instance, volume)
windows_logical_disk_idle_seconds_total{volume=~"C:.*"} == 0
for: 5m
labels: { severity: warning }
annotations:
summary: "C 盘磁盘队列过长 {{ $labels.instance }}"
- alert: WindowsServiceDown
expr: windows_service_state{state="stopped"} == 1
for: 3m
labels: { severity: critical }
annotations:
summary: "Windows 服务已停止 {{ $labels.service }}"
注意磁盘那条:volume=~"C:.*" 先过滤只监控 C 盘,idle_seconds_total == 0 表示盘忙。两个条件用 and 连,避免空闲盘或只读盘的假队列触发告警——这是我被误报折磨后的血泪总结。
十、5 个真实故障案例
故障一:MSI 装完,9182 端口不通
现象:MSI 安装显示成功,sc.exe query wmi_exporter 是 RUNNING,但 Prometheus 抓不到,Test-NetConnection -Port 9182 返回 False。
排查链:查防火墙规则 → 无入站规则;查服务 → 正常;查进程监听 → Get-NetTCPConnection 无 9182 LISTEN。
根因:Windows 防火墙默认没有放行 9182。MSI 安装器不会自动加防火墙规则,入站被拦。
修复:加 netsh advfirewall 规则(见第五节),Test-NetConnection 变 True。
经验:Windows 装完任何监听端口服务,第一件事永远先放行防火墙,别急着改 Prometheus。
故障二:防火墙放行了,远端还是不通
现象:本机 curl localhost:9182 通,加完防火墙规则后,从 Prometheus 机器 Test-NetConnection 仍 False。
排查链:查本机防火墙 profile → 规则加到了 domain,但当前网络是 private;再查域策略 → 上级 GPO 有一条阻止 9182。
根因:域环境的防火墙是"双层"。本机规则配对了,但域组策略在域控层又拦了一道;且防火墙规则默认只对当前 profile 生效,网络配置文件切换后规则不匹配。
修复:把规则同时加到 profile=domain,private,并协调域管理员在 GPO 里放行,或改走 HTTPS/代理。
经验:域内 Windows,先确认"网络配置文件是哪个"(Get-NetConnectionProfile 看 NetworkCategory),再决定规则加哪个 profile。
故障三:Windows 时间跳变,rate() 飙高误报
现象:凌晨告警系统突然收到 CPU 100% 告警,但 windows_cpu_time_total 看值正常,告警却持续。
排查链:查 rate() 计算 → 时间差为负;查系统时钟 → 与 NTP 相差 3 分钟;查告警触发时间 → 恰在时间校正后。
根因:Windows 时钟未同步,NTP 校正时时间跳变。Prometheus 的 rate() 用时间戳差值算速率,时钟跳变会让采样间隔异常,rate() 瞬间虚高甚至为负,触发误报。
修复:配置可靠 NTP 源并启用同步(w32tm /config /manualpeerlist:"ntp.aliyun.com" /syncfromflags:manual /reliable:yes,w32tm /resync),告警规则加 for: 5m 缓解瞬时跳变。
经验:Windows 服务器时间不同步是"隐性监控杀手"。加监控前先 w32tm /query /status 确认时钟正常,否则 rate 类指标全不可信。
故障四:磁盘队列告警天天误报
现象:WindowsDiskQueueHigh 告警每天触发,但 C 盘 queue_length 看其实不高,业务也无异常。
排查链:看告警时的 queue_length 值 → 来自 D 盘(备份盘);看该盘 idle_seconds_total → 长期为 0;看挂载类型 → 是网络映射盘。
根因:logical_disk collector 默认扫全部盘,包括网络映射、只读、虚拟盘。这些盘的队列指标长期异常,而原始告警没过滤盘符,把非系统盘也纳进来了。
修复:告警只对 C:.*(或业务盘)生效(见第九节),并在 Grafana 面板上 regex 过滤掉映射盘。
经验:Windows 磁盘监控必须先"选盘"再"设阈值",别默认全盘告警。Linux 可以全盘,Windows 不行——盘太多了。
故障五:process collector 开启后 Prometheus 内存暴涨
现象:为了排查某进程内存,我给文件服务器开启了 process collector,第二天 Prometheus 容器内存从 2G 涨到 9G,告警查询明显变慢。
排查链:查 Prometheus 内存 → rate(process_resident_memory_bytes) 持续涨;查该服务器 windows_process_handles 序列数 → 几万条;看进程数 → 500+ 进程。
根因:process collector 是"高基数"重灾区。一台 500 进程的服务器,每个进程输出句柄、线程、工作集等几十个指标,瞬时产生数万时间序列。Prometheus 内存随序列数线性增长,直接被打爆。
修复:临时排查完立刻关闭 process collector,恢复默认 7 个;若必须长期开,用 --collectors.process.include 只匹配关键进程(如 sqlservr,msftesql),把序列数压下来。
经验:任何"按进程/按标签"的 collector 都要评估基数。Windows 进程多,process 类指标是"能不开就不开、要开必须过滤"的典型。
FAQ
Q1:wmi_exporter 的 MSI 装完 9182 端口不通,最常见原因是什么?
A1:八成是防火墙。9182 是 TCP 入站,Windows 防火墙默认拦截,MSI 不会自动放行,需手动 netsh advfirewall 加规则。其次是服务没起——查 sc.exe query wmi_exporter,若 RUNNING 但无 9182 监听,再查注册表 ENABLED_COLLECTORS 是否配置完整。
Q2:process collector 要不要默认开启?
A2:不建议。它按进程粒度输出几十个指标,一台几百进程的服务器会产生数万时间序列,Prometheus 内存暴涨、查询变慢。只在定位单进程时临时开,长期开必须用 --collectors.process.include 过滤关键进程。
Q3:磁盘队列告警老误报,为什么?
A3:logical_disk 默认扫全部盘,含网络映射、只读、虚拟盘。用 volume=~"C:.*" 过滤只监控业务盘,并配合 idle_seconds_total == 0(盘忙)做 and 条件,再加 for: 5m 持续时长,误报基本消除。
Q4:Windows 时间跳变会影响监控吗?
A4:会,且隐蔽。wmi_exporter 采集依赖系统时钟,rate() 用时间差算速率,NTP 未同步导致时钟跳变会让 rate 瞬间飙高或为负,触发误报。先用 w32tm /query /status 确认同步,加 for 持续时长缓解瞬时跳变。
Q5:Grafana 有没有现成的 Windows 看板?
A5:有。官方模板搜 "Windows",常用 10467。但多数模板默认匹配 node_* 前缀,wmi_exporter 输出 windows_*,导入后要在 Query 里批量替换前缀,否则面板 No data。