今天要進入實戰環節啦
前面如果想把 API 從 1 個 Pod 增加到 3 個,我們會手動執行:
kubectl scale deployment/api \
--replicas=3 \
-n cka-lab
這叫做:
Manual Scaling
但 Production 的流量並不固定。
可能:
凌晨 2 點:100 requests
早上 9 點:10,000 requests
活動開始:100,000 requests
我們不可能安排一個 Engineer 一直盯著 Grafana,看到 CPU 升高就:
kubectl scale ...
流量下降之後再手動 Scale 回去。
因此 Kubernetes 提供:
Horizontal Pod Autoscaler
簡稱:
HPA
它的工作就是:
根據目前 Workload 的負載,自動增加或減少 Pod 數量。
Horizontal Scaling 是:
增加 Pod 數量
例如原本:
API Pod
流量增加後變成:
API Pod
API Pod
API Pod
也就是:
1 Pod
↓
3 Pods
↓
5 Pods
每個 Pod 本身並沒有變大,只是增加更多 Pod 一起處理 Request。
Vertical Scaling 則比較像:
原本
CPU:100m
Memory:128Mi
↓
變成
CPU:500m
Memory:512Mi
所以可以先記:
Horizontal = Pod 數量變多
Vertical = 單一 Pod 資源變大
今天只處理 HPA。
假設我們希望設定:
CPU 超過目標
→ 增加 Pod
那 Kubernetes 首先必須知道:
每個 Pod 現在到底用了多少 CPU?
這時你可能直覺執行:
kubectl top pods \
-n cka-lab
但很可能看到:
error: Metrics API not available
原因是:
Kubernetes Cluster 不一定預設就有 Resource Metrics。
HPA 本身並不是拿著一個 CPU 監控器去讀 Pod。
常見架構其實是:
Kubelet
│
│ CPU / Memory 資料
▼
Metrics Server
│
▼
metrics.k8s.io
│
├── kubectl top
│
└── HPA
所以今天第一步不是建立 HPA。
而是先讓 Cluster 擁有:
Metrics Server
Kubernetes 官方文件也說明,CPU、Memory 這類 Resource Metrics 通常由另外安裝的 Metrics Server 提供。
執行:
kubectl get deployment \
metrics-server \
-n kube-system
如果看到:
Error from server (NotFound)
代表目前沒有 Metrics Server。
也可以直接試:
kubectl top nodes
如果看到:
Metrics API not available
基本上也代表 Resource Metrics 還沒準備好。
不要把某個固定版本號直接寫死,例如:
v0.x.x
因為版本會一直更新。
官方提供一個:
releases/latest
可以直接取得目前最新正式版本。
執行:
kubectl apply -f \
https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
這就是 Metrics Server 官方目前提供的安裝方式之一。
執行之後,你會看到 Kubernetes 建立一系列 Resource:

不用每一個現在都背起來。
目前最重要的是知道:
Metrics Server 本身
↓
是一個跑在 Kubernetes 裡面的 Deployment
查看:
kubectl get deployment \
metrics-server \
-n kube-system
會看到:
NAME READY UP-TO-DATE AVAILABLE
metrics-server 0/1 1 1
也可以看 Pod:
kubectl get pods \
-n kube-system
找到:
metrics-server-xxxxxxxxxx-xxxxx
因為剛剛的
NAME READY UP-TO-DATE AVAILABLE
metrics-server 0/1 1 1
他並沒有成功 READY 好。
現在試:
kubectl top nodes
會看到顯示
error: Metrics API not available
因為我們現在用的是:
kind
也就是:
Kubernetes IN Docker
本機測試環境可能遇到 Kubelet Certificate 驗證問題。
因此你可能還是看到:
Metrics API not available
或者 Metrics Server Pod 看起來 Running,但抓不到 Node Metrics。
這時不要直接開始亂改設定。
先看 Logs。
執行:
kubectl logs \
deployment/metrics-server \
-n kube-system
如果 Logs 裡出現類似:
x509: certificate signed by unknown authority

