这篇文档将剥离 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 (托管捆绑包)。它包含三个东西:

  1. .NET Runtime (运行时,负责跑代码)
  2. ASP.NET Core Runtime (Web 库)
  3. ASP.NET Core Module v2 (关键!这是 IIS 的插件)

弯路总结:如果我们只安装 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 日志

这是调试启动崩溃的唯一手段。

  1. 打开部署目录下的 web.config
  2. 设置 stdoutLogEnabled="true"
  3. 确保 logs 文件夹存在(且 IIS 用户有写入权限)。
  4. 重启网站,请求一次。
  5. 查看 logs 目录下的文件,里面会包含完整的 C# 异常堆栈(比如数据库连接失败、依赖注入错误)。

注意:生产环境务必关闭此选项,否则日志文件会无限增长填满磁盘。


5. 我们的工作与弯路总结 (LODA.Seller 实战)

回顾我们刚才的部署过程,其实就是在通过自动化脚本,精准地控制上述所有环节。

5.1 我们做了什么(成功路径)

  1. 文件隔离:我们采用了按日期 (20251118) 分离的物理路径,避免了覆盖文件时的文件锁问题。
  2. 配置注入:通过 ensure-iis-config.ps1,我们自动检测服务器环境,并强制修正了 AppPool 的设置为 No Managed Code(您应该记得日志里那句 Auto-correcting StartMode...)。
  3. Web.Config 生成:我们没有完全依赖代码库里的 web.config,而是根据部署环境动态调整参数(如 processPath 指向)。
  4. 端口绑定:我们强制指定了 Staging (8062) 和 Prod (8063),确保了不同环境在同一台服务器上并行不悖。

5.2 我们走的弯路(经验教训)

  1. 端口推断的陷阱

    • 现象:最开始脚本试图自动分配端口,结果导致 CI 认为部署在 8062,但实际可能冲突或未生效。
    • 教训:在 servers.yml显式定义端口是 DevOps 的黄金法则。永远不要让脚本去“猜”配置。
  2. 健康检查的兼容性

    • 现象:使用 Linux 思维的 curl 去检查 Windows IIS 站点,因 Shell 差异导致 HTTP 000 假死。
    • 教训入乡随俗。在 Windows 上,PowerShell 的 Invoke-WebRequest 才是原生的王者。
  3. 文件锁死

    • 现象:重试 Job 时,因为版本号没变,试图覆盖正在运行的 DLL,导致 Access Denied
    • 教训:.NET Core 运行时会死死锁住 DLL。原子发布(部署到新目录 -> 切换 IIS 指向)是解决此问题的唯一完美方案。

希望这份文档能帮助您从底层原理上理解我们的部署工作。它不仅是一个网站,更是一个精密配合的进程协作系统。