mcpField
D
dotnet-container-deployment

dotnet-container-deployment

github.com/wshaddix云与 DevOps8672 次安装35 周前更新
代码仓库

关于 dotnet-container-deployment

为 .NET 容器部署提供指导,涵盖 Kubernetes、Docker Compose 和镜像发布流程,面向将 .NET 应用部署到集群或本地开发环境的开发者。

主要功能

  1. 01Kubernetes Deployment 与 Service 清单
  2. 02启动、存活和就绪探针
  3. 03Docker Compose 开发环境
  4. 04配置与密钥处理
  5. 05容器镜像构建和推送模式
  6. 06优雅关闭指导

适用场景

  1. 01将 .NET API 部署到 Kubernetes
  2. 02在本地运行应用依赖
  3. 03配置健康检查与关闭流程
  4. 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
映像标签策略
标签模式示例用例
latestmyapi:latest仅用于开发环境——切勿用于生产环境
Semvermyapi:1.2.3发布版本——不可变
Major.Minormyapi:1.2用于补丁更新的浮动标签
SHAmyapi:sha-abc1234每次提交唯一——便于追踪
Branchmyapi:mainCI 构建——分支中的最新版本
在 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 常见问题

  1. 不要省略启动探针——否则,存活探针会在初始化期间运行,可能导致启动较慢的应用重启。启动预算按 failureThreshold * periodSeconds 计算。
  2. 不要在存活探针中包含依赖项检查——数据库中断不应导致应用重启。存活端点只能检查进程本身。有关存活与就绪模式,请参阅 [skill:dotnet-observability]。
  3. 不要在 Kubernetes 清单中使用 latest 标签——latest 是可变的,而且 imagePullPolicy: IfNotPresent 可能会使用过时的映像。请使用不可变标签(semver 或 SHA)。
  4. 不要在 Kubernetes 清单中硬编码连接字符串——请使用通过 secretKeyRef/configMapRef 引用的 Secrets 或 ConfigMaps。
  5. 不要将 terminationGracePeriodSeconds 设置得低于 Host.ShutdownTimeout——应用需要时间排空正在处理的请求,然后 Kubernetes 才会发送 SIGKILL。
  6. 不要忘记在 Docker Compose 的 depends_on 中设置 condition: service_healthy——如果没有此条件,Compose 会立即启动依赖服务,而不会等待健康检查。

参考资料

评论

支持 Markdown