☰
check_iis 插件部署:Icinga 2 监控 IIS 站点与应用程序池
2026/10/26 4:57:21 网站建设 项目流程

简介:这是一款面向 Windows 服务器运维人员和 Icinga/Nagios 类监控体系使用者的 IIS 检查插件,采用 C# 编写,用于解决 IIS 站点与 AppPool 缺乏统一监控的问题,可在本地读取配置并监视运行状态。它可用于 Icinga2、Icinga、Centreon、Shinken、Naemon 等平台,通常由 Icinga2 代理或 NSCP 调用;运行环境要求 .NET Framework 3.5 SP1 及以上、IIS 7.0 及以上,执行账号需要管理员权限,站点与应用程序池匹配区分大小写。插件仅用于本机检查,调用时应使用命名开关,避免依赖可能变化的单字符缩写。压缩包共 18 个文件、约 36KB,包含 8 个 cs 源码、conf/config 配置、sln/csproj 工程、XML、LICENSE 和 README 等,适合查看插件实现、编译调试并接入现有监控流程。已有 202 人学习,可供需要落地 IIS 站点与 AppPool 监控、研究参数配置或进行二次开发的运维与开发读者参考,也可作为学习 C# 编写 Nagios 兼容监控插件、理解检查返回码与配置组织方式的参考样例。

1. 从一台跑满站点的 2019 说起:check_iis 装错位置等于白装

check_iis 是我在 Windows Server 上做 IIS 监控时反复用到的一个 C# 插件,它同时监控站点和应用程序池(AppPool)的运行时状态,输出格式与 Nagios/Icinga 兼容,配合 Icinga 2 就能把每个站点、每个池拆成独立服务。第一次装它的时候,我把编译好的 exe 直接丢进 Icinga 目录下的 sbin 就重启了服务,结果 Icinga Web 上一排服务全是 UNKNOWN,点进去只有一句 “Plugin exited with return code 3”,连是权限问题还是依赖问题都看不出来。后来才发现,这类自编译插件真正容易出问题的地方有三处:插件放哪里、以什么身份运行、编译时有没有勾选“首选 32 位”;插件本身反而最难出问题。这套环境在 Windows Server + IIS 的站点里极常见,一台机器上挂了十几个站点和池子时,靠appcmd list site手动巡检并不现实。这套环境更适合手上已经有 Icinga 2、正准备把站点状态监控补上的运维,以及被“池子悄悄停了没人知道”坑过一次的开发者。

2. 环境清单:Windows Server 2019 上的 .NET Framework 4.8 与 IIS 管理组件

装 check_iis 之前,必须先把环境里的三样东西核对清楚:操作系统和 IIS 的版本、.NET Framework 的版本、Icinga 2 的部署拓扑。这三样里任何一样对不上,症状都是同一句“插件跑不起来”,但排查方向完全相反。我见过有人花一下午改代码,最后发现是目标机根本没装 IIS 管理脚本与工具,程序集压根加载不出来。所以这一章先不动插件,只把这套底层环境整理干净。

2.1 三张表对清版本:操作系统、.NET Framework 与 Icinga 2

check_iis 是托管代码,运行依赖 .NET Framework 运行时;它读 IIS 配置又依赖Microsoft.Web.Administration,而这个程序集是随 IIS 管理工具一起装进系统的。所以“版本”这件事要分层看,不能只看一个数字。

层级我在用的版本可接受下限现场确认命令
操作系统Windows Server 2019Windows Server 2012 R2 及以上[Environment]::OSVersion.Version
IIS10.0IIS 8.5(Server 2012 R2)(Get-Item $env:windir\System32\inetsrv\w3wp.exe).VersionInfo.ProductVersion
.NET Framework4.84.6.2(取决于项目目标框架)注册表NDP\v4\Full的Release值
Icinga 22.13 及以上2.10(arguments DSL 已稳定)icinga2.exe --version

.NET Framework 的版本判断不要只看控制面板里的“已安装程序”,直接读注册表的 Release 值最准:

$k = 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' $rel = (Get-ItemProperty $k).Release switch ($rel) { {$_ -ge 528040} { '4.8 或更高'; break } # 528040 是 4.8 的标志值,4.8.1 会更高 {$_ -ge 461808} { '4.7.2'; break } {$_ -ge 394802} { '4.6.2'; break } default { "低于 4.6.2,Release=$rel" } }

这里的逻辑很直白:Release是一个单调递增的整数,用下限比较就能覆盖同一大版本下的补丁差异。为什么要卡这个值?因为如果你的构建机装的是 4.8,项目属性里目标框架也是 4.8,编译出来的 exe 拿到只装了 4.6.2 的机器上会直接弹“此应用程序需要 .NET Framework 4.8”,连主函数都进不去。这种情况有两条路:把项目目标框架降到目标机已有的版本重编,或者在目标机把 4.8 运行时补上。我一般选前者,因为能少一次生产变更。