或者:
cannot validate certificate
就代表可能真的遇到:
Metrics Server
↓ HTTPS
Kubelet
↓
TLS Certificate 驗證失敗
Metrics Server 必須去每台 Node 的 Kubelet 抓 CPU / Memory Metrics。
正常 Production 環境應該要:
正確驗證 Kubelet Certificate
但是 kind 這類本機 Lab 環境,有時 Certificate 配置並不符合 Metrics Server 預期。
如果你確認 Logs 真的是 Certificate 問題,可以在:
純學習用 kind Cluster
加入:
--kubelet-insecure-tls
最簡單的方法是 Patch Metrics Server Deployment:
kubectl patch deployment \
metrics-server \
-n kube-system \
--type='json' \
-p='[
{
"op": "add",
"path": "/spec/template/spec/containers/0/args/-",
"value": "--kubelet-insecure-tls"
}
]'
這個 Patch 的意思是:
找到 metrics-server Deployment
↓
找到第一個 Container
↓
找到它的 args
↓
多加入一個參數
↓
--kubelet-insecure-tls
加入之後 Deployment Template 發生變化,所以 Kubernetes 會重新建立 Metrics Server Pod。
可以看:
kubectl get pods \
-n kube-system \
-w
等新的:
metrics-server-xxxxx
變成:
1/1 Running

但一定要知道:
--kubelet-insecure-tls
真正意思是:
Metrics Server 連線到 Kubelet 時,不驗證 Kubelet 提供的 Certificate CA。
官方也明確把這個選項標示為:
For testing purposes only
因此適合我們現在這種本機 Lab,實務上不應該養成「Certificate 有問題就關 TLS 驗證」的習慣!!
稍微等一下後,再執行:
kubectl top nodes
可以看到:
再看:
kubectl top pods \
-n cka-lab
可以看到:

注意:
4m
這個 m 是:
millicpu
所以:
1000m CPU = 1 CPU Core
100m CPU = 0.1 CPU Core
10m CPU = 0.01 CPU Core
到這裡代表:
Kubelet
↓
Metrics Server
↓
Metrics API
這條資料路徑已經成功。
也可以進一步確認 Metrics API:
kubectl get apiservice \
v1beta1.metrics.k8s.io

正常會看到:
AVAILABLE
True
這代表 Kubernetes API Server 已經可以透過:
metrics.k8s.io
取得 Resource Metrics。
假設我們的 API Deployment 原本有:
resources:
requests:
cpu: 100m
以前學 requests 時,我們主要講:
Scheduler 會根據 Request 判斷 Node 有沒有足夠資源可以放這個 Pod。
但今天你會發現 Request 還有第二個重要用途:
HPA CPU Utilization 的計算基準
假設:
CPU Request
=
100m
現在 Metrics Server 量到:
CPU Usage
=
50m
HPA 算:
50m ÷ 100m
=
50%
所以 HPA 裡面看到的:
CPU 50%
不是:
這台 Node 的 CPU 用掉 50%。
也不是:
CPU Core 用掉 50%。
而是:
Pod 實際 CPU Usage,相對於 CPU Request 的比例。
例如:
Request = 100m
Usage = 20m
20 / 100
=
20%
另外一個 Pod:
Request = 100m
Usage = 80m
80 / 100
=
80%
HPA 會再根據所有相關 Pods 的平均 Utilization 做判斷。
官方 HPA 文件也特別說明:如果相關 Container 沒有設定 CPU Request,HPA 就無法正常定義這個 CPU Utilization。
所以先確認 API Deployment。
執行:
kubectl get deployment api \
-n cka-lab \
-o yaml
Deployment Container 裡應該至少有:
resources:
requests:
cpu: 100m
但目前看下來,我們先前並沒有定義
所以我們先來設定指定的 Requests 與 Limits!
kubectl set resources deployment/api -n cka-lab \
--requests=cpu=100m,memory=64Mi \
--limits=cpu=500m,memory=256Mi
改動就生效啦
今天最重要的是:
requests:
cpu: 100m
因為我們等等 HPA 要使用:
CPU Utilization %
目前 API 平常幾乎沒有 Request。
如果直接建立 HPA,你可能永遠只看到:
2% / 50%
根本不會 Scale。
所以我們故意建立一個很浪費 CPU 的 API。
直接修改既有的 app/main.py
在原本的 FastAPI 裡面改成這樣:
import os
import time
import redis
from fastapi import FastAPI
app = FastAPI()
REDIS_HOST = os.getenv(
"REDIS_HOST",
"redis"
)
redis_client = redis.Redis(
host=REDIS_HOST,
port=6379,
decode_responses=True
)
@app.get("/")
def root():
visits = redis_client.incr("visits")
return {
"message": "Hello from k8s",
"visits": visits
}
@app.get("/health/live")
def live():
return {
"status": "alive"
}
@app.get("/work")
def work():
end_time = time.time() + 0.2
counter = 0
while time.time() < end_time:
counter += 1
return {
"iterations": counter
}
這段:
while time.time() < end_time:
counter += 1
會不停做 CPU 運算。
它沒有任何商業價值。
目的只有:
讓 CPU 忙起來
因為 Application Code 改了,所以需要重新 Build Image:
回到 k8s-30days 根目錄 Build
docker build \
-t cka-api:v4 \
./app
確認:
docker images
會看到:
cka-api v4

