SuperAPI AI 工具集

sdk-container-publishing

部署与运维

查看项目

更新于 10-15 09:48

技能介绍

用途

当需要为 .NET 服务生成容器镜像、又不想手写 Dockerfile 时,可使用本技能。它适用于以下场景:

  • 想要为 .NET 服务生成容器镜像,且不想手写 Dockerfile。
  • 想了解在没有 Dockerfile 的情况下,如何为 SDK 构建的镜像打标签、命名或暴露端口。
  • 需要把容器发布接入 CI/CD 或发布流水线。
  • SDK 构建的镜像被推送到错误的仓库、使用了错误的标签或选错了基础镜像。
  • 需要镜像的 tarball 用于安全扫描或离线(air-gapped)加载。

不适用场景:需要在镜像中执行 RUN 步骤或任意层定制。SDK 工具无法模拟 RUN,此时应改为编写 Dockerfile。

主要能力

自 .NET 7 起,dotnet publish 可原生构建容器镜像。Microsoft.NET.Build.Containers 工具随 SDK 一起提供,因此无需 Dockerfile,构建镜像也不需要安装 Docker;仅在本地运行镜像时才需要 Docker 或 Podman。

最快的方式是一条命令:

dotnet publish /t:PublishContainer -c Release

该命令一步完成应用编译并产出镜像。镜像名称默认取项目的 AssemblyName,可通过 ContainerRepository 覆盖(早期版本中名为 ContainerImageName)。

三种输出模式

工具以三种方式之一写出镜像,按镜像最终去向选择:

  • 本地守护进程(默认):用于本地开发,推送到正在运行的 Docker 或 Podman 守护进程,无需额外配置。
  • Tarball:需要一个文件用于扫描、传输或后续加载时使用。设置 ContainerArchiveOutputPath 为 .tar.gz 路径,然后用 docker load -i 或 podman load -i 加载。适用于安全扫描工作流。
  • 推送镜像仓库:用于发布。设置 ContainerRegistry(例如 ghcr.io 或内部仓库),镜像会被直接推送。

关键事实

  • 构建不需要 Docker。SDK 自行创建镜像,仅在运行镜像时才需要运行时。
  • 镜像名称默认取 AssemblyName,可通过 ContainerRepository 覆盖。
  • 基础镜像会被推断。自包含项目使用 mcr.microsoft.com/dotnet/runtime-deps,ASP.NET Core 使用 dotnet/aspnet,其他应用使用 dotnet/runtime,并按 TFM 打标签。自 SDK 8.0.200 起,推断会考虑体积与安全:musl RID 选择 Alpine 变体,PublishAot=true 选择 chiseled AOT runtime-deps 变体。
  • 自 .NET 8 起,未设置标签时 SDK 会将镜像标记为 latest。如果发布依赖版本标签,必须显式设置 ContainerImageTag(单个标签)或 ContainerImageTags(列表)。这是 CI 中常见的陷阱。
  • Windows 镜像需要显式指定基础镜像。Microsoft 不再在清单列表中包含 Windows 变体。要面向 Windows,需将 ContainerBaseImage 设为具体的 nanoserver 标签(例如 mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-ltsc2022)。
  • 多架构镜像通过 ContainerRuntimeIdentifiers 生成(以分号分隔,是 RuntimeIdentifiers 的子集),输出为 OCI 镜像索引。自 SDK 8.0.405、9.0.102 和 9.0.2xx 起支持。
  • 不安全仓库通过 DOTNET_CONTAINER_INSECURE_REGISTRIES 环境变量传入(逗号分隔),自 SDK 9.0.100 起支持。自 .NET 8.0.400 起,SDK 会读取标准 Docker / Podman 配置来决定使用 HTTP 还是 HTTPS。

使用方式

最小属性集

大多数服务只覆盖需要的属性,其余交给 SDK 推断。一个最小的 csproj 如下:

<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net10.0</TargetFramework> <ContainerRepository>my-service</ContainerRepository> <ContainerImageTags>$(VersionPrefix);latest</ContainerImageTags> </PropertyGroup>

<ItemGroup> <ContainerPort Include="8080" Type="tcp" /> </ItemGroup> </Project>

