第二十二章:容器化與 Kubernetes 部署
學習目標
- 掌握 Docker 多階段建置與映像最佳實務
- 能設計與部署 Kubernetes 應用
- 使用 Helm 管理複雜部署配置
- 實作健康檢查、自動伸縮與優雅關閉
先備知識
- 熟悉 Docker 基礎
- 了解 Kubernetes 核心概念(Pod、Service、Deployment)
- 已完成第三章(開發流程)
正文
一、Docker 多階段建置
最佳實務 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
docker build -t myregistry/myapp:1.0.0 -t myregistry/myapp:latest .推送至 Registry
bash
# bash
docker login myregistry.azurecr.io
docker push myregistry/myapp:1.0.0
docker push myregistry/myapp:latest本地測試
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 - 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: 5Service 定義
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: 80Secret 管理
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
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
helm create myapp-chartChart 結構
myapp-chart/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml # Horizontal Pod Autoscaler
│ └── _helpers.tpl
└── values-prod.yamlvalues.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 - 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
# 開發環境
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 - 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 - 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 - 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 - 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 - 步驟 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 狀態與應用日誌
實作練習
- 編寫 ABP 應用的多階段 Dockerfile
- 建立 Kubernetes Deployment、Service、Secret 配置
- 設計 Helm Chart 支援開發/生產環境切換
- 測試健康檢查與優雅關閉
習題(至少 6 題)
概念題(易)
- 為何 Docker 多階段建置能減少映像大小?(難度:易)
- 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 數量
參考資料
- ABP Deployment:https://docs.abp.io/en/abp/latest/Deployment (來源:content7)
- Docker 官方:https://docs.docker.com/
- Kubernetes 官方:https://kubernetes.io
- Helm 官方:https://helm.sh/
習題解答:content/solutions/ch22-solutions.md