前言

當應用程式部署到 K8s 後,如何確保它穩定運行且高效利用資源?本篇將探討:

  • 資源管理:設定 CPU 和記憶體的請求與限制
  • 自動擴展:根據負載自動調整 Pod 數量
  • 調度策略:控制 Pod 在哪些節點上運行
  • 健康檢查:確保只有健康的 Pod 接收流量

這些是生產環境部署的必備知識。


資源管理:Requests 與 Limits

為什麼需要資源管理?

沒有資源限制的情況下:

  • 某個 Pod 可能消耗所有 CPU/記憶體
  • 導致其他 Pod 無法正常運行
  • 節點可能因 OOM(Out of Memory)崩潰
graph TD
    subgraph "沒有限制"
        A[Pod A] -->|佔用 80% CPU| N1[Node]
        B[Pod B] -->|飢餓| N1
        C[Pod C] -->|飢餓| N1
    end

    subgraph "有限制"
        D[Pod A: 限制 30%] --> N2[Node]
        E[Pod B: 限制 30%] --> N2
        F[Pod C: 限制 30%] --> N2
    end

Requests vs Limits

概念 說明 用途
Requests 最小需求 調度決策、資源預留
Limits 最大限制 防止資源濫用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
cpu: "250m" # 0.25 CPU
memory: "128Mi" # 128 MiB
limits:
cpu: "500m" # 0.5 CPU
memory: "256Mi" # 256 MiB

CPU 資源單位

表示法 說明
1 1 個 CPU 核心
500m 0.5 CPU(500 毫核心)
100m 0.1 CPU

CPU 超出 Limits:會被限流(throttle),不會被殺掉

記憶體資源單位

表示法 說明
128Mi 128 MiB(2^20 bytes)
1Gi 1 GiB
128M 128 MB(10^6 bytes)

記憶體超出 Limits:Pod 會被 OOMKilled(重啟)

QoS 類別

K8s 根據 requests/limits 設定將 Pod 分為三個服務品質類別:

graph TD
    G[Guaranteed<br/>最高優先級] --> B[Burstable<br/>中等優先級]
    B --> BE[BestEffort<br/>最低優先級]

    G -->|"requests = limits"| G
    B -->|"設定了部分資源"| B
    BE -->|"沒有設定資源"| BE
QoS 類別 條件 OOM 優先被殺順序
Guaranteed 所有容器都設定相同的 requests 和 limits 最後
Burstable 至少一個容器設定了 requests 中間
BestEffort 沒有設定任何資源請求或限制 最先
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Guaranteed 範例
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "500m" # 必須等於 requests
memory: "256Mi" # 必須等於 requests

# Burstable 範例
resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "500m" # 可以大於 requests
memory: "256Mi"

LimitRange:命名空間預設值

為避免忘記設定資源,可使用 LimitRange 設定預設值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- default: # 預設 limits
cpu: "500m"
memory: "256Mi"
defaultRequest: # 預設 requests
cpu: "100m"
memory: "64Mi"
max: # 最大允許值
cpu: "2"
memory: "1Gi"
min: # 最小允許值
cpu: "50m"
memory: "32Mi"
type: Container

ResourceQuota:命名空間配額

限制整個命名空間的資源使用量:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-quota
namespace: production
spec:
hard:
requests.cpu: "10" # 總 CPU 請求
requests.memory: "20Gi" # 總記憶體請求
limits.cpu: "20" # 總 CPU 限制
limits.memory: "40Gi" # 總記憶體限制
pods: "50" # 最多 50 個 Pod
persistentvolumeclaims: "10" # 最多 10 個 PVC

自動擴展

擴展類型

graph LR
    A[自動擴展] --> HPA[水平擴展 HPA<br/>增減 Pod 數量]
    A --> VPA[垂直擴展 VPA<br/>調整資源配置]
    A --> CA[叢集擴展 CA<br/>增減節點數量]

Horizontal Pod Autoscaler(HPA)

