这篇文档将剥离 CI/CD 的外壳,直击运行时的核心:为什么 .NET Core 能在 IIS 上跑?它是怎么跑的?配置的关键点在哪里?
深度解析:.NET Core / .NET 6+ 在 Windows IIS 下的托管机制
1. 核心架构:从 "Pipeline" 到 "Reverse Proxy"
在传统的 .NET Framework (ASP.NET 4.x) 时代,IIS 深度集成 System.Web,IIS 工作进程 (w3wp.exe) 直接加载 CLR 并运行代码。
但在 .NET Core / .NET 6+ 时代,架构发生了根本性的变化。IIS 不再直接“运行”你的代码,而是扮演了一个 反向代理 (Reverse Proxy) 的角色。
1.1 关键组件:ASP.NET Core Module (ANCM)
IIS 本身不懂 .NET Core。为了让它们配合,微软推出了一个名为 ASP.NET Core Module (ANCM) 的原生 IIS 模块。
当请求到达 IIS (端口 80/443) 时,IIS 会将请求交给 ANCM,由 ANCM 负责启动和管理你的 .NET 应用程序。
1.2 两种托管模式 (Hosting Models)
这是配置中最关键的概念,决定了你的应用性能和运行方式。
A. 进程内托管 (In-Process Hosting) - 默认推荐
- 原理:.NET Core 运行时直接加载到 IIS 的工作进程 (
w3wp.exe) 中。 - 性能:极高。请求在进程内部传递,没有网络开销。
- Web Server:使用
IISHttpServer实现。 - 表现:任务管理器里只有一个
w3wp.exe。
B. 进程外托管 (Out-of-Process Hosting)
- 原理:IIS (
w3wp.exe) 仅仅是一个转发器。它启动一个独立的dotnet.exe进程运行你的应用(使用 Kestrel 服务器)。IIS 通过 HTTP 将请求转发给 Kestrel。 - 性能:略低(有进程间通讯开销)。
- 表现:任务管理器里能看到
w3wp.exe(负责转发) 和dotnet.exe(负责业务)。 - 场景:主要用于兼容性测试或完全隔离。
2. 环境准备与安装细节
要在 Windows Server 上运行 .NET 6 API,单纯复制文件是不够的。
2.1 必须安装:Hosting Bundle
必须下载并安装 .NET Core Hosting Bundle (托管捆绑包)。它包含三个东西:
弯路总结:如果我们只安装 SDK 或 Runtime,而没装 Hosting Bundle,IIS 里就没有 ANCM 模块,访问网站会直接报 HTTP 500.19 或 模块未找到。
3. IIS 配置详解:那些“反直觉”的设置
习惯了传统 ASP.NET 的开发者,在配置 .NET Core 站点时容易踩坑。
3.1 应用程序池 (Application Pool) 设置
这是最容易错的地方。
- 配置项:
.NET CLR 版本 - 正确设置:
无托管代码 (No Managed Code) - 原因:IIS 不需要加载旧的 .NET Framework CLR。现代 .NET 运行时由 ANCM 负责启动,IIS 只需要作为一个原生宿主。如果你选了
.NET v4.0,反而会造成不必要的资源浪费,甚至冲突。
3.2 Web.Config 的角色转变
在 .NET 6 中,web.config 不再负责 AppSettings(那些去 appsettings.json 了)。它的主要职责变成了配置 ANCM。
一个标准的 .NET Core web.config 如下:
<configuration>
<system.webServer>
<handlers>
<!-- 注册 ANCM 处理器,接管所有请求 -->
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<!-- 核心配置段 -->
<aspNetCore processPath="dotnet"
arguments=".\Sellers.Platform.Web.Host.dll"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess" />
</system.webServer>
</configuration>
- processPath: 指示 IIS 如何启动应用。通常是
dotnet,或者直接指向.exe。 - arguments: 指定启动的 DLL。
- hostingModel: 显式指定
inprocess(进程内) 或outofprocess(进程外)。
4. 故障排查:当 "HTTP 500" 发生时
IIS 报错通常很含糊(如 500.30 ANCM Startup Failure)。要看到真实的 .NET 异常,需要开启 stdout 日志。
4.1 开启 stdout 日志
这是调试启动崩溃的唯一手段。
- 打开部署目录下的
web.config。 - 设置
stdoutLogEnabled="true"。 - 确保
logs文件夹存在(且 IIS 用户有写入权限)。 - 重启网站,请求一次。
- 查看
logs目录下的文件,里面会包含完整的 C# 异常堆栈(比如数据库连接失败、依赖注入错误)。
注意:生产环境务必关闭此选项,否则日志文件会无限增长填满磁盘。
5. 我们的工作与弯路总结 (LODA.Seller 实战)
回顾我们刚才的部署过程,其实就是在通过自动化脚本,精准地控制上述所有环节。
5.1 我们做了什么(成功路径)
- 文件隔离:我们采用了按日期 (
20251118) 分离的物理路径,避免了覆盖文件时的文件锁问题。 - 配置注入:通过 ensure-iis-config.ps1,我们自动检测服务器环境,并强制修正了 AppPool 的设置为 No Managed Code(您应该记得日志里那句
Auto-correcting StartMode...)。 - Web.Config 生成:我们没有完全依赖代码库里的
web.config,而是根据部署环境动态调整参数(如processPath指向)。 - 端口绑定:我们强制指定了 Staging (8062) 和 Prod (8063),确保了不同环境在同一台服务器上并行不悖。
5.2 我们走的弯路(经验教训)
-
端口推断的陷阱:
- 现象:最开始脚本试图自动分配端口,结果导致 CI 认为部署在 8062,但实际可能冲突或未生效。
- 教训:在 servers.yml 中显式定义端口是 DevOps 的黄金法则。永远不要让脚本去“猜”配置。
-
健康检查的兼容性:
- 现象:使用 Linux 思维的
curl去检查 Windows IIS 站点,因 Shell 差异导致HTTP 000假死。 - 教训:入乡随俗。在 Windows 上,PowerShell 的
Invoke-WebRequest才是原生的王者。
- 现象:使用 Linux 思维的
-
文件锁死:
- 现象:重试 Job 时,因为版本号没变,试图覆盖正在运行的 DLL,导致
Access Denied。 - 教训:.NET Core 运行时会死死锁住 DLL。原子发布(部署到新目录 -> 切换 IIS 指向)是解决此问题的唯一完美方案。
- 现象:重试 Job 时,因为版本号没变,试图覆盖正在运行的 DLL,导致
希望这份文档能帮助您从底层原理上理解我们的部署工作。它不仅是一个网站,更是一个精密配合的进程协作系统。