mcpField
D
dotnet-container-deployment

dotnet-container-deployment

github.com/wshaddixクラウド・DevOps8672 回インストール35 週間前に更新
リポジトリ

dotnet-container-deployment について

Kubernetes、Docker Compose、イメージ公開のワークフローを扱う、.NET コンテナー展開のガイダンススキルです。クラスターやローカル開発環境に .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. 04CI でのイメージ構築とプッシュ

インストール

コマンドをコピーしてターミナルで実行します。

$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] を参照してください。コンテナーデプロイのテストについては、Testcontainers パターンを扱う [skill:dotnet-integration-testing] と、デプロイ済みコンテナーを対象とした E2E テストを扱う [skill:dotnet-playwright] を参照してください。

相互参照:Dockerfile とイメージのベストプラクティスについては [skill:dotnet-containers]、Kubernetes プローブで使用するヘルスチェックエンドポイントのパターンについては [skill:dotnet-observability] を参照してください。


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"

本番環境では、ソース管理に保存された通常の Kubernetes Secrets ではなく、外部 Secrets オペレーター(例:External Secrets Operator、Sealed Secrets)を使用してください。


Kubernetes プローブ

プローブを使用して、Kubernetes がアプリケーションの正常性を確認する方法を指定します。.NET アプリケーションで定義したヘルスチェックエンドポイントに対応します([skill:dotnet-observability] を参照)。

プローブの種類
プローブ目的エンドポイント失敗時のアクション
Startupアプリの初期化は完了していますか?/health/live待機を続けます(最大 failureThreshold * periodSeconds)
Livenessプロセスは正常ですか?/health/livePod を再起動します
Readinessプロセスはトラフィックを処理できますか?/health/readyService のエンドポイントから削除します
プローブ設定のガイドライン
# 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);
});

Kubernetes が SIGKILL を送信する前にアプリがシャットダウンするよう、ShutdownTimeout は terminationGracePeriodSeconds より小さい値に設定してください。


ローカル開発用 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 コンテナー

Dockerfile の代わりに dotnet publish /t:PublishContainer を使用するプロジェクトの場合:

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] を参照してください)
  • リソースの要求量と上限を設定してください。設定しないと、ポッドがほかのワークロードのリソースを枯渇させたり、予測不能なタイミングで OOM により強制終了されたりする可能性があります
  • root 以外のユーザーで実行してください。ポッドのセキュリティコンテキストで runAsNonRoot: true を設定し、chiseled イメージを使用してください([skill:dotnet-containers] を参照)
  • Docker Compose ではヘルスチェックとともに depends_on を使用してください。依存関係の準備が整う前にアプリが起動するのを防げます
  • シークレットをマニフェストに含めないでください。ソース管理に平文の値を含めず、外部シークレットオペレーターとともに Kubernetes Secrets を使用してください
  • ShutdownTimeout を terminationGracePeriodSeconds に合わせてください。Kubernetes が SIGKILL を送信する前に、アプリがクリーンアップを完了できるようにします

エージェントが注意すべき点

  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 はヘルスチェックを待たずに依存サービスをすぐに起動します。

参考資料

コメント