资讯动态

Icinga 2 监控 IIS 站点假死:check_iis 原理与部署排障

发布时间:2026/10/26 20:42:58 来源:尧图企业网站定制
简介这是一款用 C# 编写的 IIS 监控插件面向使用 Icinga2、Icinga、Centreon、Shinken、Naemon 等类 Nagios 监控系统的 Windows 运维人员用于在本机检查 IIS 站点与应用程序池状态。压缩包共 18 个文件约 36KB包含 8 个 cs 源码文件、config 与 conf 配置示例、xml、sln 解决方案、csproj 工程文件以及 license、md 说明等便于直接阅读实现、编译调整并接入现有监控流程。资源要求运行环境为 Windows Server 2008 及以上、IIS 7.0 及以上并安装 .NET Framework 3.5 SP1 或 4.0插件需由 Icinga2 代理、NSCP 等以管理员权限本地执行才能读取 IIS 配置存储。已有 202 人学习适合需要补足 IIS 站点与应用程序池本地巡检能力的运维人员参考也可据此理解插件参数开关、名称匹配区分大小写等使用约束。1. IIS 站点假死时Icinga 为什么还在显示绿色上周有个朋友半夜打电话说生产 IIS 上的订单接口 503 了半小时Icinga 大屏一片绿。问题不在 Icinga而在他只监控了 W3SVC 服务服务在跑站点和应用程序池却早已趴下。check_iis这个 C# 插件就是补这个缺口的——它直接读本地 IIS 的站点集合与 AppPool 集合把每个站点、每个池的Started/Stopped状态翻译成 Icinga 认识的退出码和性能数据。适合已经在 Windows Server 上跑 Icinga 2 Agent、又不想为 IIS 单独装一套采集器的人。它不替代 HTTP 探活但能让你在“服务没挂、站点挂了”的第一分钟收到告警而不是等用户打电话。2. 拆 check_iis 的插件协议返回码、性能数据和参数边界2.1 先对齐 Nagios/Icinga 插件约定再谈 C# 实现Icinga 2 调用的“插件”不是常驻进程而是一个可执行文件Icinga 按配置拼出命令行启动进程读它的标准输出第一行再根据进程退出码决定服务状态。这套约定来自 Nagios 插件规范Icinga 2 完整兼容。对check_iis来说退出码只有四种有意义退出码Icinga 状态典型含义0OK被检查的站点/池全部处于 Started1WARNING停止数量达到 warning 阈值或部分对象无法读取2CRITICAL停止数量达到 critical 阈值或 ServerManager 初始化失败3UNKNOWN参数错误、权限不足、IIS 管理组件缺失标准输出的第一行是“状态摘要”竖线|之后是 perfdata。Icinga 会把|前面的文本直接展示在 Web 界面把后面的键值对交给 Graphite、InfluxDB 或 PNP 做趋势图。常见格式像这样OK - 5 sites checked, 0 stopped; 8 app pools checked, 0 stopped | sites_total5 stopped_sites0 pools_total8 stopped_pools0这段输出里sites_total和stopped_sites是自定义性能指标名字可以自己定但别带空格和引号否则 Icinga 解析 perfdata 时会翻车。我一般把“总数”和“停止数”都打出来因为只看停止数无法判断是“1/2 停了”还是“1/200 停了”后者往往更值得警惕。2.2 用 ServerManager 读状态为什么不用 appcmd 或 ServiceControllerC# 读本地 IIS 有三条路System.ServiceProcess.ServiceController看 W3SVC、appcmd.exe解析文本、Microsoft.Web.Administration.ServerManager直接操作配置对象。前两条都有硬伤。ServiceController只能看到整个 W3SVC 服务无法区分某个站点或某个应用程序池appcmd要起子进程、解析本地化文本站点名带空格或中文时容易切错列。ServerManager是 IIS 自带的托管接口Sites和ApplicationPools都是强类型集合取State属性即可不依赖控制台输出格式。代价是它依赖Microsoft.Web.Administration.dll这个程序集不在标准 .NET Framework 全局程序集缓存里而是随 IIS 管理组件安装在%windir%\System32\inetsrv\下。编译时要显式引用运行时目标机器也必须装 IIS 管理工具。下面是我常用的核心读取逻辑省略了参数解析和异常包装重点看状态判断using System; using System.Linq; using Microsoft.Web.Administration; class CheckIis { static int Main(string[] args) { // 参数示例-s 站点名 -a 应用池名 -w 1 -c 2 // 实际项目中解析逻辑应校验空值这里只保留骨架 string siteName GetArg(args, -s); string poolName GetArg(args, -a); int warn int.Parse(GetArg(args, -w) ?? 1); int crit int.Parse(GetArg(args, -c) ?? 2); using (var server new ServerManager()) { var sites server.Sites .Where(s string.IsNullOrEmpty(siteName) || s.Name siteName) .ToList(); var pools server.ApplicationPools .Where(p string.IsNullOrEmpty(poolName) || p.Name poolName) .ToList(); int stoppedSites sites.Count(s s.State ! ObjectState.Started); int stoppedPools pools.Count(p p.State ! ObjectState.Started); // 先输出单行摘要竖线后接 perfdataIcinga 只认这一行 string summary $sites{sites.Count}, stopped{stoppedSites}; $pools{pools.Count}, stopped{stoppedPools}; string perf $sites_total{sites.Count} stopped_sites{stoppedSites} $pools_total{pools.Count} stopped_pools{stoppedPools}; int exitCode 0; string state OK; if (stoppedSites stoppedPools crit) { state CRITICAL; exitCode 2; } else if (stoppedSites stoppedPools warn) { state WARNING; exitCode 1; } Console.WriteLine(${state} - {summary} | {perf}); return exitCode; } } static string GetArg(string[] args, string name) { int i Array.IndexOf(args, name); return (i 0 i 1 args.Length) ? args[i 1] : null; } }这段代码的关键不在语法而在三个边界。第一ServerManager构造函数本身会加载applicationHost.config如果 IIS 管理组件没装或配置文件损坏会直接抛异常必须在外层包try/catch并返回退出码 3否则 Icinga 只会看到进程崩溃。第二ObjectState.Started对应用程序池只表示“池进程已启动”不表示站点能响应 HTTP所以check_iis适合做“状态层”监控HTTP 层要另配check_http。第三-s和-a为空时应当是“全量扫描”而不是报参数错误很多人第一次接入时只想监控全部站点如果强制传名字反而不好用。2.3 参数模型站点名、池名和阈值怎么传插件参数没有统一标准但 Nagios 系插件通常用短横线加单字母。check_iis这类本地检查常见做法是留四个参数-s指定站点名-a指定应用程序池名-w和-c分别指定 warning/critical 阈值。阈值语义我倾向于按“停止的对象数量”算而不是按百分比。原因很实际一个站点停了就是停了百分比在全量扫描时会被大量正常站点稀释导致告警延迟。参数作用不传时的行为注意点-s只检查指定站点扫描全部站点站点名含空格时在 Icinga 配置里要整体作为一个参数值-a只检查指定应用程序池扫描全部池池名和站点名不是一回事别混用-wWARNING 阈值默认 1按停止数量计达到即 WARNING-cCRITICAL 阈值默认 2必须大于等于-w否则状态会跳变-t超时秒数如果实现由 Icinga 侧控制建议 Icingatimeout大于插件内部超时如果拿到手的版本参数名不同先跑一次check_iis.exe --help或直接不带参数运行看它打印的用法不要照搬别人的配置。Icinga 的CheckCommand只是把变量拼成命令行参数名错一个字母服务就会 UNKNOWN。3. 编译 check_iis从 C# 源码到可执行插件的完整链路3.1 环境准备IIS 管理组件比 .NET 更容易漏编译机不需要装完整 IIS但必须装“IIS 管理控制台”和“IIS 管理脚本和工具”因为Microsoft.Web.Administration.dll随这两个功能落地。Windows Server 上用 PowerShell 一条命令能确认# 检查 IIS 管理组件是否已安装返回 True 才说明程序集存在 Test-Path $env:windir\System32\inetsrv\Microsoft.Web.Administration.dll返回False时去“服务器管理器 → 添加角色和功能 → Web 服务器(IIS) → 管理工具”勾选 IIS 管理控制台。别只装 IIS 运行时运行时不带这个 DLL。编译器用 .NET Framework 自带的csc.exe就够路径通常在C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe。用 64 位编译器因为目标 IIS 是 64 位进程Platform设成x64可以避免“在 64 位系统上加载 32 位程序集”的怪问题。3.2 一条 csc 命令完成编译把源码保存为CheckIis.cs在“开发者命令提示符”或普通 cmd 里执行下面这条命令。/reference指向 IIS 管理程序集/platform:x64固定目标架构/target:exe生成控制台程序csc /target:exe /platform:x64 /out:check_iis.exe ^ /reference:C:\Windows\System32\inetsrv\Microsoft.Web.Administration.dll ^ CheckIis.cs编译成功后当前目录会出现check_iis.exe。先别急着拷到 Icinga 目录在编译机上直接跑一次确认它能读到本机 IIScheck_iis.exe echo ExitCode%ERRORLEVEL%正常输出应该是一行OK - sites...退出码 0。如果输出CRITICAL且退出码 2先看是不是本机真有 stopped 的池如果直接抛异常多半是没装 IIS 管理组件或没用管理员身份运行。ServerManager读配置需要管理员权限普通 cmd 会抛UnauthorizedAccessException这一点在编译机上就会暴露别等到 Icinga 里才发现。3.3 部署路径与文件权限Icinga 2 在 Windows 上的插件目录由常量PluginDir决定默认是C:\Program Files\Icinga2\sbin。把 exe 拷进去不要放在用户目录或桌面因为 Icinga 服务账户可能没有那些路径的读取权限copy check_iis.exe C:\Program Files\Icinga2\sbin\check_iis.exe icacls C:\Program Files\Icinga2\sbin\check_iis.exe /grant NT AUTHORITY\SYSTEM:(RX)Icinga 2 服务默认以LocalSystem运行LocalSystem在本机拥有管理员权限能读applicationHost.config。如果现场把服务账户改成了专用低权账户check_iis会返回 UNKNOWN 或直接异常。常见做法是给该账户加到本地Administrators组或者至少授予%windir%\System32\inetsrv\config目录的读取权限但后者在 IIS 共享配置场景下不一定够所以生产上我更倾向让 Icinga 服务保持LocalSystem用 Icinga 自身的权限模型限制谁能改配置而不是压缩插件进程的权限。部署完再做一次“以服务账户身份”的验证用psexec -s以LocalSystem启动 cmd再跑check_iis.exe。这一步能提前发现“管理员能跑、SYSTEM 跑不了”的权限坑。没有psexec的话直接看 Icinga 日志里check_iis的 stderr也能定位到权限异常。4. 把 check_iis 接进 Icinga 2CheckCommand、Service 与阈值配置4.1 定义 CheckCommand把参数映射到 Icinga 变量Icinga 2 的CheckCommand负责“命令怎么拼”。下面这段配置假设插件放在默认PluginDir参数名与第 2 章对齐。import plugin-check-command会带上超时和退出码处理的基础逻辑不要省object CheckCommand check_iis { import plugin-check-command command [ PluginDir /check_iis.exe ] arguments { -s { value $iis_site$ description IIS site name; empty means all sites } -a { value $iis_apppool$ description IIS application pool name; empty means all pools } -w { value $iis_warning$ description Warning threshold for stopped objects } -c { value $iis_critical$ description Critical threshold for stopped objects } } timeout 30s }arguments里的value引用的是服务或主机上的自定义变量变量名以$包裹。Icinga 在运行时把$iis_site$替换成实际值如果变量未定义这个参数会被整段省略正好对应“全量扫描”的语义。timeout 30s是 Icinga 侧的进程超时不是插件内部超时。ServerManager在站点极多时加载applicationHost.config可能超过 10 秒默认 10 秒容易误报30 秒是折中值。4.2 用 apply 规则批量生成 Service不要为每台机器手写 Service 对象。主机上定义vars.iis_site和vars.iis_apppool再用apply Service批量生成。下面这条规则对每个带iis_site变量的主机生成一个服务服务名带站点名方便在界面上直接定位apply Service iis-site- { import generic-service check_command check_iis vars.iis_site host.vars.iis_site vars.iis_apppool host.vars.iis_apppool vars.iis_warning 1 vars.iis_critical 2 check_interval 1m retry_interval 30s max_check_attempts 3 assign where host.vars.iis_site }主机对象里这样写object Host web-node-01 { import generic-host address 10.0.0.11 vars.os Windows vars.iis_site OrderApi vars.iis_apppool OrderApiPool }check_interval 1m适合站点级监控retry_interval 30s让偶发抖动快速恢复max_check_attempts 3避免一次读取失败就告警。如果站点很多不建议每个站点一个 Service 对象把 Icinga 撑爆更常见的做法是“全量扫描 一个服务”用 perfdata 记录 stopped 数量再对关键站点单独加 HTTP 检查。4.3 阈值与依赖别让 AppPool 和站点各报各的check_iis的-w/-c按“停止数量”计数全量扫描时阈值要放大。比如一台机器有 40 个池-w 1 -c 2意味着停一个就 WARNING、停两个就 CRITICAL这在开发机上合理在生产上会天天告警。我的习惯是关键站点单独建服务阈值-w 1 -c 1全量扫描服务阈值设-w 3 -c 5只用来兜底发现“批量挂掉”的场景。另外站点和应用程序池之间存在依赖关系池停了站点通常也不可用但check_iis会分别计数导致同一个故障产生两条告警。可以用 Icinga 的Service依赖把站点服务挂在池服务下面池 CRITICAL 时抑制站点告警apply Dependency site-depends-on-pool to Service { parent_service_name iis-pool- host.name child_service_name iis-site- host.name disable_checks true assign where host.vars.iis_site host.vars.iis_apppool }这段依赖的前提是池服务名和站点服务名按固定规则生成。如果命名不统一宁可先不加依赖否则会出现“父服务名不存在”的配置错误Icinga 启动时直接报错。5. 避坑排查权限、编码、超时和“假 Started”的四类翻车5.1 手工运行 OKIcinga 里却 UNKNOWN现象在服务器上以管理员身份跑check_iis.exe返回 OKIcinga Web 上该服务是 UNKNOWN插件输出为空或Plugin exited with code 3。原因Icinga 2 服务账户不是当前登录用户。默认LocalSystem有管理员权限但如果现场改成了NT AUTHORITY\NetworkService或专用域账户ServerManager读applicationHost.config会被拒绝异常信息写到 stderrIcinga 只拿到非零退出码。解决先查 Icinga 服务账户sc qc icinga2看SERVICE_START_NAME。然后用同一账户跑一次插件确认权限。要么把服务账户改回LocalSystem要么给该账户授%windir%\System32\inetsrv\config的读取权限并加入IIS_IUSRS。改完重启 Icinga 服务别只 reloadWindows 服务账户变更需要重启生效。5.2 站点名带空格或中文命令行被截断现象-s My Site在手工执行时正常配到 Icinga 后服务报UNKNOWN - site not found或者插件收到的是My和Site两个参数。原因Icinga 的arguments默认会把值做一层引用但如果变量里本身带引号或者用了command [ ... ]数组拼接时手工加了引号最终命令行会出现双重引号或转义错误。解决Icinga 2 的数组式command会自动处理含空格参数不要在value里再写引号。vars.iis_site My Site这样写即可。排查时在 Icinga 日志里打开debug级别看实际拼出的命令行或者用icinga2 console调get_check_command(check_iis).execute()观察参数。中文站点名还要注意插件输出编码控制台代码页不是 UTF-8 时Icinga 读到的摘要可能是乱码但退出码仍然正确不影响告警。5.3 插件超时但手工跑只要两秒现象Icinga 报Timeout exceeded手工执行check_iis.exe明明很快返回。原因Icinga 的timeout是“进程启动到退出”的总时间包含 Icinga 创建进程、加载 .NET Framework、JIT 编译、ServerManager初始化。冷启动时 .NET 程序首次运行可能多花几秒加上站点多时applicationHost.config解析慢10 秒默认值不够。解决把CheckCommand的timeout调到 30s同时确认插件内部没有死循环。如果仍然超时用psexec -s以服务账户跑一次并计时区分是“权限导致卡住”还是“真的慢”。权限不足时ServerManager有时不立刻抛异常而是卡在 COM 调用上表现就是超时而非报错。5.4 AppPool 显示 Started站点仍然 503现象check_iis返回 OK所有池都是Started但用户访问站点 503。原因ObjectState.Started只说明池进程已创建不说明池里的工作进程能处理请求。池可能因为 Rapid-Fail Protection 被标记为“启动但不可用”或者站点绑定的应用程序池名称写错、池虽然启动但 .NET 运行时加载失败。check_iis是状态检查不是 HTTP 探活这个边界必须清楚。解决关键站点额外配check_http或check_http的 IIS 变体检查具体 URL 的状态码和响应时间。check_iis负责“池/站点对象是否存在且处于启动态”HTTP 检查负责“请求能不能通”两者组合才能覆盖“假 Started”。如果只想用一个插件可以在check_iis里加-u参数做 HTTP 探测但那就超出“轻量本地检查”的定位了编译依赖也会变多。6. 进阶验证用退出码和 perfdata 把监控结果坐实插件接进 Icinga 之后最容易犯的错是“看界面绿色就以为对了”。我习惯做三层验证每层都落在具体命令上。第一层手工执行看退出码。Windows cmd 用%ERRORLEVEL%PowerShell 用$LASTEXITCODE不要只看输出文本# 直接调用插件显式打印退出码确认 0/1/2/3 与输出一致 C:\Program Files\Icinga2\sbin\check_iis.exe -w 1 -c 2 Write-Host ExitCode$LASTEXITCODE第二层在 Icinga 侧确认实际执行的命令和变量替换结果。用icinga2 console连本机执行get_service(web-node-01, iis-site-OrderApi).check_command看命令对象或者开debug日志抓一次Executing check command的完整命令行。很多“参数没传进去”的问题在这一步就能看到是变量名拼错还是apply规则没匹配。第三层看 perfdata 是否真的进了后端。Icinga Web 的服务详情里如果 perfdata 显示为灰色说明|后面的格式不合法。check_iis输出sites_total5 stopped_sites0这种“键数值”对是合法的如果值带单位或引号Icinga 会丢弃整段。下面这张表是我排 perfdata 问题时对照用的输出片段是否合法原因stopped_sites0合法纯键值对数值无单位stopped_sites0;1;2合法带 warn/crit 阈值Icinga 能识别stopped_sites0 sites不合法数值后跟了裸文本解析失败stopped sites0不合法键名带空格和引号Icinga 不认阈值调优也别拍脑袋。先让全量扫描服务跑一周看stopped_sites的基线如果每天凌晨备份时池会重启基线就是 1那-w 1必然误报应该把-w提到 2 或改用max_check_attempts抑制。关键站点则相反-w 1 -c 1都不过分因为单站点停止就是故障。从那以后我每次接新插件都强制走一遍“手工退出码 → Icinga 实际命令行 → perfdata 入库”这三步不再只看界面颜色。这套习惯帮我省过好几次半夜被假告警叫醒的麻烦也希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