根據 CPU、記憶體或自定義指標自動調整 Pod 副本數。

graph LR
    M[Metrics Server] -->|收集| HPA[HPA Controller]
    HPA -->|調整副本數| DEP[Deployment]
    DEP --> P1[Pod 1]
    DEP --> P2[Pod 2]
    DEP --> P3[Pod 3]

安裝 Metrics Server

1
2
3
4
5
6
7
8
9
# Minikube
minikube addons enable metrics-server

# 或手動安裝
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# 驗證
kubectl top nodes
kubectl top pods

創建 HPA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app

minReplicas: 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 # 記憶體使用率目標 80%

behavior: # 擴縮容行為控制
scaleDown:
stabilizationWindowSeconds: 300 # 穩定視窗 5 分鐘
policies:
- type: Percent
value: 50 # 每次最多縮減 50%
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 4 # 每次最多增加 4 個
periodSeconds: 60

快速創建 HPA

1
2
3
4
5
6
7
8
# 簡單方式
kubectl autoscale deployment web-app \
--min=2 --max=10 \
--cpu-percent=70

# 查看 HPA 狀態
kubectl get hpa
kubectl describe hpa web-app-hpa

HPA 運作原理

1
期望副本數 = 當前副本數 × (當前指標值 / 目標指標值)

範例

  • 當前副本:3
  • 當前 CPU 使用率:90%
  • 目標 CPU 使用率:70%
  • 計算:3 × (90 / 70) = 3.86 → 4 個副本

Vertical Pod Autoscaler(VPA)

自動調整 Pod 的 CPU 和記憶體請求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app

updatePolicy:
updateMode: "Auto" # Off, Initial, Auto

resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: "100m"
memory: "50Mi"
maxAllowed:
cpu: "2"
memory: "2Gi"
controlledResources: ["cpu", "memory"]

注意:VPA 需要額外安裝,且與 HPA 一起使用時需謹慎配置。


Pod 調度策略

Node Selector

最簡單的節點選擇方式:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
nodeSelector:
gpu: "true" # 選擇帶有 gpu=true 標籤的節點
disk: "ssd"
containers:
- name: gpu-app
image: gpu-app:latest
1
2
3
# 為節點添加標籤
kubectl label nodes node-1 gpu=true
kubectl label nodes node-1 disk=ssd

Node Affinity

更強大的節點選擇規則:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: v1
kind: Pod
metadata:
name: affinity-pod
spec:
affinity:
nodeAffinity:
# 硬性要求(必須滿足)
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: zone
operator: In # In, NotIn, Exists, DoesNotExist, Gt, Lt
values:
- asia-east1-a
- asia-east1-b

# 軟性偏好(盡量滿足)
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd

containers:
- name: app
image: my-app:latest

Pod Affinity / Anti-Affinity

控制 Pod 之間的關係:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
template:
spec:
affinity:
# Pod Anti-Affinity:避免同一節點
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web-app
topologyKey: kubernetes.io/hostname

# Pod Affinity:靠近特定 Pod
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: cache
topologyKey: kubernetes.io/hostname

containers:
- name: web
image: web-app:latest

Taints 與 Tolerations

「污點」讓節點拒絕特定 Pod,「容忍」讓 Pod 克服污點。

1
2
3
4
5
# 為節點添加污點
kubectl taint nodes node-1 dedicated=gpu:NoSchedule

# 移除污點
kubectl taint nodes node-1 dedicated=gpu:NoSchedule-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule" # NoSchedule, PreferNoSchedule, NoExecute

containers:
- name: gpu-app
image: gpu-app:latest
Effect 說明
NoSchedule 新 Pod 不會調度到此節點
PreferNoSchedule 盡量避免調度(軟性)
NoExecute 不調度且驅逐現有 Pod

健康檢查:Probes

三種探針

graph LR
    SP[Startup Probe<br/>啟動檢查] --> LP[Liveness Probe<br/>存活檢查]
    LP --> RP[Readiness Probe<br/>就緒檢查]

    SP -->|失敗| KILL1[重啟容器]
    LP -->|失敗| KILL2[重啟容器]
    RP -->|失敗| REMOVE[從 Service 移除]