然後因為 kind Node 本身也是 Container,所以你的 Mac 有這個 Image,不代表 kind Node 一定能直接使用。
需要把 Image Load 進 kind:
kind load docker-image \
cka-api:v4 \
--name cka-lab

現在 kind Cluster 才能使用:
cka-api:v4
假設 Container Name 是:
api
可以:
kubectl set image \
deployment/api \
api=cka-api:v4 \
-n cka-lab
kubectl set image:指令動詞。deployment/api:目標資源類型與名稱(deployment 叫 api)。api=cka-api:v4:格式為 <CONTAINER_NAME>=<NEW_IMAGE>,代表將名為 api 的容器之映像檔換成 cka-api:v4。n cka-lab:指定 Namespace。如果不知道 Container Name,可以先看:
kubectl get deployment api \
-n cka-lab \
-o jsonpath='{.spec.template.spec.containers[*].name}'
api)。cka-api:v4」來推斷要替換哪一個,必須明確指定容器名。o jsonpath 提取 spec.template.spec.containers 陣列中所有物件的 name 欄位。更新後:
kubectl rollout status \
deployment/api \
-n cka-lab
kubectl set image 只是送出規格變更請求(非同步),終端機會立即返回,不代表 Pod 已經啟動成功。rollout status,後續步驟(如自動化整合測試)會直接打在尚未更新完成或正在崩潰的環境上。deployment "api" successfully rolled out,代表可用副本數已達到預期且更新順利完成。等到:
deployment "api" successfully rolled out
再確認:
kubectl get pods \
-n cka-lab
Running,READY 必須是 1/1(不是 ImagePullBackOff 或 CrashLoopBackOff)。為了等一下清楚看到:
1
↓
2
↓
3
我們先手動 Scale 回:
kubectl scale \
deployment/api \
--replicas=1 \
-n cka-lab
確認:
kubectl get deployment api \
-n cka-lab
應該是:
READY
1/1

在建立 HPA 前,必須確保所有運行中的 Pod 都已套用最新的 Resource Request。如果前一次滾動更新或縮容留有正在終止(Terminating)的舊 Pod,HPA 會因「找不到部分 Pod 的 CPU Request」而陷入 <unknown> 狀態。
先檢查目前 Pod 清單:
Bash
kubectl get pods \
-n cka-lab \
-l app=api
1。STATUS 必須是 Running,絕不能有任何殘留的 Terminating 舊 Pod。READY 必須是 1/1。若看到舊 Pod 卡在 Terminating,可直接強制清除快取殘留:
Bash
# 若有殘留的 Terminating Pod 才需執行
kubectl delete pod <卡住的舊Pod名稱> -n cka-lab --force --grace-period=0
以我自己而言,就發現兩個必須要刪的
他們就是會導致 HPA 卡在 <unknown> 的罪魁禍首!
先前我少做了這個檢查,於是執行 kubectl describe hpa 發現的報錯:
Plaintext
failed to get cpu utilization: missing request for cpu in container api of Pod api-77d48d6587-w75sg
failed to get cpu utilization: missing request for cpu in container api of Pod api-77d48d6587-szc28

完全對應到這兩顆狀態為 ContainerStatusUnknown、活了 5 天多(5d3h)的殭屍 Pod。
因為它們身上帶有 app=api 的標籤,HPA 計算時判定它們是目標 Pod,但因為狀態異常且是沒配資源的舊版本,導致整組指標被判定為無效。
Bash
kubectl delete pod api-77d48d6587-szc28 api-77d48d6587-w75sg -n cka-lab --force --grace-period=0
**確認只剩正常的 Pod:**Bash
```
kubectl get pods -n cka-lab -l app=api
```

- 輸出應該只會看到唯一的 `api-76985779dd-psb42`,且狀態為 `Running`、`1/1`。
並用 top 確認指標數據已能正常抓取:
Bash
kubectl top pod -n cka-lab

