Skip to content

第二十二章:容器化與 Kubernetes 部署

學習目標

  • 掌握 Docker 多階段建置與映像最佳實務
  • 能設計與部署 Kubernetes 應用
  • 使用 Helm 管理複雜部署配置
  • 實作健康檢查、自動伸縮與優雅關閉

先備知識

  • 熟悉 Docker 基礎
  • 了解 Kubernetes 核心概念(Pod、Service、Deployment)
  • 已完成第三章(開發流程)

正文

一、Docker 多階段建置

最佳實務 Dockerfile

dockerfile()

dockerfile
# dockerfile
# Stage 1: 建置
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# 複製 .csproj 檔並還原
COPY ["src/MyApp.Web/MyApp.Web.csproj", "src/MyApp.Web/"]
COPY ["src/MyApp.Application/MyApp.Application.csproj", "src/MyApp.Application/"]
COPY ["src/MyApp.Domain/MyApp.Domain.csproj", "src/MyApp.Domain/"]
COPY ["src/MyApp.EntityFrameworkCore/MyApp.EntityFrameworkCore.csproj", "src/MyApp.EntityFrameworkCore/"]

RUN dotnet restore "src/MyApp.Web/MyApp.Web.csproj"

# 複製完整程式碼並建置
COPY . .
RUN dotnet build "src/MyApp.Web/MyApp.Web.csproj" -c Release -o /app/build

# Stage 2: 發佈
FROM build AS publish
RUN dotnet publish "src/MyApp.Web/MyApp.Web.csproj" -c Release -o /app/publish

# Stage 3: 執行
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
EXPOSE 80 443

# 建立非 root 使用者提升安全性
RUN useradd -m -s /bin/bash appuser
USER appuser

COPY --from=publish /app/publish .

# 健康檢查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD curl -f http://localhost/health || exit 1

ENTRYPOINT ["dotnet", "MyApp.Web.dll"]

二、建置與推送映像

建置映像

bash()

bash
# bash
docker build -t myregistry/myapp:1.0.0 -t myregistry/myapp:latest .

推送至 Registry

bash()

bash
# bash
docker login myregistry.azurecr.io
docker push myregistry/myapp:1.0.0
docker push myregistry/myapp:latest

本地測試

bash()

bash
# bash
docker run -p 8080:80 \
  -e ASPNETCORE_URLS=http://+:80 \
  -e ConnectionStrings__Default="Server=host.docker.internal;Database=BookStore;User Id=sa;Password=YourPass" \
  myregistry/myapp:1.0.0

三、Kubernetes 部署

基本 Deployment

yaml()

yaml
# yaml - k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myregistry/myapp:1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
          env:
            - name: ASPNETCORE_URLS
              value: "http://+:80"
            - name: ConnectionStrings__Default
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: connection-string
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health/live
              port: 80
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 5

Service 定義

yaml()

yaml
# yaml - k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  type: LoadBalancer
  selector:
    app: myapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Secret 管理

yaml()

yaml
# yaml - k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData:
  connection-string: "Server=db-server;Database=BookStore;User Id=sa;Password=YourSecurePass"

部署至 Kubernetes

bash()

bash
# bash
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml

# 驗證部署
kubectl get deployments
kubectl get pods
kubectl get svc

四、Helm Chart

建立 Helm Chart

bash()

bash
# bash
helm create myapp-chart

Chart 結構

text()

myapp-chart/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml         # Horizontal Pod Autoscaler
│   └── _helpers.tpl
└── values-prod.yaml

values.yaml 範例

yaml()

yaml
# yaml - myapp-chart/values.yaml
replicaCount: 3

image:
  repository: myregistry/myapp
  tag: "1.0.0"
  pullPolicy: IfNotPresent

service:
  type: LoadBalancer
  port: 80
  targetPort: 80

resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "500m"

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

environment:
  ASPNETCORE_ENVIRONMENT: "Production"

database:
  host: "db-server"
  name: "BookStore"

deployment.yaml 模板

yaml()

yaml
# yaml - myapp-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: { { include "myapp.fullname" . } }
spec:
  replicas: { { .Values.replicaCount } }
  template:
    spec:
      containers:
        - name: { { .Chart.Name } }
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: { { .Values.service.targetPort } }
          env:
            - name: ConnectionStrings__Default
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: connection-string
          resources: { { - toYaml .Values.resources | nindent 12 } }