探針 用途 失敗行為
Startup 檢查應用是否已啟動 重啟容器
Liveness 檢查應用是否正常運行 重啟容器
Readiness 檢查應用是否準備好接收流量 從 Service 移除

探針類型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# HTTP GET
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome

# TCP Socket
livenessProbe:
tcpSocket:
port: 8080

# Exec 命令
livenessProbe:
exec:
command:
- cat
- /tmp/healthy

# gRPC(K8s 1.24+)
livenessProbe:
grpc:
port: 8080

完整探針配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: v1
kind: Pod
metadata:
name: probe-demo
spec:
containers:
- name: app
image: my-app:latest
ports:
- containerPort: 8080

# 啟動探針:給應用充足的啟動時間
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 最多嘗試 30 次
periodSeconds: 10 # 每隔 10 秒
# 總共最多 30 × 10 = 300 秒啟動時間

# 存活探針:應用運行後持續檢查
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0 # 啟動後立即檢查
periodSeconds: 10 # 每隔 10 秒
timeoutSeconds: 1 # 超時時間
successThreshold: 1 # 成功閾值
failureThreshold: 3 # 失敗 3 次後重啟

# 就緒探針:決定是否接收流量
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3 # 失敗 3 次後從 Service 移除

探針最佳實踐

  1. 總是設定 readinessProbe:避免流量發送到未就緒的 Pod
  2. 合理設定 liveness 閾值:避免因短暫問題頻繁重啟
  3. 慢啟動應用使用 startupProbe:替代增加 initialDelaySeconds
  4. 分離健康端點:healthz(健康)和 ready(就緒)應該是不同的邏輯

實戰範例:生產就緒配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
apiVersion: apps/v1
kind: Deployment
metadata:
name: production-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0

selector:
matchLabels:
app: production-app

template:
metadata:
labels:
app: production-app
spec:
# 調度策略
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: production-app
topologyKey: kubernetes.io/hostname

containers:
- name: app
image: my-app:v1.0.0
ports:
- containerPort: 8080

# 資源配置
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"

# 健康檢查
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10

livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
failureThreshold: 3

readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3

# 環境變數
env:
- name: NODE_ENV
value: "production"

# 安全上下文
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000

---
# HPA 配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: production-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: production-app
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60

---
# PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: production-app-pdb
spec:
minAvailable: 2 # 最少保持 2 個 Pod 運行
selector:
matchLabels:
app: production-app

本章重點回顧

資源管理

  1. Requests:調度決策、資源保證
  2. Limits:資源上限、防止濫用
  3. QoS:Guaranteed > Burstable > BestEffort
  4. LimitRange/ResourceQuota:命名空間級別控制

自動擴展

  1. HPA:基於 CPU/記憶體自動調整副本數
  2. VPA:自動調整資源請求
  3. 設定合理的 min/max:防止過度擴展

調度策略

  1. Node Selector:簡單標籤選擇
  2. Affinity:複雜的親和性規則
  3. Taints/Tolerations:節點排斥與容忍

健康檢查

  1. Startup:慢啟動應用
  2. Liveness:應用是否存活
  3. Readiness:是否可接收流量

下一篇預告

在最後一篇文章中,我們將進行 實戰部署

  • 完整微服務應用架構
  • Helm 簡介與應用
  • 多環境配置管理
  • 故障排除技巧

系列文章導覽

  • Part 1:入門篇 - 認識 K8s 與核心架構
  • Part 2:基礎篇 - Pod、Deployment 與 Service
  • Part 3:網路篇 - 服務發現與流量管理
  • Part 4:配置與存儲篇 - ConfigMap、Secret 與 Volume
  • Part 5:進階篇 - 資源管理與自動擴展(本篇)
  • Part 6:實戰篇 - 部署完整微服務應用

參考資源