dotnet-container-deployment について
Kubernetes、Docker Compose、イメージ公開のワークフローを扱う、.NET コンテナー展開のガイダンススキルです。クラスターやローカル開発環境に .NET アプリを展開する開発者を対象とします。
主な機能
- 01Kubernetes Deployment・Service マニフェスト
- 02起動・ライブネス・レディネスプローブ
- 03Docker Compose 開発環境
- 04設定とシークレットの扱い
- 05コンテナーイメージのビルドとプッシュ
- 06グレースフルシャットダウンの指針
活用シーン
- 01.NET API の Kubernetes 展開
- 02依存サービスのローカル実行
- 03ヘルスチェックと終了処理の設定
- 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/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);
});
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
イメージのタグ付け戦略
| タグパターン | 例 | 用途 |
|---|---|---|
latest | myapi:latest | 開発環境のみ。本番環境では決して使用しないでください |
| Semver | myapi:1.2.3 | リリースバージョン。不変です |
| Major.Minor | myapi:1.2 | パッチ更新用の可変タグ |
| SHA | myapi:sha-abc1234 | コミットごとに一意。追跡可能性を確保します |
| Branch | myapi:main | CI ビルド。ブランチの最新イメージ |
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 を送信する前に、アプリがクリーンアップを完了できるようにします
エージェントが注意すべき点
- スタートアッププローブを省略しないでください。設定しないと、初期化中に稼働状態プローブが実行され、起動の遅いアプリが再起動される可能性があります。起動猶予時間は
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 はヘルスチェックを待たずに依存サービスをすぐに起動します。