Kubernetes 模式
用於可靠部署、管理和除錯工作負載的生產級 Kubernetes 模式。
何時啟動
- 撰寫 Kubernetes 清單(Deployment、Service、Ingress、Job)
- 設定資源請求/限制、存活/就緒探針
- 設定 RBAC、命名空間或 ServiceAccount
- 在 Kubernetes 中管理設定和機密
- 除錯 CrashLoopBackOff、OOMKilled、擱置中的 Pod 或映像拉取錯誤
- 設定 HPA(水平 Pod 自動伸縮器)或 PodDisruptionBudget
- 審查 Kubernetes YAML 的安全性與正確性
使用時機
與上方何時啟動相同。此別名符合 repo 技能格式慣例。在撰寫、審查或除錯 Kubernetes YAML 與工作負載時,請使用此技能。
運作方式
此技能提供可複製貼上、生產級的 YAML 模式和按任務組織的 kubectl 除錯指令:
- 部署模板 — 一個完整配置的生產級
Deployment,包含安全上下文、滾動更新策略、全部三種探針類型、資源限制,以及從 ConfigMap/Secret 注入環境變數。 - 探針 — 啟動探針 vs 存活探針 vs 就緒探針的決策表,附帶正確的
failureThreshold × periodSeconds計算。 - Service 與 Ingress — ClusterIP、LoadBalancer 和具有 cert-manager 註解的 TLS Ingress 模式。
- ConfigMap 與 Secret —
envFrom、檔案掛載和外部機密指引。 - 資源管理 — 依工作負載類型(Web API、JVM、工作者、Sidecar)的請求和限制經驗法則。
- RBAC — 最小權限 ServiceAccount → Role → RoleBinding 鏈。
- HPA 與 PDB — 自動伸縮和節點排水安全配置。
- Job 與 CronJob — 一次性與排程工作負載模式,包含正確的
restartPolicy。 - kubectl 速查表 — 日誌、執行、回滾、連接埠轉發、試執行和常見錯誤診斷指令。
- 反模式與檢查清單 — 不該做的事,以及安全/可靠性/可觀測性檢查清單。
範例
請參閱以下各節,以取得完整、可執行的範例。快速參考:
| 任務 | 跳轉至 |
|---|---|
| 完整的生產級 Deployment YAML | 核心工作負載模式 |
| 探針配置 | 探針 |
| 最小權限 RBAC 設置 | RBAC |
| 除錯 CrashLoopBackOff | kubectl 除錯速查表 |
| 自動伸縮 | HPA |
核心工作負載模式
Deployment — 生產模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-namespace
labels:
app: my-app
version: "1.0.0"
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新期間允許 1 個額外 Pod
maxUnavailable: 0 # 絕不低於預期數量
template:
metadata:
labels:
app: my-app
version: "1.0.0"
spec:
# Pod 層級的安全上下文
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
# 優雅關閉
terminationGracePeriodSeconds: 30
containers:
- name: my-app
image: ghcr.io/org/my-app:1.0.0 # 絕不使用 :latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
protocol: TCP
# 資源請求與限制皆為必填
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
# 容器安全上下文
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# 探針(請參閱下方探針章節)
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
# 從 ConfigMap 和 Secret 注入環境變數
envFrom:
- configMapRef:
name: my-app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets
key: db-password
# 當 readOnlyRootFilesystem: true 時的可寫入 tmp 目錄
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
探針 — 存活、就緒、啟動
理解每種探針的使用時機至關重要:
| 探針 | 失敗動作 | 使用時機 |
|---|---|---|
startupProbe | 若啟動過慢則終止容器 | 啟動緩慢的應用程式(JVM、Python) |
livenessProbe | 重新啟動容器 | 死結 / 處理程序掛起檢測 |
readinessProbe | 從 Service 端點中移除 | 暫時不可用(資料庫重新連線) |
# 正確模式:startupProbe 涵蓋緩慢啟動,
# 然後 liveness/readiness 接手
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 30 * 5s = 150s 最大啟動時間
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 30
failureThreshold: 3 # 3 * 30s = 90s 後重新啟動
readinessProbe:
httpGet:
path: /ready # 獨立端點:檢查資料庫、快取等。
port: 8080
periodSeconds: 10
failureThreshold: 2
# 錯誤:使用 initialDelaySeconds 而沒有 startupProbe
# 如果應用程式需要 60 秒啟動,應改用 startupProbe
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # 不佳:任意等待,競態條件
Service 和 Ingress
Service 類型
# ClusterIP(預設)— 僅供內部使用
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: my-namespace
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
# LoadBalancer — 外部流量(雲端供應商)
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
具有 TLS 的 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: my-namespace
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: my-app-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
ConfigMap 和 Secret
ConfigMap — 非機敏配置
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: my-namespace
data:
LOG_LEVEL: "info"
APP_ENV: "production"
MAX_CONNECTIONS: "100"
# 對於複雜配置,掛載為檔案
app.yaml: |
server:
port: 8080
timeout: 30s
# 將 ConfigMap 掛載為檔案
volumes:
- name: config
configMap:
name: my-app-config
items:
- key: app.yaml
path: app.yaml
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
Secret — 機敏資料
# 從字面值建立 Secret(CLI,然後儲存在 Vault/SOPS)
kubectl create secret generic my-app-secrets \
--from-literal=db-password='s3cr3t' \
--namespace=my-namespace \
--dry-run=client -o yaml | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
namespace: my-namespace
type: Opaque
# 值經過 base64 編碼(未加密 — 生產環境請使用 Sealed Secrets 或 ESO)
data:
db-password: czNjcjN0 # 's3cr3t' 的 base64
重要事項: 原始的 Kubernetes Secret 僅經過 base64 編碼,除非您的叢集已配置加密,否則不會在靜態時加密。請使用 Sealed Secrets 或 External Secrets Operator 進行生產環境部署。
資源請求和限制
resources:
requests: # 調度器使用此值來放置 Pod
cpu: "100m" # 100 毫核 = 0.1 CPU
memory: "128Mi"
limits: # 超過此值時容器會被終止或限制
cpu: "500m"
memory: "256Mi"
經驗法則:
| 工作負載類型 | CPU 請求 | 記憶體請求 | 備註 |
|---|---|---|---|
| Web API | 100–250m | 128–256Mi | 設定限制為請求的 2-4 倍 |
| Worker/消費者 | 250–500m | 256–512Mi | 為可預測性,將記憶體限制設為等於請求 |
| JVM 應用程式 | 500m–1 | 512Mi–2Gi | 在 -Xmx 之上預留空間以應付 JVM 開銷 |
| Sidecar | 10–50m | 32–64Mi | 保持最小 |
# 錯誤:沒有請求或限制 — 不可預測的調度,OOM 驅逐
containers:
- name: app
image: myapp:latest
# 缺少 resources: {} — 這在生產環境中是危險的
# 錯誤:有限制但沒有請求 — 請求會預設為限制值,過度保留容量
resources:
limits:
cpu: "2"
memory: "1Gi"
# 缺少 requests — 將預設為限制值
RBAC — 角色與 ServiceAccount
最小權限原則
兩種模式,取決於應用程式是否呼叫 Kubernetes API:
模式 A — 應用程式不需要 Kubernetes API(大多數應用程式)
在 ServiceAccount 上停用 Token 自動掛載。不需要 Role/RoleBinding。
# 停用 Token 的 ServiceAccount — 最安全的預設值
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: false # 不會將 K8s API Token 注入 Pod
# 在 Deployment 中引用 — 沒有 Token,沒有 API 存取
spec:
template:
spec:
serviceAccountName: my-app-sa
automountServiceAccountToken: false # 雙重確認:也在 Pod 層級設定
模式 B — 應用程式確實需要 Kubernetes API(Operator、控制器、配置監視器)
啟用 Token,並僅授予實際需要的權限。
# 1. ServiceAccount — 為此 SA 啟用 Token
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: true # 需要 Token:應用程式呼叫 K8s API
# 2. Role — 僅授予應用程式需要的權限(命名空間範圍)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: my-app-role
namespace: my-namespace
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"] # 唯讀,特定資源
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["my-app-secrets"] # 按名稱限制為特定 Secret
verbs: ["get"]
# 3. 將 Role 綁定到 ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-rolebinding
namespace: my-namespace
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: my-namespace
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: my-app-role
# 4. 在 Deployment 中引用 SA
spec:
template:
spec:
serviceAccountName: my-app-sa
# automountServiceAccountToken 從 SA 預設為 true — 會注入 Token
水平 Pod 自動伸縮器 (HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: my-namespace
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2 # 為高可用性,至少 2 個副本
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # 當平均 CPU > 70% 時擴展
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
HPA 要求所有容器都設定
resources.requests— 它將使用率計算為目前值 / 請求值。
PodDisruptionBudget (PDB)
防止在節點排水或滾動更新期間有過多 Pod 下線:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
namespace: my-namespace
spec:
minAvailable: 2 # 或使用 maxUnavailable: 1
selector:
matchLabels:
app: my-app
命名空間與多租戶
# 使用資源配額創建命名空間
kubectl create namespace my-namespace
# 套用 ResourceQuota 以限制命名空間消耗
kubectl apply -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-namespace-quota
namespace: my-namespace
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "8"
limits.memory: 8Gi
pods: "20"
EOF
Job 和 CronJob
# 一次性 Job(資料庫遷移、資料處理)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: my-namespace
spec:
backoffLimit: 3 # 失敗時最多重試 3 次
ttlSecondsAfterFinished: 3600 # 1 小時後自動刪除
template:
spec:
restartPolicy: OnFailure # Job 使用 Never(而非 Always)
containers:
- name: migrate
image: ghcr.io/org/my-app:1.0.0
command: ["python", "manage.py", "migrate"]
resources:
requests:
cpu: "250m"
memory: "256Mi"
# 排程 Job(定期任務)
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup
namespace: my-namespace
spec:
schedule: "0 3 * * *" # 每天凌晨 3 點
timeZone: "America/New_York" # 明確設定時區
concurrencyPolicy: Forbid # 不允許多個執行個體同時執行
successfulJobsHistoryLimit: 3 # 保留最近 3 次成功執行
failedJobsHistoryLimit: 1 # 保留最近 1 次失敗
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: ghcr.io/org/cleanup:latest # 對於 CronJob 使用固定標籤
command: ["cleanup", "--retention=30d"]
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
kubectl 除錯速查表
# 檢視 Pod 及其狀態
kubectl get pods -n my-namespace -o wide
# 取得 Pod 詳細資訊(包括事件)
kubectl describe pod <pod-name> -n my-namespace
# 檢視即時日誌
kubectl logs -f <pod-name> -n my-namespace
# 檢視前一個容器執行個體的日誌(崩潰後)
kubectl logs -p <pod-name> -n my-namespace
# 使用標籤選擇器串流多個 Pod 的日誌
kubectl logs -l app=my-app -n my-namespace --tail=100 -f
# 在 Pod 中執行互動式 Shell
kubectl exec -it <pod-name> -n my-namespace -- /bin/sh
# 試執行 YAML 套用(不實際套用)
kubectl apply -f manifest.yaml --dry-run=client -o yaml
# 驗證 YAML 語法
kubectl apply -f manifest.yaml --dry-run=server
# 回滾到先前的修訂版本
kubectl rollout undo deployment/my-app -n my-namespace
# 回滾到特定修訂版本
kubectl rollout undo deployment/my-app -n my-namespace --to-revision=2
# 檢視部署歷史
kubectl rollout history deployment/my-app -n my-namespace
# 將本地連接埠轉發到 Pod
kubectl port-forward pod/<pod-name> 8080:8080 -n my-namespace
# 將本地連接埠轉發到服務
kubectl port-forward service/my-app 8080:80 -n my-namespace
# 複製檔案至/從 Pod
kubectl cp /local/file <pod-name>:/remote/path -n my-namespace
kubectl cp <pod-name>:/remote/path /local/file -n my-namespace
# 檢視頂層資源使用率
kubectl top pods -n my-namespace
kubectl top nodes
# 取得所有類型的事件
kubectl get events -n my-namespace --sort-by='.lastTimestamp'
# 檢視 API 資源
kubectl api-resources
常見錯誤診斷
# CrashLoopBackOff — 檢視日誌與事件
kubectl logs <pod-name> -n my-namespace
kubectl logs -p <pod-name> -n my-namespace # 取得崩潰前的最後一次輸出
kubectl describe pod <pod-name> -n my-namespace | grep -A 10 "State:"
# OOMKilled — 資源限制太低
kubectl describe pod <pod-name> -n my-namespace | grep "OOMKilled"
kubectl top pod <pod-name> -n my-namespace # 實際使用率
# 映像拉取錯誤 — 檢查認證
kubectl describe pod <pod-name> -n my-namespace | grep -A 5 "Events:"
# 解決方案:檢查 ImagePullSecrets 或映像標籤
# 擱置中的 Pod — 資源不足或 PVC 問題
kubectl describe pod <pod-name> -n my-namespace | grep -A 10 "Conditions:"
kubectl get events -n my-namespace --sort-by='.lastTimestamp' | grep "FailedScheduling"
# 原因:資源不足、PVC 未綁定、節點親和性、汙點/容忍度
# 容器正在初始化 — InitContainer 尚未完成
kubectl logs <pod-name> -n my-namespace -c <init-container-name>
反模式與檢查清單
不該做的事
| 反模式 | 為什麼是錯的 | 正確做法 |
|---|---|---|
使用 :latest 映像標籤 | 不可重現,難以回滾 | 使用語義化版本或 Git SHA |
| 省略資源請求/限制 | 不可預測的調度,OOM 驅逐 | 始終設定請求(至少) |
| 沒有存活探針 | 死結的容器會一直運行 | 設定有意義的存活探針 |
initialDelaySeconds 沒有 startupProbe | 任意等待,競態條件 | 使用 startupProbe |
在 Pod 層級使用 ClusterRole | 安全漏洞:權限過大 | 使用最小權限的 Role |
| 將 Secret 提交到版本控制 | 憑證外洩 | 使用 Sealed Secrets / Vault / SOPS |
| ServiceAccount 的 Token 自動掛載 | 不必要的 API 存取 | 不需要 API 時設定為 false |
安全、可靠性與可觀測性檢查清單
安全
- 容器使用非 root 使用者執行
-
allowPrivilegeEscalation設為 false - 根檔案系統設為唯讀
- 捨棄所有功能
- 設定 resourceNames 以限制 Secret 存取
- 映像來自私有註冊表(無公開映像)
- 映像簽署已啟用(cosign)
可靠性
- 設定資源請求與限制
- 設定 startupProbe + livenessProbe + readinessProbe
- 設定 PodDisruptionBudget
- 對於 HA,設定至少 2 個副本
- 啟用 HPA(透過指標)
- 設定終止優雅期限
- 設定滾動更新策略(maxSurge, maxUnavailable)
- 設定節點親和性或 topologySpreadConstraints
可觀測性
- 應用程式日誌輸出到 stdout/stderr
- 指標端點(/metrics)已啟用
- 設定 readiness 端點以檢查資料庫/快取
- 設定 liveness 端點(不同於 readiness)
- 啟用 Pod 資源的 metadata 注釋(Prometheus 自動發現)
- 啟用審計日誌
ServiceAccount 的進階考量
自動掛載 Token 的行為
Kubernetes 1.24+ 變更了行為:
| Kubernetes 版本 | SA 上 automountServiceAccountToken 的預設值 | 行為 |
|---|---|---|
| < 1.24 | true | 總是自動掛載 Token |
| 1.24+ | true(行為不變) | 但是,Token 是綁定到 Pod 生命週期且附帶時間限制的 |
# 對祕密進行最小權限存取的最佳模式
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
namespace: my-namespace
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: # 僅限這些名稱
- "my-app-secrets"
verbs: ["get"]
進階拓撲與排程
Pod Topology Spread Constraints
# 確保 Pod 均勻分布在區域/分區中
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: my-app
節點親和性
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- compute
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node-type
operator: In
values:
- spot
從 ConfigMap 掛載檔案
# 將整個 ConfigMap 掛載為目錄
volumes:
- name: config-volume
configMap:
name: my-app-config
defaultMode: 0444
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
# 僅選取特定金鑰
volumes:
- name: config-volume
configMap:
name: my-app-config
items:
- key: app.yaml
path: myapp.yaml
volumeMounts:
- name: config-volume
mountPath: /etc/myapp
readOnly: true
常見陷阱
探針:存活 vs 就緒的混淆
一個常見的錯誤是對存活和就緒使用相同的端點。存活探針不應仰賴外部相依服務(資料庫、快取),否則資料庫中斷會導致容器重新啟動,而非僅是將流量路由走。
資源:僅設限制而未設請求
# 錯誤
resources:
limits:
cpu: 500m
memory: 256Mi
# requests 會預設為 limits,導致保留過多容量
權限:不必要的 ClusterRole
當 Role 就能滿足時,請勿使用 ClusterRole。使用 Role 可將權限限制在特定命名空間。
映像:latest 標籤
latest 標籤會在每次部署時變更,導致無法重現的部署和困難的回滾。始終使用語義化版本或特定 SHA 摘要。
設定:在映像中硬編碼
請勿在建置映像時將設定值燒錄進去。使用 ConfigMap 和 Secret 注入,以便在不重新建置的情況下管理配置。
支援的 Kubernetes 版本
此技能涵蓋 Kubernetes 1.24+ 的功能。
| 功能 | 最低建議 API 版本 |
|---|---|
| HPA | autoscaling/v2 (1.23+) |
| PodDisruptionBudget | policy/v1 (1.21+) |
| Ingress | networking.k8s.io/v1 (1.19+) |
| RBAC | rbac.authorization.k8s.io/v1 (1.8+) |
| CronJob | batch/v1 (1.21+) |
生產就緒的 ConfigMap 與 Secret 注入
# 將 ConfigMap 中所有金鑰作為環境變數注入
envFrom:
- configMapRef:
name: my-app-config
# 將 Secret 中所有金鑰作為環境變數注入
envFrom:
- secretRef:
name: my-app-secrets
# 從 ConfigMap 注入個別金鑰
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: my-app-config
key: LOG_LEVEL
# 從 Secret 注入個別金鑰
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets
key: db-password
外部 Secret 整合
對於真正的生產環境加密,使用 External Secrets Operator:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-app-external-secret
namespace: my-namespace
spec:
refreshInterval: "1h"
secretStoreRef:
name: aws-secretsmanager
kind: ClusterSecretStore
target:
name: my-app-secrets
creationPolicy: Owner
data:
- secretKey: db-password
remoteRef:
key: /production/my-app/db-password