[應用] keda 部署 × Redis 自動縮放
===
###### tags: `K8s`
###### tags: `Kubernetes`, `k8s`, `app`, `keda`, `使用教學`
<br>
[TOC]
<br>
:::success
## :bulb: 這在做什麼?解決什麼問題?
* **目的**:讓你的 Deployment 依「**外部訊號**」(這裡是依據 Redis 佇列長度)**自動擴縮 Pod 數**,而不是只看 CPU/Memory。
* **好處**:當工作排隊變多就擴容、清空就縮容,**效能與成本更平衡**,**不必手動調整 replicas**。
<br>
## :bulb: 最小工作流程(對照底下範例教學)
1. **安裝 KEDA**(Helm,一行指令即可)。
2. **準備訊號來源**:起一個 Redis(測試用 queue)。
3. **部署待縮放的 App**(簡單 hello-world Deployment)。
4. **建立 ScaledObject**(`type: redis`,填 `address`、`listName`、`listLength`、`min/maxReplicaCount`)。
5. **注入負載**:`LPUSH` ≥ 5 筆到 `hello` list → 觀察 Pod 自動從 1 擴到最多 5。
:::
<br>
## 範例:根據 queue(像是 RabbitMQ 或 Redis)的訊息數量來自動縮放一個 Deployment
> by gpt4.1 (2025/08/01)
### 步驟 1:安裝 KEDA(用 Helm)
```bash
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace keda --create-namespace
```
<br>
### 步驟 2:部署 Redis(當成 queue 測試用)
```bash
kubectl create namespace demo
kubectl -n demo run redis --image=redis --port=6379
kubectl -n demo expose pod/redis --port=6379
```
- [[完整詳解] redis 部署與測試教學(含注意事項)](/cx7lMfyRTfimOB2Ym_W6SA)
<br>
### 步驟 3:建立 Hello World App
這是一個**最小**的 Python App,每 5 秒印個 log。
**deployment.yaml**:
```yaml=
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: app
image: busybox
command: ["/bin/sh", "-c"]
args:
- while true; do echo `date +%T` Hello, KEDA!; sleep 5; done
```
```bash
kubectl apply -f deployment.yaml
```
```
$ kubectl -n demo get all
NAME READY STATUS RESTARTS AGE
pod/hello-world-57959d477f-cbgpz 1/1 Running 0 3d16h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello-world 1/1 1 1 3d19h
```
<br>
### 步驟 4:建立 KEDA ScaledObject
**scaledobject.yaml**:
- 這裡的意思是「根據 Redis 中 `hello` 這個 key 的長度,來決定要不要自動縮放 `hello-world` 這個 deployment」。
- 我們設定 **queue length >= 5** 就擴增 pod。
```yaml=
# scaledobject.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: hello-world-scaler
namespace: demo
spec:
scaleTargetRef:
# kind: Deployment # 預設類型為 Deployment
name: hello-world # Deployment/hello-world
minReplicaCount: 1
maxReplicaCount: 5
# KEDA 會去連線到你指定的 redis,
# 然後監控 listName: hello 的長度。
# 只要這個 list 長度 > 5 就會 scale up。
triggers:
- type: redis
metadata:
address: redis.demo.svc.cluster.local:6379
listName: hello
listLength: "5"
```
```bash
kubectl apply -f scaledobject.yaml
```
- ### 注意
- **更完整的表達 `scaleTargetRef`**
```yaml
spec:
scaleTargetRef: # 要控制縮放的對象
apiVersion: apps/v1
kind: Deployment
name: hello-world
```
- **`listLength`: 必須是字串,否則會丟出底下錯誤訊息:**
`The ScaledObject "scaled-object" is invalid: spec.triggers[0].metadata.listLength: Invalid value: "integer": spec.triggers[0].metadata.listLength in body must be of type string: "integer"`
- ### 查詢 scaledobject 屬性
```
kubectl explain scaledobject
kubectl explain scaledobject.spec
kubectl explain scaledobject.spec.triggers
```
- ### 查詢 scaledobject 部署狀態
```
$ kubectl -n demo get scaledobjects
NAMESPACE NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE FALLBACK PAUSED TRIGGERS AUTHENTICATIONS AGE
demo hello-world-scaler apps/v1.Deployment hello-world 1 5 True True False Unknown redis 2d2h
# 留意 READY & ACTIVE 狀態:
# READY ACTIVE
# True True
```
```
$ kubectl get all -n demo
NAME READY STATUS RESTARTS AGE
pod/hello-world-57959d477f-cbgpz 1/1 Running 0 47h
pod/redis 1/1 Running 0 2d2h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/redis ClusterIP 10.111.162.218 <none> 6379/TCP 2d2h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello-world 1/1 1 1 2d2h
NAME DESIRED CURRENT READY AGE
replicaset.apps/hello-world-57959d477f 1 1 1 2d2h
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
horizontalpodautoscaler.autoscaling/keda-hpa-hello-world-scaler Deployment/hello-world 1/5 (avg) 1 5 1 2d2h
# TARGETS: 1/5 (avg)
# 數量會變更,隨著 listName: hello 的長度而變
```
---
### 步驟 5:觀察自動縮放
1. **預設狀態**:只會有 1 個 Pod
```bash
kubectl get pods -n demo
```
2. **手動加訊息到 queue(模擬流量)**
```bash
kubectl -n demo run -it --rm redis-client --image=redis -- bash
```
進去 shell 後執行下列指令:
```
redis-cli -h redis
redis:6379> LPUSH hello a # ["a"]
redis:6379> LPUSH hello b # ["b", "a"]
redis:6379> LPUSH hello c # ["c", "b", "a"]
redis:6379> LPUSH hello d # ["d", "c", "b", "a"]
redis:6379> LPUSH hello e # ["e", "d", "c", "b", "a"]
redis:6379> LRANGE hello 0 -1 # ["e", "d", "c", "b", "a"]
```
- `-h redis`:連線到前面建立的 service/redis
```
# 前面建立的 service/redis
kubectl -n demo expose pod/redis --port=6379
```
- 將 a, b, c, d, e 字串,推送到 `hello` 這個 queue
- 更詳細的解釋,可參考前面提到的:
- [[完整詳解] redis 部署與測試教學(含注意事項)](/cx7lMfyRTfimOB2Ym_W6SA)
這樣 queue 長度達 5,就會觸發自動擴 pod!
3. **檢查 pods 數量**
```bash
kubectl get pods -n demo -w
```
你會看到 `hello-world` 從 1 個 pod scale 到更多(最多 5 個)。
<br>
---
### 觀念回顧
:::success
### :bulb: 核心觀念(3 句話)
1. 你寫的 **ScaledObject(CRD)** =「要監控什麼、要縮誰」的設定檔。
2. **KEDA Controller** 讀取 `triggers.type`(例:`redis`)→ 找到對應 **Scaler**,依 `metadata` 去查外部系統(Redis)。
3. KEDA 依回傳的指標,自動建立/更新 **HPA**,調整 Deployment **replicas**。
:::
---
### 常見 FAQ
* **如果沒裝 Helm?**
也可以用 yaml 安裝 KEDA,參見官方[安裝頁](https://keda.sh/docs/latest/deploy/).
* **要用不同 Scaler?**
KEDA 支援非常多 scaler,像是 http, cron, kafka, Prometheus 等,換 yaml 設定就行。
* **重點排查 Checklist**
- [ ] Redis 真的在 service/port 上可以連線
- [ ] KEDA Operator 日誌是否有錯誤
```
kubectl logs -n keda -l app=keda-operator
```
- [ ] 你的 Redis 有正確的 list
* **怎麼確認是否在運作?**
- **看物件狀態**
`kubectl -n demo get scaledobjects`(**READY / ACTIVE** 應為 True)
`kubectl -n demo get hpa`(`keda-hpa-...` 會出現、TARGETS 會隨 queue 變動)
`kubectl -n demo get pods -w`(觀察 replicas 變化)
- **看 KEDA 日誌**
`kubectl logs -n keda -l app=keda-operator`
* **HPA 目標 (TARGETS) 維持高值**
- **Polling/Cooldown 設定太長**
檢查你的 ScaledObject YAML,有沒有設 `cooldownPeriod`、`pollingInterval`。預設 `cooldownPeriod` 是 5 分鐘(300 秒),scale down 會延遲。
- **調整 `cooldownPeriod`(如你不想等很久)**
```yaml
spec:
cooldownPeriod: 30 # 30秒(預設300秒)
```
- **直接 scale down 測試(手動)**
```sh
kubectl scale deployment hello-world --replicas=1
```
---
### 延伸閱讀
* [KEDA 官方 Redis 範例](https://keda.sh/docs/latest/scalers/redis/)
* [KEDA ScaledObject 文件](https://keda.sh/docs/latest/concepts/scaling-deployments/)
* [KEDA 支援的所有 Scaler](https://keda.sh/docs/latest/scalers/)
<br>
---
---
<br>
## 討論
### ScaledObject 怎麼知道 redis 的操作方式與屬性?
- ### gpt-4.1 (2025/08/02)
你的這個問題很棒,這其實是 KEDA 的一個設計精隨!
### ScaledObject 怎麼知道 Redis 的操作方式與屬性?
其實 **ScaledObject 並不知道怎麼操作 Redis**,它只是**定義了一個監控觸發條件(trigger)**,以及對應需要的 metadata(像你範例裡的 `address`、`listName`、`listLength`)。
真正**理解與實作操作邏輯的是 KEDA 的 Scaler(例如 Redis Scaler)**。
KEDA 本身是一個控制器(Controller),當你建立 `ScaledObject` 物件時,KEDA Controller 會根據你填的 `type: redis`,自動選擇對應的 Scaler(程式碼),去讀取你 `metadata` 裡的參數,然後去執行操作。
### 具體流程
1. **你寫的 yaml**(只是一個 CRD 實體,描述要監控什麼,參數有哪些)。
2. **KEDA Controller** 讀取 ScaledObject,根據 `triggers.type` 決定要用哪一種 Scaler,例如 Redis Scaler。
3. **Redis Scaler** 會讀你的 `metadata`(address、listName、listLength 等),然後自己去對 Redis 做連線與查詢。
* 例如會用 `address` 去連 Redis server
* 用 `listName` 查找 List
* 用 `listLength` 來比對是否超過閾值
4. **KEDA Controller** 根據 Redis Scaler 回報的值,決定要不要改變 deployment 的 replica 數量。
### metadata 屬性如何定義?
每一種 Scaler 的 metadata 格式是**寫死在 KEDA 的 source code 裡**的,不同的 trigger type 會有不同的欄位與意義。
官方文件都會列出各種 scaler 可支援哪些參數,例如[Redis Scaler 的 metadata](https://keda.sh/docs/2.17/scalers/redis-lists/):
```yaml
triggers:
- type: redis
metadata:
address: <Redis connection string>
listName: <Redis list name>
listLength: <Queue length to trigger scaling>
```
### 總結
* **ScaledObject 本身不知道怎麼操作 Redis,只是提供 metadata。**
* **真正知道的是 KEDA controller + redis scaler 的程式碼(在 KEDA 的 pod 裡執行)**。
* 你可以把 ScaledObject 想成「設定檔」,KEDA controller 負責解析它、根據 type 跳到對應的 scaler 程式邏輯,然後根據 metadata 去真正連線、查詢 redis。
---
有需要看 KEDA Redis Scaler 的原始碼範例嗎?還是想了解更多 Scaler 的設計?
<br>
### 目前 type 有哪幾種?
- ### gpt-4.1 (2025/08/02)
KEDA 支援的 `type`(scaler 類型)非常多,這些 type 決定你可以根據哪些外部資源或訊號自動調整 Kubernetes deployment 的 replica 數量。
每一種 type 都有自己的 metadata 參數格式。常見的類型有:
---
### 常見 KEDA trigger `type` 一覽(部分舉例)
* **cpu**
* **memory**
* **cron**
* **prometheus**
* **http** / **http-add-on**
* **rabbitmq**
* **kafka**
* **azure-queue** / **azure-servicebus**
* **gcp-pubsub**
* **aws-sqs**
* **redis**
* **mongodb**
* **mysql**
* **postgresql**
* **mysql**
* **elasticsearch**
* **activemq**
* **mssql**
* **cloudwatch**
* **azure-eventhub**
* **azure-blob**
* **liiklus**
* **nats-streaming**
* **pulsar**
* **stan**
* **influxdb**
* **zookeeper**
* **docker**
* **kubernetes-workload**
* **metrics-api**
* **external**
* […還有很多!](https://keda.sh/docs/latest/scalers/)
---
### 官方完整清單
完整支援的 type(scaler/triggers)可參考官方文件的[Scalers 一覽表](https://keda.sh/docs/latest/scalers/)
裡面有所有支援類型、每個 type 對應的 metadata 格式與說明。
> **最新的 scaler type 請參考:**
> [https://keda.sh/docs/latest/scalers/](https://keda.sh/docs/latest/scalers/)
你可以用這些 type 來監控各種來源,例如訊息佇列、資料庫、雲端服務、排程、甚至自訂的 external API,來彈性擴縮容。
---
如果你有特定想用的 type,告訴我,我可以幫你解釋 metadata 與常見用法!
<br>
### 畫出文件的流程圖/架構圖
- ### gpt-5 (2025/09/03)
下面提供 4 張以 **Mermaid** 撰寫的圖(流程圖、架構圖、時序圖、狀態圖)。把程式碼貼到支援 Mermaid 的環境(如 Markdown 編輯器或 Mermaid Live Editor)即可渲染。若需要 **PNG/SVG** 檔,我也可以直接匯出版本給你。
---
### ① 教學流程圖(從安裝到觀察自動縮放)
```mermaid
%%{init: {'flowchart': {'htmlLabels': true}}}%%
flowchart TD
step1["步驟 1:安裝 KEDA(Helm)"]
step2["步驟 2:部署 Redis(demo/redis + Service)"]
step3["步驟 3:部署 App(demo/hello-world)"]
step4["步驟 4:建立 ScaledObject(type=redis,listLength='5')"]
step5["步驟 5:注入負載(LPUSH hello a..e)"]
step6["觀察 HPA 與 Pods 自動擴縮"]
chk{"ScaledObject<br/>READY/ACTIVE?"}
log["檢查 KEDA Operator 日誌<br/>kubectl logs -n keda -l app=keda-operator"]
done(("完成"))
step1 --> step2 --> step3 --> step4 --> step5 --> step6
step6 -->|排查| chk
chk -->|No| log
chk -->|Yes| done
```
---
### ② 叢集架構圖(元件關係與資料流)
```mermaid
%%{init: {'flowchart': {'htmlLabels': true}}}%%
flowchart LR
subgraph cluster_k8s ["K8s Cluster"]
subgraph ns_keda ["keda(ns)"]
KO["KEDA Operator<br/>(Deployment/Pod)"]
end
subgraph ns_demo ["demo(ns)"]
RSVC["Service/redis:6379"]
R["Redis Pod"]
APP["Deployment/hello-world"]
HPA["HPA:keda-hpa-hello-world-scaler"]
end
APIS["K8s API Server"]
KCM["Kube Controller Manager"]
end
%% 關聯
RSVC --- R
KO -- "讀取/監控 ScaledObject" --> APIS
APIS -- "CRD/ScaledObject 事件" --> KO
KO -. "poll" .-> RSVC
KO -- "建立/更新" --> HPA
HPA -- "指標與目標" --> KCM
KCM -- "調整 replicas" --> APP
```
---
### ③ 時序圖(佇列觸發擴縮的互動流程)
```mermaid
sequenceDiagram
participant User
participant Redis as Redis (list: hello)
participant KEDA as KEDA Operator
participant HPA as HPA (keda-hpa-...)
participant KCM as Kube Controller Manager
participant Deploy as Deployment/hello-world
participant Pods as Pods
User->>Redis: LPUSH hello a..e
loop 每個 pollingInterval
KEDA->>Redis: LLEN hello
Redis-->>KEDA: length = N
alt N >= listLength(5)
KEDA->>HPA: 更新/維持外部指標 (觸發擴容)
HPA->>KCM: 請求 scale up
KCM->>Deploy: replicas -> k(<=max)
Deploy->>Pods: 建立新 Pods
else N < 5 (或清空)
KEDA->>HPA: 允許縮容(受 cooldown 限制)
HPA->>KCM: scale down(cooldown 後)
KCM->>Deploy: replicas -> 1
Deploy->>Pods: 終止多餘 Pods
end
end
```
---
### ④ 狀態圖(縮放決策的簡化狀態機)
```mermaid
stateDiagram-v2
[*] --> Idle: LLEN < 5
Idle --> Active: LLEN >= 5
Active --> ScalingUp: 觸發 HPA 擴容
ScalingUp --> Active: 達到需求或上限
Active --> Cooldown: LLEN < 5
Cooldown --> Idle: cooldownPeriod 結束
```
---
### ⑤ hello 佇列長度 vs. 時間(LPUSH a..e,之後 RPOP 兩次)
```mermaid
xychart-beta
title "Redis 列表 hello 的長度隨時間變化"
x-axis "時間" [T0, T1, T2, T3, T4, T5, T6, T7]
y-axis "LLEN(hello)" 0 --> 6
line "length" [0, 1, 2, 3, 4, 5, 3, 1]
```
* 範例事件對應:
* T1\~T5:依序 `LPUSH a,b,c,d,e` → 長度 1→5
* T6:消費一筆(如 `RPOP`)→ 長度 3(示意一次取兩筆)
* T7:再消費一筆 → 長度 1(示意)
> 你可以把資料點改成自己的實測序列(照順序填陣列即可)。
---
### ⑥ hello 佇列內容的快照時間線(每一步顯示佇列內容)
```mermaid
timeline
title Redis 列表 hello 的內容變化(頭部在左側,LPUSH 進頭部)
section Push 階段
T0 : [](起始)
T1 : LPUSH a → [a]
T2 : LPUSH b → [b, a]
T3 : LPUSH c → [c, b, a]
T4 : LPUSH d → [d, c, b, a]
T5 : LPUSH e → [e, d, c, b, a] ;LLEN=5(達擴縮門檻)
section 消費階段
T6 : 消費兩筆(如 RPOP 兩次)→ [e, d, c] ;LLEN=3
T7 : 再消費兩筆(示意)→ [e] ;LLEN=1
```
> 若你的消費端用 `LPOP`,內容縮減會從左側開始;上面的例子用 `RPOP` 示意從尾端取走。
<br>
{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}