看到有輸出 CPU 毫核數(例如 3m),代表 Metrics 正常,可以放心建立 HPA。
現在終於可以建立 HPA:
kubectl autoscale \
deployment api \
-n cka-lab \
--cpu-percent=50 \
--min=1 \
--max=5

這裡有四件事情:
deployment api
代表 HPA 要控制:
Deployment/api
--cpu-percent=50
代表希望平均:
CPU Utilization ≈ 50%
--min=1
代表最少保持:
1 Pod
而:
--max=5
代表即使 CPU 再高,也最多:
5 Pods
執行:
kubectl get hpa \
-n cka-lab
可以看到:

最重要的是:
3% / 50%
左邊:
3%
代表目前平均 CPU Utilization。
右邊:
50%
代表我們希望維持的 Target。
所以現在:
3% << 50%
HPA 當然不需要增加 Pod。
這點非常重要。
很多人第一次會以為:
CPU > 50%
↓
Pod +1
其實不是。
HPA 基本思路比較接近:
Desired Replicas
≈
目前 Pod 數量
×
目前 Metric
÷
目標 Metric
例如現在:
Pod = 1
CPU = 100%
Target = 50%
粗略計算:
1 × 100 / 50
=
2
所以大約需要:
2 Pods
如果:
目前 2 Pods
平均 CPU = 150%
Target = 50%
粗略就是:
2 × 150 / 50
=
6
但因為我們設定:
max = 5
所以最多只會到:
5 Pods
實際演算法還會考慮 tolerance、Pod readiness、缺少 Metrics 等因素,不是單純每超過50%一次就 +1。官方 HPA 文件也描述了這個比例式計算方式。
可搭配此圖更好理解
現在 CPU 還很低,我們來瘋狂打:
/work
建立 BusyBox:
kubectl run load-generator \
-n cka-lab \
--image=busybox:1.36 \
--restart=Never \
-- \
sh -c \
'while true; do wget -q -O- http://api/work > /dev/null; done'
這個 Pod 會執行:
while true
↓
wget http://api/work
↓
再 wget
↓
再 wget
↓
永遠執行
API 就會一直執行:
while time.time() < end_time:
CPU Usage 自然會提高。
BusyBox 完全不知道:
API Pod IP
它只使用:
http://api/work
其中:
api
就是:
Service Name
Kubernetes DNS 會把:
api
解析成 Service。
所以:
Load Generator Pod
│
│ http://api/work
▼
Service/api
│
▼
API Pods
這又再次用到了前面學過的:
Service Discovery
現在非常推薦開三個 Terminal。
第一個:
kubectl top pods \
-n cka-lab
你可能慢慢看到:
api-xxxxx 85m
如果 Request 是:
100m
代表大約:
85 / 100
=
85%
第二個 Terminal:
kubectl get hpa \
-n cka-lab \
-w
可能從:
3%/50%
變成:
85%/50%
甚至:
120%/50%

第三個 Terminal:
kubectl get pods \
-n cka-lab \
-w
接著可能看到:
api-aaaaa
變成:
api-aaaaa
api-bbbbb
再變:
api-aaaaa
api-bbbbb
api-ccccc
你就親眼看到 HPA 生效了。
整條流程其實是:
Request 不斷進入 API
│
▼
Pod CPU Usage 上升
│
▼
Kubelet 取得 Container Metrics
│
▼
Metrics Server 收集 Metrics
│
▼
metrics.k8s.io
│
▼
HPA Controller 讀取 Metrics
│
▼
比較目前 CPU Utilization
和 Target 50%
│
▼
計算需要多少 replicas
│
▼
修改 Deployment replicas
│
▼
ReplicaSet 建立更多 Pod
所以 HPA 自己並不直接:
Create Pod
它實際上比較像:
HPA
↓
改 Deployment replicas
↓
Deployment / ReplicaSet
↓
建立 Pod
這跟我們手動執行:
kubectl scale deployment/api --replicas=3
最後的效果很像。
差別只是:
以前:
Engineer
↓
kubectl scale
↓
Deployment
現在變成:
Metrics
↓
HPA
↓
Deployment
自動完成。
如果想知道 HPA 為什麼 Scaling:
kubectl describe hpa \
api \
-n cka-lab
這個指令非常重要。
你可以看到:
Metrics
Min replicas
Max replicas
Current replicas
Desired replicas
Conditions
Events
Events 可能告訴你:
New size: 3
reason: cpu resource utilization above target