说明:

  • 没有 ContainerRegistry、ContainerBaseImage、ContainerUser。SDK 会为大多数 ASP.NET 服务正确设置这些默认值。
  • ContainerPort 项可在没有 Dockerfile 的情况下暴露端口。
  • 标签遵循 semver:包版本加一个浮动的 latest——但前提是 $(VersionPrefix) 在该文件被求值之前已设置(在命令行、Directory.Build.props 中或项目文件更早位置)。SDK 会在项目主体之后应用其 1.0.0 默认值,因此普通的本地发布会将 $(VersionPrefix) 解析为空,镜像只被标记为 latest。

CI/CD 发布

在 CI 中,针对仓库发布并使用发布名称打标签。标签驱动的模式如下:

dotnet publish src/MyService/MyService.csproj
-p:VersionPrefix=${{ github.ref_name }}
-p:ContainerRegistry=docker.example.internal
-c Release /t:PublishContainer

-p:VersionPrefix 会填充项目中设置的 ContainerImageTags 列表,因此镜像会同时带有发布名称和 latest 标签。不要同时传入 -p:ContainerImageTag——SDK 会拒绝同时使用 ContainerImageTag 和 ContainerImageTags(CONTAINER2008)。

如果要在整个仓库集中管理镜像设置,可将其放入带 CI 条件判断的 Directory.Build.props 中,例如版本标签和标签(labels)。

适用场景

  • 为 .NET 服务快速生成容器镜像,无需维护 Dockerfile。
  • 在 CI/CD 或发布流水线中自动构建并推送镜像。
  • 生成镜像 tarball 用于安全扫描或离线环境加载。
  • 需要多架构镜像或针对自包含 / AOT 项目优化基础镜像。

限制

  • 无法执行 RUN 步骤或任意层定制;需要时应编写 Dockerfile。
  • 默认输出模式需要本地 Docker / Podman 守护进程;无守护进程时需改用 ContainerArchiveOutputPath 输出 tarball。
  • Windows 镜像需显式指定基础镜像。
  • 未显式设置标签时,自 .NET 8 起镜像会被标记为 latest,可能导致版本标签缺失。
  • 镜像名称默认取 AssemblyName,默认推送到本地守护进程,可能推送到非预期位置。

故障排查

常见故障包括标签漂移、镜像名或仓库错误、无法执行 RUN、构建主机无守护进程、不安全仓库被拒、多 RID 发布竞争以及多目标发布。要点:显式设置标签(ContainerImageTag 或 ContainerImageTags)、显式设置 ContainerRepository 和 ContainerRegistry,在没有守护进程时使用 ContainerArchiveOutputPath。

  • 标签漂移:流水线发布了镜像但找不到预期版本,只有 latest。原因是自 .NET 8 起,未设置 ContainerImageTag 或 ContainerImageTags 时 SDK 将镜像标记为 latest 而非 $(Version)。修复:在发布步骤中显式设置标签。
  • 镜像名或仓库错误:镜像被推送到意外位置。原因是 SDK 从 AssemblyName 推导仓库名并默认推送到本地守护进程。修复:显式设置 ContainerRepository 和 ContainerRegistry。
  • RUN 不可用:需要在镜像内运行命令但 SDK 不支持。原因是 SDK 工具在没有 Dockerfile 时无法执行 RUN 步骤。修复:通过 ContainerBaseImage 使用自定义基础镜像,或编写 Dockerfile。
  • 构建主机无守护进程:发布失败,因为无法访问 Docker / Podman 守护进程。原因是默认输出模式推送到本地守护进程。修复:设置 ContainerArchiveOutputPath 写出 tarball,无需守护进程。

自包含与 AOT

自包含项目默认使用 runtime-deps 基础镜像。AOT 项目会获得 chiseled AOT runtime-deps 变体,以生成更小、更安全的镜像。

验证

确认镜像已构建并存在:

本地守护进程中的镜像

docker images | grep <repository>

已生成 tarball

ls -la <ContainerArchiveOutputPath>

推送成功(仓库模式)

docker pull <ContainerRegistry>/<ContainerRepository>:<tag>

相关技能

  • dotnet-skills:testcontainers - 在测试中运行容器
  • dotnet-skills:aot-trimming - AOT 与裁剪,与基础镜像选择相关