关于 dotnet-container-deployment
为 .NET 容器部署提供指导,涵盖 Kubernetes、Docker Compose 和镜像发布流程,面向将 .NET 应用部署到集群或本地开发环境的开发者。
主要功能
- 01Kubernetes Deployment 与 Service 清单
- 02启动、存活和就绪探针
- 03Docker Compose 开发环境
- 04配置与密钥处理
- 05容器镜像构建和推送模式
- 06优雅关闭指导
适用场景
- 01将 .NET API 部署到 Kubernetes
- 02在本地运行应用依赖
- 03配置健康检查与关闭流程
- 04在 CI 中构建并推送镜像
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-container-deployment安装后在 Claude Code 中输入/dotnet-container-deployment确认已加载。
详细介绍
dotnet-container-deployment
将 .NET 容器部署到 Kubernetes 和本地开发环境。涵盖 Kubernetes Deployment、Service 和探针 YAML、本地开发工作流的 Docker Compose,以及用于构建和推送容器映像的 CI/CD 集成。
不在此范围内: Dockerfile 编写、多阶段构建、基础映像选择以及 dotnet publish 容器映像,请参阅 [skill:dotnet-containers]。高级 CI/CD 管道模式(矩阵构建、部署管道、环境晋级),请参阅 [skill:dotnet-gha-deploy] 和 [skill:dotnet-ado-patterns]。DI 和异步模式,请参阅 [skill:dotnet-csharp-dependency-injection] 和 [skill:dotnet-csharp-async-patterns]。容器部署测试,请参阅 [skill:dotnet-integration-testing] 中的 Testcontainers 模式,以及 [skill:dotnet-playwright] 中针对已部署容器进行的 E2E 测试。
交叉引用:[skill:dotnet-containers] 介绍 Dockerfile 和映像最佳实践;[skill:dotnet-observability] 介绍 Kubernetes 探针使用的运行状况检查端点模式。
Kubernetes Deployment
Deployment 清单
适用于生产环境的 .NET API Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
labels:
app: order-api
app.kubernetes.io/name: order-api
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: api
spec:
replicas: 3
selector:
matchLabels:
app: order-api
template:
metadata:
labels:
app: order-api
spec:
containers:
- name: order-api
image: ghcr.io/myorg/order-api:1.0.0
ports:
- containerPort: 8080
protocol: TCP
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector.monitoring:4317"
- name: OTEL_SERVICE_NAME
value: "order-api"
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: order-api-secrets
key: connection-string
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 30
securityContext:
runAsNonRoot: true
runAsUser: 1654
fsGroup: 1654
terminationGracePeriodSeconds: 30
Service 清单
在集群内公开 Deployment:
apiVersion: v1
kind: Service
metadata:
name: order-api
labels:
app: order-api
spec:
type: ClusterIP
selector:
app: order-api
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
用于非敏感配置的 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: order-api-config
data:
ASPNETCORE_ENVIRONMENT: "Production"
Logging__LogLevel__Default: "Information"
Logging__LogLevel__Microsoft.AspNetCore: "Warning"
在 Deployment 中引用:
envFrom:
- configMapRef:
name: order-api-config
用于敏感配置的 Secrets
apiVersion: v1
kind: Secret
metadata:
name: order-api-secrets
type: Opaque
stringData:
connection-string: "Host=postgres;Database=orders;Username=app;Password=secret"
在生产环境中,请使用外部 Secrets Operator(例如 External Secrets Operator、Sealed Secrets),而不要使用存储在源代码管理中的纯 Kubernetes Secrets。
Kubernetes 探针
探针用于告知 Kubernetes 如何检查应用的运行状况。它们对应于 .NET 应用中定义的运行状况检查端点(请参阅 [skill:dotnet-observability])。
探针类型
| 探针 | 用途 | 端点 | 失败时的操作 |
|---|---|---|---|
| Startup | 应用是否已完成初始化? | /health/live | 继续等待(最多等待 failureThreshold * periodSeconds) |
| Liveness | 进程是否运行正常? | /health/live | 重启 Pod |
| Readiness | 进程能否处理流量? | /health/ready | 从 Service 端点中移除 |
探针配置指南
# Startup probe: give the app time to initialize
# Total startup budget: failureThreshold * periodSeconds = 30 * 5 = 150s
startupProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 30
# Liveness probe: detect deadlocks and hangs
# Only runs after startup probe succeeds
livenessProbe:
httpGet:
path: /health/live
port: 8080
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
# Readiness probe: control traffic routing
readinessProbe:
httpGet:
path: /health/ready
port: 8080
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
优雅关闭
.NET 会响应 SIGTERM 并开始优雅关闭。配置 terminationGracePeriodSeconds,以便正在处理的请求能够完成:
spec:
terminationGracePeriodSeconds: 30
在应用中,使用 IHostApplicationLifetime 处理关闭:
app.Lifetime.ApplicationStopping.Register(() =>
{
// Perform cleanup: flush telemetry, close connections
Log.CloseAndFlush();
});
确保 Host.ShutdownTimeout 允许正在处理的请求完成:
builder.Host.ConfigureHostOptions(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds(25);
});
将 ShutdownTimeout 设置为小于 terminationGracePeriodSeconds 的值,以确保应用在 Kubernetes 发送 SIGKILL 之前关闭。
用于本地开发的 Docker Compose
Docker Compose 可提供与生产依赖项相匹配的本地开发环境。
基本 Compose 文件
# docker-compose.yml
services:
order-api:
build:
context: .
dockerfile: src/OrderApi/Dockerfile
ports:
- "8080:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__DefaultConnection=Host=postgres;Database=orders;Username=app;Password=devpassword
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# Note: CMD-SHELL + curl requires a base image with shell and curl installed.
# Chiseled/distroless images lack both. For chiseled images, either use a
# non-chiseled dev target in the Dockerfile or omit the healthcheck and rely
# on depends_on ordering (acceptable for local dev).
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health/live || exit 1"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
postgres:
image: postgres:17
environment:
POSTGRES_DB: orders
POSTGRES_USER: app
POSTGRES_PASSWORD: devpassword
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d orders"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
volumes:
postgres-data:
开发环境覆盖配置
使用单独的覆盖文件配置开发环境专用设置:
# docker-compose.override.yml (auto-loaded by docker compose up)
services:
order-api:
build:
target: build # Stop at build stage for faster rebuilds
volumes:
- .:/src # Mount source for hot reload
environment:
- ASPNETCORE_ENVIRONMENT=Development
- DOTNET_USE_POLLING_FILE_WATCHER=true
command: ["dotnet", "watch", "run", "--project", "src/OrderApi/OrderApi.csproj"]
可观测性堆栈
添加 OpenTelemetry 收集器和 Grafana,以便在本地进行可观测性监控:
# docker-compose.observability.yml
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otelcol-config.yaml"]
volumes:
- ./infra/otelcol-config.yaml:/etc/otelcol-config.yaml
ports:
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
volumes:
grafana-data:
使用可观测性堆栈运行:
docker compose -f docker-compose.yml -f docker-compose.observability.yml up
CI/CD 集成
用于构建和推送 .NET 容器映像的基本 CI/CD 模式。高级 CI 模式(矩阵构建、环境晋级、部署管道),请参阅 [skill:dotnet-gha-publish]、[skill:dotnet-gha-deploy] 和 [skill:dotnet-ado-publish]。
GitHub Actions:构建并推送
# .github/workflows/docker-publish.yml
name: Build and Push Container
on:
push:
branches: [main]
tags: ["v*"]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
映像标签策略
| 标签模式 | 示例 | 用例 |
|---|---|---|
latest | myapi:latest | 仅用于开发环境——切勿用于生产环境 |
| Semver | myapi:1.2.3 | 发布版本——不可变 |
| Major.Minor | myapi:1.2 | 用于补丁更新的浮动标签 |
| SHA | myapi:sha-abc1234 | 每次提交唯一——便于追踪 |
| Branch | myapi:main | CI 构建——分支中的最新版本 |
在 CI 中使用 dotnet publish Container
对于使用 dotnet publish /t:PublishContainer 而非 Dockerfile 的项目:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- name: Publish container image
run: |
dotnet publish src/OrderApi/OrderApi.csproj \
--os linux --arch x64 \
/t:PublishContainer \
-p:ContainerRegistry=${{ env.REGISTRY }} \
-p:ContainerRepository=${{ env.IMAGE_NAME }} \
-p:ContainerImageTag=${{ github.sha }}
核心原则
- 使用启动探针,将初始化时间与存活检测解耦——如果没有启动探针,启动较慢的应用可能会在就绪之前被终止
- 将存活与就绪分开——存活检查不应包含依赖项健康状态(端点模式请参阅 [skill:dotnet-observability])
- 设置资源请求和限制——否则,Pod 可能会挤占其他工作负载的资源,或不可预测地因 OOM 被终止
- 以非 root 用户运行——在 Pod 安全上下文中设置
runAsNonRoot: true,并使用 chiseled 映像(请参阅 [skill:dotnet-containers]) - 在 Docker Compose 中使用带健康检查的
depends_on——防止依赖项就绪前应用启动 - 不要将机密信息放入清单——使用 Kubernetes Secrets 和外部机密运算符,不要在源代码管理中存放明文值
- 使 ShutdownTimeout 与 terminationGracePeriodSeconds 相匹配——确保应用在 Kubernetes 发送 SIGKILL 前完成清理
Agent 常见问题
- 不要省略启动探针——否则,存活探针会在初始化期间运行,可能导致启动较慢的应用重启。启动预算按
failureThreshold * periodSeconds计算。 - 不要在存活探针中包含依赖项检查——数据库中断不应导致应用重启。存活端点只能检查进程本身。有关存活与就绪模式,请参阅 [skill:dotnet-observability]。
- 不要在 Kubernetes 清单中使用
latest标签——latest是可变的,而且imagePullPolicy: IfNotPresent可能会使用过时的映像。请使用不可变标签(semver 或 SHA)。 - 不要在 Kubernetes 清单中硬编码连接字符串——请使用通过
secretKeyRef/configMapRef引用的 Secrets 或 ConfigMaps。 - 不要将
terminationGracePeriodSeconds设置得低于Host.ShutdownTimeout——应用需要时间排空正在处理的请求,然后 Kubernetes 才会发送 SIGKILL。 - 不要忘记在 Docker Compose 的
depends_on中设置condition: service_healthy——如果没有此条件,Compose 会立即启动依赖服务,而不会等待健康检查。