还有一点容易被忽略:Microsoft.Web.Administration.dll的版本要跟 IIS 大版本接近。从 IIS 10 的机器上拷 dll 到 IIS 8.5 的机器,能不能跑完全看运气,ServerManager.OpenRemote一类的调用可能直接抛COMException。跨版本部署时,我宁可在目标机上装管理工具,也不做 dll 搬运。

2.2 用 PowerShell 补齐 IIS 管理脚本与工具

IIS 已经在跑站点,不代表管理接口齐全。精简安装的模板里经常只装了Web-Server,Web-Scripting-Tools是空的,这时System32\inetsrv目录下根本没有那个 dll。补齐的命令不长:

# 以管理员身份运行 PowerShell Install-WindowsFeature -Name Web-Server, Web-Mgmt-Console, Web-Scripting-Tools, Web-Mgmt-Service -IncludeManagementTools

参数逐个说清楚:

  • Web-Server:IIS 核心角色。已经装了的话这条是幂等的,重复执行不会坏事。
  • Web-Mgmt-Console:IIS 管理器图形界面。插件本身不需要它,但排错时没有它,你得靠appcmd一条条敲,效率差很多。
  • Web-Scripting-Tools:提供appcmd.exe和Microsoft.Web.Administration.dll,这是 check_iis 真正依赖的一项,也是最多人漏装的一项。
  • Web-Mgmt-Service:远程管理服务,只有需要从别的机器连 IIS 管理器时才要,纯本地监控可以不装。
  • -IncludeManagementTools:把 PowerShell 的WebAdministration模块一起装上,后面写站点清单、做验证都要用它。

装完立刻做一次“用微软自己的模块读一遍”的验证:

$dll = "$env:windir\System32\inetsrv\Microsoft.Web.Administration.dll" Test-Path $dll (Get-Item $dll).VersionInfo.FileVersion Import-Module WebAdministration Get-Website | Select-Object Name, State Get-IISAppPool | Select-Object Name, State

Test-Path返回 False 就说明管理工具没装好,后面所有步骤都不用做了。第二行看 dll 版本,Server 2019 上一般是 10.0.x。后两条命令是用WebAdministration模块枚举站点和池子,这一步如果能正常出结果,说明 IIS 的配置存储是健康的;如果这里就报错,那 check_iis 一定也跑不通,先把 IIS 自身修好再看插件。这套验证顺序我建议固定下来,能省掉大量“到底是插件问题还是 IIS 问题”的争论。

2.3 插件目录与 Icinga 2 常量:不要把自编译产物塞进 sbin

Icinga 2 的 Windows 包里有PluginDir常量,指向安装目录下的sbin。把 exe 扔进去确实能跑,但升级 agent 的时候这个目录会被安装包整体覆盖,自编译的插件连同你改过的参数一起消失,这是很典型的血泪经验。我一般单独建目录,再用自定义常量引过去:

$dir = 'C:\ProgramData\icinga2\plugins\check_iis' New-Item -ItemType Directory -Path $dir -Force | Out-Null # 断开继承,只留 SYSTEM 和 Administrators 完全控制 icacls $dir /inheritance:r /grant "SYSTEM:(OI)(CI)F" /grant "Administrators:(OI)(CI)F"

/inheritance:r去掉从父目录继承来的 ACE,(OI)(CI)表示对象继承和容器继承,F是完全控制。为什么要收紧?因为这个目录里的东西是被监控服务以高权限执行的,任何能往这里写文件的人,等于能在服务器上以系统权限跑代码。多花三十秒设权限,比事后解释为什么监控目录能被普通用户改写要轻松得多。

然后在 Icinga 的常量文件里登记路径:

// C:\ProgramData\icinga2\etc\icinga2\constants.conf const CustomPluginDir = "C:\\ProgramData\\icinga2\\plugins"

Icinga 2 的字符串里反斜杠要写两个,这是它自己的转义规则,写成单反斜杠会被当成转义序列,路径直接变形。常量定义好之后,后面 CheckCommand 里就用CustomPluginDir + "/check_iis/check_iis.exe"拼接,正斜杠在 Windows 的进程创建里一样能识别,还省掉一层转义。

3. 编译 check_iis:从 .sln 到能双击运行的 check_iis.exe

源码是 C# 的,编译这一步绕不过去。我不太赞成在 IIS 服务器上装一整套 Visual Studio,装 Build Tools 就够,几百兆和几个 G 的差别,在只做一次编译的场景里很实在。这一章的三节分别对应编译、拷贝、平台目标——最后一项是最隐蔽的,很多“命令行能跑、服务里跑不了”的现场都是它在作怪。