現在把 Load Generator 刪掉:
kubectl delete pod \
load-generator \
-n cka-lab
Request 消失:
Load Generator
X
所以:
API CPU Usage
↓
可以重新看:
kubectl top pods \
-n cka-lab
可能從:
80m
慢慢降回:
3m

降下來了!
你可能會看到 Load 已經停了:
CPU = 3%
但是 Pod 還是:
5 個
不用急。
HPA 不會看到 CPU 一下降就馬上:
5
↓
1
因為 Production 流量可能只是短暫下降。
例如:
10:00 大量 Request
10:01 少量 Request
10:02 又大量 Request
如果 Kubernetes 每次都立即:
Scale Up
Scale Down
Scale Up
Scale Down
就會一直建立、刪除 Pod。
因此 HPA 有 Stabilization 等機制,特別是 Scale Down 會比較保守。
官方文件也說明 HPA 是週期性 Control Loop,而不是每一毫秒不停重新 Scaling;控制器會定期重新評估 Metrics。
所以稍微等待後,你才會看到:
5 Pods
↓
3 Pods
↓
1 Pod
最終不會低於:
minReplicas = 1
執行:
kubectl get hpa \
-n cka-lab
現在重新變成:
表示:
CPU 很低
↓
HPA 判斷不需要多餘 Capacity
↓
回到 1 Pod
整個 Autoscaling Lab 就完成了。
有時候可能看到:
<unknown>/50%
先不要直接刪 HPA。
按照這個順序查:
kubectl top pods -n cka-lab
如果 top 本身就失敗:
先查 Metrics Server
看:
kubectl logs \
deployment/metrics-server \
-n kube-system
以及:
kubectl get apiservice \
v1beta1.metrics.k8s.io
如果 kubectl top 正常,但 HPA 還是:
unknown
再看:
kubectl describe hpa \
api \
-n cka-lab
最後確認 Deployment 有:
resources:
requests:
cpu: 100m
所以 Troubleshooting 思路應該是:
HPA 沒 Metric
↓
kubectl top 正常嗎?
↓
Metrics API 正常嗎?
↓
Metrics Server 正常嗎?
↓
CPU Request 有設定嗎?
而不是一開始就:
刪掉 HPA 重建
現在把整個 Day 19 串起來:
API Pod
│
│ CPU / Memory Usage
▼
Kubelet
│
▼
Metrics Server
│
▼
metrics.k8s.io
│
▼
HPA Controller
│
│ 比較
│
├── Current CPU
│
└── Target CPU 50%
│
▼
Desired Replicas
│
▼
Deployment
│
▼
ReplicaSet
│
▼
更多 / 更少 Pod
其中:
resources:
requests:
cpu: 100m
又扮演了一個非常重要的角色。
HPA 計算:
CPU Utilization
使用的是:
實際 CPU Usage
──────────────
CPU Request
例如:
50m
────
100m
= 50%
因此:
Resources Request
不只是 Scheduler 用來安排 Pod 的資訊。
它也會影響:
HPA
這就是 Kubernetes 很重要的一個觀念:
很多設定不是只服務單一功能,而是整個 Kubernetes Control Plane 的其他元件也會使用。
今天真正做的事情可以濃縮成:
① 安裝 Metrics Server
↓
② kubectl top 能取得 CPU / Memory
↓
③ Deployment 設定 CPU Request
↓
④ 建立 HPA
Target CPU = 50%
Min = 1
Max = 5
↓
⑤ Load Generator 大量打 /work
↓
⑥ API CPU 上升
↓
⑦ HPA 增加 Deployment replicas
↓
⑧ ReplicaSet 建立更多 API Pods
↓
⑨ 停止 Load
↓
⑩ CPU 降低
↓
⑪ HPA 慢慢 Scale Down
所以從 Day 19 開始,我們已經不再只是:
「我想要幾個 Pod」
而是開始讓 Kubernetes 根據:
實際系統狀態
自己決定需要多少 Capacity。
下一個問題則完全不同。
Deployment 假設:
Application 應該一直活著。
例如:
API
Redis
Web Server
都是長時間運作。
但有些工作本來就是:
開始
↓
執行一件事情
↓
成功
↓
Process 結束
例如:
Database Migration
每天產生一次 Report
批次資料處理
Backup
這時候就會進入 Kubernetes 另一類非常重要的 Workload:
Job
CronJob
明天,我們要帶大家認識另一群使命完全不同的 Workloads 控制器:👉 Day 20|不是所有 Pod 都要永遠活著:Job、CronJob 與 DaemonSet
我們明天見!