部署 Helm Chart

bash()

bash
# bash
# 開發環境
helm install myapp ./myapp-chart -f myapp-chart/values.yaml

# 生產環境
helm install myapp ./myapp-chart -f myapp-chart/values-prod.yaml

# 升級
helm upgrade myapp ./myapp-chart --set image.tag=2.0.0

# 回滾
helm rollback myapp 1

五、健康檢查與優雅關閉

實作健康檢查端點

csharp()

csharp
// csharp - Program.cs
builder.Services.AddHealthChecks()
    .AddCheck("liveness", () => HealthCheckResult.Healthy())
    .AddDbContextCheck<MyDbContext>("database")
    .AddCheck("redis", RedisHealthCheck, tags: new[] { "cache" });

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = reg => reg.Tags.Contains("live")
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = reg => reg.Tags.Contains("ready")
});

優雅關閉

csharp()

csharp
// csharp - Program.cs
var host = app.Build();

// 接聽 SIGTERM 信號(K8s 發送的關閉信號)
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();

lifetime.ApplicationStopping.Register(async () =>
{
    Console.WriteLine("應用開始關閉...");

    // 完成現有請求(30 秒超時)
    await Task.Delay(TimeSpan.FromSeconds(30));

    Console.WriteLine("應用已關閉");
});

await host.RunAsync();

Kubernetes 優雅終止配置

yaml()

yaml
# yaml - k8s/deployment.yaml
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 40 # 給予 Pod 40 秒完成請求
      containers:
        - name: myapp
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 15"] # 延遲 15 秒再開始關閉

六、自動伸縮

Horizontal Pod Autoscaler(HPA)

yaml()

yaml
# yaml - k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 80
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 50
          periodSeconds: 60

七、藍綠部署(Blue-Green Deployment)

藍綠部署策略

bash()

bash
# bash - 步驟 1:啟動綠色版本(新版)
kubectl set image deployment/myapp-blue myapp=myregistry/myapp:2.0.0

# 步驟 2:切換 Service 至綠色版本
kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"green"}}}'

# 步驟 3:驗證新版本穩定性
sleep 60
curl http://myapp-svc/health

# 步驟 4:成功則保留,失敗則回滾
# 回滾至藍色版本
kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"blue"}}}'

八、實務最佳實務

  • 使用語義版本與標籤規範映像版本
  • 定義合理的資源請求與限制,避免過度配置
  • 在 CI/CD 中自動建置、測試、推送映像
  • 使用私有 Registry 與映像掃描工具檢測漏洞
  • 定期更新基礎映像與依賴
  • 監控 Pod 狀態與應用日誌

實作練習

  1. 編寫 ABP 應用的多階段 Dockerfile
  2. 建立 Kubernetes Deployment、Service、Secret 配置
  3. 設計 Helm Chart 支援開發/生產環境切換
  4. 測試健康檢查與優雅關閉

習題(至少 6 題)

概念題(易)

  1. 為何 Docker 多階段建置能減少映像大小?(難度:易)
  2. Kubernetes Deployment 與 Service 的角色各為何?(難度:中)

計算 / 練習題(中) 3. 設計一個高可用 Kubernetes 應用配置:最小 3 副本、自動伸縮、藍綠部署。(難度:中) 4. 計算不同資源限制下應用能支援的最大並發用戶數。(難度:中)

實作 / 編碼題(較難) 5. 編寫完整的容器化配置(Dockerfile、K8s YAML、Helm Chart)與 CI 流程。(難度:較難) 6. 實作自動伸縮、藍綠部署與故障自動恢復的 Kubernetes 配置。(難度:較難)

術語表

  • Container(容器):輕量級隔離環境,包含應用與依賴
  • Pod(豆莢):Kubernetes 最小單位,包含一個或多個容器
  • Deployment(部署):宣告式管理 Pod 與副本的 Kubernetes 物件
  • HPA(自動伸縮):根據 CPU/Memory 動態調整 Pod 數量

參考資料

習題解答:content/solutions/ch22-solutions.md

Released under the MIT License.