3.1 用 NuGet 还原加 MSBuild 构建

如果项目里有packages.config或者用了PackageReference,先还原包,再构建。命令我一般写成两段,出问题时好判断是哪一步挂的:

# 1) 还原 NuGet 包到本地目录 nuget restore .\check_iis.sln -PackagesDirectory .\packages # 2) 构建 Release 版本 $msbuild = "${env:ProgramFiles}\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe" & $msbuild .\check_iis.sln ` /t:Rebuild ` /p:Configuration=Release ` /p:Platform="Any CPU" ` /p:TargetFrameworkVersion=v4.8 ` /p:Prefer32Bit=false ` /v:m

参数和取舍:

  • /t:Rebuild:强制清理后全量重编,避免增量编译拿到上次的旧产物。第一次在陌生源码上编译时,我一定用 Rebuild 而不是 Build。
  • /p:Configuration=Release:Debug 版本带调试符号、优化关闭,性能差而且体积大,不适合放到监控机上长期跑。
  • /p:TargetFrameworkVersion=v4.8:显式覆盖项目属性,跟 2.1 里核对出来的运行时版本对齐。如果目标机只有 4.6.2,这里就改成v4.6.2。
  • /p:Prefer32Bit=false:这条后面 3.3 单独展开,先记住它必须显式关掉。
  • /v:m:只输出重要信息,日志不会刷屏。

有些 fork 出来的版本根本没有.sln,只有一个Program.cs。这种情况直接用框架自带的编译器,不需要装任何 SDK:

$csc = "$env:windir\Microsoft.NET\Framework64\v4.0.30319\csc.exe" & $csc /nologo /target:exe /platform:anycpu /optimize+ ` /out:.\bin\check_iis.exe ` /r:"$env:windir\System32\inetsrv\Microsoft.Web.Administration.dll" ` .\Program.cs

/platform:anycpu而不是anycpu32bitpreferred,这两个值的区别就是 3.3 要讲的那件事;/optimize+打开优化;/r:引用的是系统里的 IIS 管理程序集;/target:exe生成控制台程序。注意这里用的是Framework64目录下的编译器,从源头保证产物不被标记成 32 位优先。

编译完先看一眼产物:

Get-ChildItem .\bin\Release | Select-Object Name, Length

正常应该能看到check_iis.exe,可能还有check_iis.exe.config、check_iis.pdb。看到Microsoft.Web.Administration.dll也在里面,说明项目把它当本地引用复制了,这样部署的时候少一步拷文件。

3.2 拷贝产物:不能只拷一个 exe

我见过最多的部署翻车是“只拷了 exe”。这个插件的产物不止一个文件,少拷哪一个都会在运行时炸:

$src = '.\bin\Release' $dst = 'C:\ProgramData\icinga2\plugins\check_iis' Copy-Item "$src\check_iis.exe" $dst -Force if (Test-Path "$src\check_iis.exe.config") { Copy-Item "$src\check_iis.exe.config" $dst -Force } Copy-Item "$env:windir\System32\inetsrv\Microsoft.Web.Administration.dll" $dst -Force Get-ChildItem $dst | Select-Object Name, Length

check_iis.exe.config里通常带supportedRuntime声明或程序集绑定重定向,漏拷的典型症状是启动时报“配置系统未能初始化”或者加载了错误版本的程序集。把Microsoft.Web.Administration.dll一起拷到 exe 同目录,是让插件目录自包含:程序集加载器会先在 exe 所在目录找,找到就不再依赖 GAC。好处是目标机即使没装管理工具也能跑;代价是以后升级 IIS 时这个 dll 要记得同步,否则会出现版本错配的诡异问题。两种做法我心里有取舍:内网固定版本的机器,我倾向让它依赖系统 GAC;要批量铺到一堆版本不一的机器上,就拷本地。

拷完先手工跑一次帮助,不要急着配 Icinga:

cd C:\ProgramData\icinga2\plugins\check_iis .\check_iis.exe -h "exit=$LASTEXITCODE"

这一步别省。不同人 fork 出来的版本,参数命名差别很大,有的用-s/-a区分站点和池,有的用--type sites,有的干脆只接受一个-n列表。你必须把-h的真实输出抄下来,后面 4.1 的 arguments 表照着它改。帮助命令本身如果就报异常,说明依赖没解决,回到 2.2 重来。

3.3 平台目标与 Prefer 32-bit:编译期就得定死的事

.NET Framework 4.5 之后新建的控制台项目,属性里有个“首选 32 位”的复选框,默认是勾上的。勾上之后编译出来的 exe 会被打上32BITPREFERRED标记,在任何 64 位 Windows 上它都以 32 位进程启动。问题就出在这里:32 位进程访问C:\Windows\System32时,文件系统会把它重定向到C:\Windows\SysWOW64,而SysWOW64\inetsrv目录在只装了 64 位 IIS 的机器上通常根本不存在。于是插件报“找不到 Microsoft.Web.Administration.dll”——可你在资源管理器里明明看得见那个文件,这种玄学感极强的报错,我第一次遇到时盯了半小时。

确认方式有两种,装了 Windows SDK 的话corflags最直接:

corflags .\check_iis.exe # 关注的两位: # 32BITREQ : 0 # 32BITPREF : 0 ← 这个必须是 0

没装 SDK 的话,用 PowerShell 看进程位数也行:跑起插件后另开窗口执行Get-Process check_iis | Select-Object Path,再看任务管理器里的“32 位”标注。我一般还是装个 SDK,corflags和dumpbin在排查这类问题时太顺手。

解决方式就是重编。项目属性里取消勾选,或者命令行传/p:Prefer32Bit=false,两种都行,效果一样。这条规则我后来固化成习惯:凡是 .NET 的 Windows 监控插件,编译默认关掉 32 位优先,不管它到底会不会读 System32。因为这类插件十有八九要碰系统目录或者注册表的重定向区域。

4. 配置 Icinga 2:CheckCommand、Service 模板与站点/AppPool 两类对象

插件在命令行跑通之后,接下来是把它翻译成 Icinga 2 的对象。这一层最容易出错的不是语法,是参数展开——Icinga 的 arguments DSL 有几个属性决定了命令行最后长什么样,写错一个,插件收到的参数就跟你想的不一样,表现为“配置校验通过、检查结果莫名其妙”。

4.1 CheckCommand 与参数映射

先是参数约定。下面这张表是我按常见实现整理的,实际以你-h的输出为准,参数名不一致就改 arguments 里的键,路径不用动:

短参含义我的默认值备注
-t监控对象类型sites取值sites或apppools
-n站点名或池名空(表示全部)可重复传入
-w警告阈值1异常对象个数
-c严重阈值2异常对象个数
-p输出性能数据开启布尔开关,不接值
-v输出每个对象状态关闭排错时临时打开

对应到 Icinga 的对象定义:

// C:\ProgramData\icinga2\etc\icinga2\conf.d\commands\check_iis.conf object CheckCommand "check_iis" { command = [ CustomPluginDir + "/check_iis/check_iis.exe" ] arguments = { "-t" = { value = "$iis_target_type$" description = "sites 或 apppools" } "-n" = { value = "$iis_target_name$" repeat_key = true description = "传数组时展开成多个 -n" } "-w" = { value = "$iis_warn$" description = "异常对象数达到该值返回 WARNING" } "-c" = { value = "$iis_crit$" description = "异常对象数达到该值返回 CRITICAL" } "-p" = { set_if = "$iis_perfdata$" description = "布尔开关型参数,不接值" } } vars.iis_target_type = "sites" vars.iis_warn = 1 vars.iis_crit = 2 vars.iis_perfdata = true }

几个关键点解释一遍:

command写成数组而不是一整条字符串,是因为拼接后的路径里只要出现空格(Program Files那种),整串会被当成一个可执行文件名去找,进程创建直接失败。数组的每个元素单独作为一个 argv 传递,不会被拆分。

repeat_key = true决定了数组怎么展开。Service 里给vars.iis_target_name传[ "shop", "api" ],有这一行会展开成-n shop -n api;没有这一行会变成-n shop,api或者只取第一个。很多 C# 插件只认重复短参,不认逗号分隔,这个属性写不写,结果完全不同。

set_if用于布尔开关。如果写成value = "$iis_perfdata$",关掉的时候命令行会出现-p "",插件把空字符串当成一个参数值,可能直接被判为参数错误。set_if为真才追加这个参数,为假时整条不出现,这才是开关参数该有的行为。

vars段是默认值,Service 上定义同名变量会覆盖它。把站点/池的默认阈值放在这里,可以少写很多重复配置。

改完先看展开结果,别急着重启:

& "C:\Program Files\ICINGA2\sbin\icinga2.exe" object list --type CheckCommand --name check_iis

输出里能看到arguments的完整解析结果。如果-n那一项的repeat_key显示为 false,说明配置文件没被加载到——检查conf.d下的 include 是否包含你新建的子目录。

4.2 站点与 AppPool 的 Service 模板

监控对象多的时候不要在 Service 里一个个手写,用apply ... for循环生成。先在 Host 上声明清单:

<p> <a href="https://download.csdn.net/download/weixin_42102933/18499915" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询