8.1 发布方式与部署目标 发布是把应用翻译成"目标机可运行的产物集":框架依赖发布产出轻量包但要求目标机装运行时,自包含发布把运行时打进产物换取零依赖。部署目标决定产物落在哪——IIS 或 Nginx 后面当反向代理、容器镜像里随编排调度、或直接交给平台托管。选型跟着运维能力与交付约束走。 最后一章第一节处理一个开发机上感觉不到的问题: 与"服务器上运行"之间隔着一次翻译。观察哨本节先拆开发布产物的包裹看里面是什么,再做两种发布方式的对照实验,最后给部署目标一张选型表。 拆开发布产物 产物里没有源代码,也没有"服务器程序"——应用集加配置由目标机上的运行时装载执行。理解这一点,"目标机要装什么"的问题就有了答案的雏形:要么目标机有运行时(框架依赖),要么产物自己带(自包含)。
发布是把应用翻译成"目标机可运行的产物集":框架依赖发布产出轻量包但要求目标机装运行时,自包含发布把运行时打进产物换取零依赖。部署目标决定产物落在哪——IIS 或 Nginx 后面当反向代理、容器镜像里随编排调度、或直接交给平台托管。选型跟着运维能力与交付约束走。
最后一章第一节处理一个开发机上感觉不到的问题:dotnet run 与"服务器上运行"之间隔着一次翻译。观察哨本节先拆开发布产物的包裹看里面是什么,再做两种发布方式的对照实验,最后给部署目标一张选型表。
# 发布:产出落在 publish 目录(Debug 默认配置) dotnet publish -c Release # 输出关键行: # 已成功创建包 # 已将以下文件复制到输出目录(节选) # 看看包裹里有什么 dir bin\Release\net8.0\publish\ # 应用程序集(ObsStation.dll)——编译后的代码 # 依赖清单与运行时配置(deps.json 与 runtimeconfig.json)——告诉运行时怎么装配 # 配置文件(appsettings 全家)——2.3 节的配置来源 # 静态资源目录——页面与样式脚本 # 依赖包清单里登记的 NuGet 依赖
产物里没有源代码,也没有"服务器程序"——应用集加配置由目标机上的运行时装载执行。理解这一点,"目标机要装什么"的问题就有了答案的雏形:要么目标机有运行时(框架依赖),要么产物自己带(自包含)。
# 方式一:框架依赖发布(默认) dotnet publish -c Release -o fdd # 产物大小(空模板实测):约 200 KB 量级 # 前提:目标机必须装有匹配版本的 .NET 运行时 # 方式二:自包含发布,指定目标平台 dotnet publish -c Release -r linux-x64 --self-contained -o scd # 产物大小:约 90 MB 量级——把运行时整个带上了 # 前提:无。目标机不需要装任何 .NET # 运行方式也变了: # 框架依赖:dotnet ObsStation.dll ← 用目标机的运行时 # 自包含: ./ObsStation ← 产物自带可执行入口
| 维度 | 框架依赖 | 自包含 |
|---|---|---|
| 产物体积 | 小(百 KB 级) | 大(百 MB 级) |
| 目标机要求 | 装匹配运行时 | 零要求 |
| 补丁升级 | 运行时补丁全局生效,一次安装多家受益 | 每个产物独立升运行时,需重新发布 |
| 版本锁定 | 跟随目标机 | 锁死在发布时刻 |
| 典型场景 | 服务器池统一管理运行时 | 容器、嵌入式、客户环境不可控 |

单文件发布与裁剪是两个进阶开关:单文件把散落文件合并成一个可执行(交付清爽,启动时仍解压到内存或磁盘);裁剪移除"未引用"的运行时程序集(体积再减,但反射密集的应用有裁剪掉在用代码的风险——开启前必须回归测试)。两个开关解决的是分发体验问题,与运行行为无关,优先级低于主选型。
反向代理形态:Kestrel 直接对外在现代部署里已经少见,主流是前面站一层 Nginx 或 IIS。代理负责 TLS 终结、静态资源分流、多实例负载均衡;应用只管收转发来的请求。有一个必配细节:代理转发后应用看到的"客户端地址与协议"都是代理的,需要转发头协议把原始信息传回来(代理设置转发头,应用侧启用对应中间件处理),否则日志里的 IP 全是代理地址、生成的链接协议错乱——观察哨处理过"日志全是内网地址、封禁功能失效"的事故,根因就是这一处配置。
容器形态:自包含与容器天然一对——镜像里除了产物不需要任何 .NET 安装。容器化的 ASP.NET Core 有个约定俗成的组合拳:基础镜像带运行时(框架依赖风格的分层复用)、配置全走环境变量(2.3 节的双下划线约定在这里发光,一份镜像靠环境变量跑遍三套环境)、健康检查端点交给编排器探活(8.2 节展开)。
平台托管形态:应用服务平台按"代码或产物"维度收货,构建、发布、证书、扩缩容全托管。适合小团队起步,代价是平台绑定与定制上限。
选型不走信仰走约束:运维团队能力(有没有容器平台)、交付环境(客户机房还是云上)、合规要求(补丁责任归谁)。我的默认建议:新项目容器化起步(本地与生产环境一致性收益最大),传统机房交付用反向代理形态,平台托管留给原型与内部工具。
Release 编译打开优化、去掉调试符号,吞吐与体积都占优;Debug 面向调试器,性能不可代表生产。发布永远用 Release,这是底线而非偏好。另有一档发布优化常被忽略:发布与编译是两个开关,配置选 Release 的同时,发布参数里还能选"发布时就绪运行"——产物带预编译机器码,首个请求不再付出即时编译成本,冷启动敏感的场景值得打开。
能,而且这正是发布方式与 8.2 节环境机制的组合价值:产物完全相同,两台机器用不同的环境变量集(环境名、连接串、端口)各自启动。运维上把"产物"与"配置"分开管理——产物从构建流水线来,配置从部署清单来,回滚时只换产物不动配置,发布事故的恢复时间以秒计。
⚠️ 常见坑:开发机验证的是框架依赖产物,交付给客户的却是自包含产物且没在目标平台跑过一次——运行时标识写错平台(比如把 linux-x64 写成 win-x64),到客户现场才暴露。发布矩阵里每个目标平台都要有 CI 或手工的冒烟验证。
💡 关键直觉:发布是"翻译"不是"拷贝"——把"能在我的开发机跑"翻译成"能在目标环境跑"。翻译要核对两份词典:运行时(装没装、版本对不对)与环境(配置、地址、头信息)。两份词典都对上,产物才真正落地。