跳至主要内容

AutoScaling - Custom Pod AutoScaler

在 Kubernetes 中,原生的水平 Pod 自動擴展(HPA)功能通常依賴於 CPU 和記憶體的平均使用量或使用率作為自動擴展的觸發指標。 然而,對於使用 partition 分配機制的應用程式(如 Kafka)來說,這樣的設定可能會導致工作負載分布不均,從而無法有效地擴展資源。

例如,某些 Pod 可能達到其負載上限,但由於整體的平均資源使用率尚未達到閥值,HPA 可能無法及時觸發擴展。

為了解決這個問題,一個有效的策略是使用 KEDA(Kubernetes Event-Driven Autoscaling)。 KEDA 透過監控每個 Pod 的事件處理速度(例如 Kafka 訊息的消費速度)來進行自動擴展,而不僅僅依賴於 Pod 的平均資源使用情況。

這種方法能精確地調整 Pod 數量,有效解決工作負載不均的問題,並優化資源使用,提升整體應用程式的性能與可擴展性。


Kubernetes Event-Driven Autoscaling (KEDA)

KEDA-architecture

Kubernetes Event-Driven Autoscaling (KEDA) 是一種 基於事件的自動擴展 解決方案, 主要用於 自動調整 Kubernetes 工作負載的副本數量,根據 事件驅動 來動態調整應用程式的資源分配, 而不是僅依賴 CPU 或記憶體使用率(如 Horizontal Pod Autoscaler, HPA)。


KEDA 的核心特點

  1. 事件驅動的自動縮放
    • KEDA 允許應用程式根據特定事件來源(如 Kafka、RabbitMQ、Azure Service Bus、Prometheus 等)來決定何時擴展或縮減 Pod 數量。
  2. 與 HPA(Horizontal Pod Autoscaler)整合
    • KEDA 會將事件來源的數據轉換為 Kubernetes 的 HPA 指標,使 HPA 能夠根據這些事件來觸發 Pod 擴展。
  3. 精細控制 Pod 的擴展
    • 當沒有負載時,KEDA 可以將 Pod 縮減為零,以節省資源;當事件出現時,則能夠迅速擴展 Pod。
  4. 支援多種 Scalers(事件來源)
    • KEDA 內建支援 50 多種 Scaler,例如:
      • 訊息佇列:Kafka、RabbitMQ、Azure Queue Storage
      • 監控系統:Prometheus、Datadog
      • 雲端服務:AWS SQS、Google Pub/Sub
      • 資料庫:PostgreSQL、MySQL

KEDA 的應用場景

  1. 無伺服器架構(Serverless)
    • 讓 Kubernetes 上的應用程式具備無伺服器能力,當沒有負載時,Pod 數量可以降到零,避免資源浪費。
  2. 訊息佇列處理
    • 透過 KEDA,系統能夠根據 Kafka、RabbitMQ、SQS 等訊息佇列中的負載動態擴展 Pod,確保即時處理能力。
  3. 資料庫觸發自動擴展
    • 例如,根據 PostgreSQL 的資料變更來觸發 Pod 擴展,適用於高流量的應用程式。
  4. 雲端監控與異常響應
    • 使用 Prometheus 或 Datadog 作為事件來源,當系統監測到異常時,動態擴展應用程式以應對突發流量。

KEDA 與 HPA 的比較

HPA(水平自動擴展)KEDA(事件驅動自動擴展)
觸發條件主要依賴 CPU / Memory依賴 事件來源(Kafka, RabbitMQ, HTTP 佇列...)
最低 Pod 數量1(無法歸零)0(完全釋放資源)
適用場景長時間運行的應用(如 Web 伺服器)需根據負載動態啟動的應用(如背景處理、批次任務)
擴展速度受 HPA 內部計算影響即時事件驅動

KEDA CRD:ScaledObject 與 ScaledJob

KEDA 的框架中,ScaledObjectScaledJob 是兩種核心的 自定義資源(Custom Resource Definitions, CRDs), 用來定義如何根據事件來 自動擴展(Scaling)Pod建立短暫的批次工作(Job)

這兩種 CRD 提供了一種靈活的方式來管理 Kubernetes 應用的 事件驅動擴展,適用於各種應用場景,如 佇列處理、批次任務、資料庫監控 等。

ScaledObject

ScaledObject 用於 管理長期執行的 Pod(Deployment、StatefulSet、ReplicaSet),並根據事件來源來 動態擴展或縮減 Pod 的副本數。

ScaledObject 主要功能

  • 針對 Kubernetes 部署(Deployment, StatefulSet, ReplicaSet)進行擴展
  • 當沒有事件時,可以將 Pod 縮減至 0(省資源)
  • 可與 HPA(Horizontal Pod Autoscaler)整合
  • 支援多種事件來源,如 Kafka、RabbitMQ、Prometheus、AWS SQS 等

ScaledObject YAML 範例

範例:根據 Kafka 訊息數量動態調整 Pod

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-scaler
namespace: default
spec:
scaleTargetRef:
name: my-kafka-consumer # 指定要擴展的 Deployment
minReplicaCount: 0 # 最小 Pod 數量
maxReplicaCount: 10 # 最大 Pod 數量
idleReplicaCount: 1 # 當無負載時,仍保留 1 個 Pod 避免冷啟動
cooldownPeriod: 30 # 無負載後 30 秒內縮減
pollingInterval: 5 # 每 5 秒檢查一次負載

# 可選:當 KEDA 無法存取事件來源時的回退機制
fallback:
failureThreshold: 5 # 連續 5 次失敗視為無法存取事件來源
replicas: 3 # 無法存取事件來源時,強制維持 3 個 Pod

# 可選:進階擴展行為控制
advanced:
restoreToOriginalReplicaCount: true # 停用 KEDA 時恢復原本的 ReplicaCount
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 60 # 60 秒內不進行縮減
policies:
- type: Percent
value: 50
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 5

triggers:
- type: kafka
metadata:
bootstrapServers: my-kafka-broker:9092
topic: my-topic
consumerGroup: my-group
lagThreshold: '100' # 當 Kafka 訊息積壓超過 100 則觸發擴展

ScaledObject 重要參數

參數作用
scaleTargetRef.name指定要擴展的 Pod(Deployment, StatefulSet, ReplicaSet)
minReplicaCount設定 Pod 的最小數量,可為 0
maxReplicaCount設定 Pod 的最大數量
idleReplicaCount當無負載時,仍然保留的 Pod 數量,避免冷啟動(例如 1 表示即使無負載仍保留 1 個 Pod)
cooldownPeriod無負載時等待多久再縮減 Pod
pollingInterval多久檢查一次事件來源(預設 30 秒)
triggers定義 事件來源(Kafka, RabbitMQ, SQS, Prometheus...)

ScaledObject 可選參數

參數作用
fallback (可選)當 KEDA 無法存取事件來源時,回退到預設副本數
fallback.failureThresholdKEDA 失敗多少次後,啟用回退機制
fallback.replicas回退機制啟動後,強制維持的 Pod 數量
advanced (可選)進階自動擴展設定
advanced.restoreToOriginalReplicaCount停用 KEDA 時,是否恢復原本的 ReplicaCount
advanced.horizontalPodAutoscalerConfig自訂 HPA(水平 Pod 自動擴展)的行為
advanced.horizontalPodAutoscalerConfig.behavior擴展與縮減的行為控制

ScaledJob

ScaledJob 用於 管理短暫執行的批次工作(Jobs),適合一次性或臨時性的計算工作,當事件發生時,KEDA 會 動態啟動 Kubernetes Job,並在工作完成後自動釋放資源。

ScaledJob 主要功能

  • 適用於批次任務(Batch Jobs),如影像處理、資料分析
  • 當負載增加時,自動啟動多個 Job 處理
  • 無負載時,不會產生多餘的 Pod(降低資源消耗)
  • 自動管理 Job 的刪除與錯誤處理

ScaledJob YAML 範例

範例:根據 RabbitMQ 訊息數量觸發 Job,並加入進階控制參數

apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: rabbitmq-job-scaler
namespace: default
spec:
jobTargetRef:
parallelism: 2 # 允許最多 2 個 Job 同時運行
completions: 1 # 每個 Job 只需成功執行 1 次
activeDeadlineSeconds: 300 # Job 最長運行時間為 300 秒,超時則終止
backoffLimit: 3 # Job 失敗後最多重試 3 次
template:
spec:
containers:
- name: rabbitmq-client
image: rabbitmq-client:latest
command:
- receive
args:
- '$(RABBITMQ_QUEUE)'
restartPolicy: Never # Job 結束後不自動重啟

minReplicaCount: 0 # 當無負載時,不產生 Job
maxReplicaCount: 20 # 最多允許 20 個 Job 併發
successfulJobsHistoryLimit: 5 # 保留 5 個成功 Job 記錄
failedJobsHistoryLimit: 5 # 保留 5 個失敗 Job 記錄
pollingInterval: 10 # 每 10 秒檢查一次負載

# 可選:從指定 Container 讀取環境變數
envSourceContainerName: rabbitmq-client

# 可選:控制 Job 滾動更新行為
rollout:
strategy: gradual # 逐步滾動更新(可選 "immediate" 或 "gradual")

# 可選:自訂 Job 的擴展策略
scalingStrategy:
strategy: custom # 可選 "default"、"custom" 或 "accurate"
customScalingQueueLengthDeduction: 5 # 每次擴展時,額外扣除 5 個訊息數
customScalingRunningJobPercentage: 50 # 當 50% Job 運行時,停止擴展

triggers:
- type: rabbitmq
metadata:
queueName: my-queue
hostFromEnv: RABBITMQ_HOST
mode: QueueLength
value: '50' # 當佇列訊息超過 50 時觸發 Job

ScaledJob 重要參數

參數作用
jobTargetRef.template.spec.containers定義執行的 Job 內容(映像名稱等)
parallelism控制 Job 內部的併發數量(例如 2 表示同時最多 2 個 Pod 執行該 Job)
completions每個 Job 需要成功執行的次數(通常為 1,但可以設為更高來確保多次成功)
activeDeadlineSecondsJob 最長允許執行時間,超時則終止
backoffLimitJob 失敗後允許的重試次數
minReplicaCount最小 Job 數量,可為 0(表示無負載時不產生 Job)
maxReplicaCount最大可同時執行的 Job 數量
successfulJobsHistoryLimit保留的成功 Job 記錄數量
failedJobsHistoryLimit保留的失敗 Job 記錄數量
pollingInterval多久檢查一次事件來源(預設 30 秒)
triggers定義 事件來源(Kafka, RabbitMQ, SQS, Prometheus...)

ScaledJob 可選參數

參數作用
envSourceContainerName (可選)指定從哪個 Container 讀取環境變數
rollout (可選)控制 Job 的滾動更新策略
rollout.strategygradual 表示逐步滾動,immediate 表示立即更新
scalingStrategy (可選)自訂 KEDA 的 Job 擴展行為
scalingStrategy.strategydefault 使用 KEDA 預設策略,或可使用custom自訂邏輯,accurate則提供更精確的擴展
scalingStrategy.customScalingQueueLengthDeduction每次擴展時,從佇列長度額外扣除多少消息數量
scalingStrategy.customScalingRunningJobPercentage當運行中的 Job 超過某百分比時,停止擴展

ScaledObject vs ScaledJob

ScaledObjectScaledJob
適用對象長期運行的應用(Deployment, StatefulSet, ReplicaSet)短期批次工作(Job)
最小副本數可縮減至 0無負載時不產生 Job
最大副本數設定最大 Pod設定最大 Job
是否會自動刪除Pod 會持續運行(除非手動刪除)Job 完成後自動刪除
適用場景API 伺服器、微服務、自動擴展消費者批次任務、影像處理、資料轉換

KEDA Triggers 介紹

這裡我們會介紹以下 5 種 KEDA Triggers

  1. Prometheus → 透過 Prometheus 監控指標來觸發擴展
  2. Kafka → 根據 Kafka 佇列積壓(lag)動態擴展
  3. PostgreSQL → 根據 SQL 查詢結果決定擴展
  4. Google Cloud Platform Storage (GCS) → 依據 GCS 儲存桶的物件數觸發擴展
  5. Elasticsearch → 根據日誌數量、搜尋請求或待處理訂單觸發擴展

1. Prometheus Trigger

Prometheus 是一個開源的監控與警報工具,專門處理指標資料而設計,我們可以使用 Prometheus Scaler 來偵測單一 Pod 的使用率,根據資料透過 自訂指標(metrics) 來觸發 KEDA 擴展。

YAML 範例

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: prometheus-scaler
namespace: default
spec:
scaleTargetRef:
name: my-deployment # 要擴展的 Deployment 名稱
minReplicaCount: 1
maxReplicaCount: 10
pollingInterval: 10 # 每 10 秒檢查一次指標
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.default.svc:9090
metricName: http_requests_total
query: sum(rate(http_requests_total{deployment="my-deployment"}[2m])) # 取 2 分鐘內請求速率
threshold: '100' # 當請求速率超過 100 次/秒時擴展

參數說明

參數作用
serverAddressPrometheus 伺服器位址(通常是 http://prometheus.default.svc:9090
metricName指標名稱,例如 http_requests_total
queryPromQL 查詢語法,決定如何計算擴展條件
threshold當查詢結果超過此值時觸發擴展

適用場景

  • HTTP 請求流量變化大時,自動擴展服務
  • 根據 CPU、記憶體、使用者數量等指標來調整 Pod 數量

2. Kafka Trigger

Kafka 是一個分散式的資料串流平台,可以處理實時資料串流,KEDA 可以根據 Kafka topic 的積壓數量(lag) 來決定是否擴展 Consumer。

YAML 範例

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-scaler
namespace: default
spec:
scaleTargetRef:
name: kafka-consumer
minReplicaCount: 1
maxReplicaCount: 10
pollingInterval: 5
triggers:
- type: kafka
metadata:
bootstrapServers: my-kafka-broker:9092
topic: my-topic
consumerGroup: my-group
# Optional
lagThreshold: '100' # 當積壓超過 100 則訊息時觸發擴展
offsetResetPolicy: latest # 設定 Kafka 消費者從最新的訊息開始讀取

參數說明

參數作用
bootstrapServersKafka 代理伺服器的位址
topic要監聽的 Kafka topic
consumerGroup監聽的消費者群組(Consumer Group)
lagThreshold當 Kafka topic 積壓數量超過該值時觸發擴展
offsetResetPolicy設定 Kafka 消費者從最新的訊息開始讀取

適用場景

  • Kafka Consumer 需要根據負載動態擴展
  • 確保訊息處理速度不會落後

3. PostgreSQL Trigger

PostgreSQL Trigger 可以根據 PostgreSQL 查詢結果(例如當前正在處理的任務數量)來決定是否需要調整資源,並根據負載情況自動擴展或縮減 Pod 數量。

YAML 範例

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: airflow-worker
namespace: default
spec:
scaleTargetRef:
name: airflow-worker
minReplicaCount: 1
maxReplicaCount: 10 # Optional. Default: 100
pollingInterval: 10 # Optional. Default: 30 seconds
cooldownPeriod: 30 # Optional. Default: 300 seconds
triggers:
- type: postgresql
metadata:
connectionFromEnv: AIRFLOW_CONN_AIRFLOW_DB # 從環境變數中取得 PostgreSQL 連線字串
query: "SELECT ceil(COUNT(*): : decimal / 16) FROM task_instance WHERE state='running' OR state='queued'" # SQL 查詢語法,查詢當前正在運行或排隊的任務數量,並將其除以 16 來確定所需的工作者數量
targetQueryValue: 1 # 當查詢結果大於 1 時觸發擴展

參數說明

參數作用
connectionFromEnv透過環境變數設定 PostgreSQL 連線字串,該字串中包含資料庫的連線資訊(如使用 AIRFLOW_CONN_AIRFLOW_DB 來取得連線字串)
querySQL 查詢語法,這個查詢會執行後返回一個數值,KEDA 會將其與 targetQueryValue 進行比較。此查詢的目的是計算當前正在處理或排隊的任務數量
targetQueryValue當查詢結果超過該值時,觸發 Pod 擴展。這表示當目前的任務數量達到或超過此值時,就會啟動擴展動作

適用場景

  • 批次處理系統(如:資料匯入、報表生成)
  • 根據未處理的訂單或請求數量自動擴展 API 伺服器

4. Google Cloud Platform Storage (GCS) Trigger

KEDA 也可以監聽 Google Cloud Storage(GCS)儲存桶,當桶內的檔案數量超過指定值時,自動擴展處理這些檔案的 Pod。

YAML 範例

apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: keda-trigger-auth-gcp-credentials
spec:
secretTargetRef:
- parameter: GoogleApplicationCredentails
name: gcp-storage-secret # Required. Refers to the name of the secret
key: GOOGLE_APPLICATION_CREDENTAILS_JSON # Required.
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: gcp-storage-scaledobject
namespace: default
spec:
scaleTargetRef:
name: keda-gcp-storage-go
minReplicaCount: 1
maxReplicaCount: 10
pollingInterval: 15
triggers:
- type: gcp-storage
authenticationRef:
name: keda-trigger-auth-gcp-credentails
metadata:
bucketName: 'Transactions'
targetObjectCount: '100' # 當桶內物件超過 100 個時擴展
blobPrefix: blobsubpath # Default: "" 只計算名稱以此字串開頭的物件
blobDelimiter: '/' # 當啟用時,KEDA 只會監聽指定「資料夾」內的物件

參數說明

參數作用
bucketName監聽的 GCS 儲存桶名稱
targetObjectCount當桶內物件超過此數量時觸發擴展
blobPrefix只計算名稱以此字串開頭的物件,適用於監聽特定類型的檔案,例如 "logs/" 只計算 logs/ 開頭的檔案。
blobDelimiter將 GCS 存儲桶視為類似目錄結構時使用,例如設定 / 代表 GCS「資料夾」,若 blobPrefix: "logs/"blobDelimiter: "/",則 KEDA 只會計算 logs/ 內的物件,不會包含子目錄內的檔案。

適用場景

  • 根據 GCS 中的檔案數量擴展影像處理、資料轉換服務
  • 適用於影片編碼、日誌分析、機器學習資料處理等應用

5. Elasticsearch Trigger

Elasticsearch 是一個強大的全文搜尋與分析引擎,常用於日誌分析、搜尋引擎、指標監控等場景。KEDA 可以根據 Elasticsearch 查詢結果 來決定是否擴展 Kubernetes 的 Pod,例如根據索引中的文件數量、查詢命中數量、搜尋頻率等條件觸發自動擴展。


YAML 範例

這個範例示範如何根據 索引中的未處理日誌數量 來觸發擴展應用程式:

apiVersion: v1
kind: Secret
metadata:
name: elasticsearch-secrets
type: Opaque
data:
password: XXXXXXXX # Elasticsearch 登入密碼(Base64 編碼)
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: keda-trigger-auth-elasticsearch-secret
spec:
secretTargetRef:
- parameter: password
name: elasticsearch-secrets
key: password
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: elasticsearch-scaledobject
namespace: default
spec:
scaleTargetRef:
name: log-processor # 要擴展的 Deployment
minReplicaCount: 1
maxReplicaCount: 15
pollingInterval: 10 # 每 10 秒檢查一次 Elasticsearch
triggers:
- type: elasticsearch
authenticationRef:
name: keda-trigger-auth-elasticsearch-secret
metadata:
addresses: http://elasticsearch.default.svc:9200
username: my_user # 直接在 metadata 設定 Elasticsearch 使用者名稱
index: logs
searchTemplateName: my_search_template # Elasticsearch Search Template 名稱
valueLocation: 'hits.total.value' # 結果中要比對的欄位
threshold: '1000' # 當查詢結果超過 1000 則時觸發擴展
params: '{"status": "pending"}' # 給 Search Template 的參數

參數說明

參數作用
addressesElasticsearch 伺服器位址(通常是 http://elasticsearch.default.svc:9200)。
usernameElasticsearch 登入使用者名稱,這裡直接寫在 metadata,而不是存入 Secret。
index監聽的 Elasticsearch 索引(如 logsorders)。
searchTemplateNameElasticsearch Search Template 名稱,用於預定義查詢模板,減少查詢複雜度。
valueLocation要比對的查詢結果欄位,例如 "hits.total.value" 指的是匹配的文件數量。
threshold當查詢結果數量超過該值時觸發擴展
params提供給 Search Template 的參數,例如 '{"status": "pending"}'

適用場景

  1. 日誌分析與處理
    • 根據 Elasticsearch 中待處理的日誌數量 擴展日誌分析服務
    • 避免日誌積壓,確保日誌處理速度跟上流量
  2. 搜尋請求監控
    • 根據搜尋請求的 命中數量或頻率 來調整搜尋 API 的 Pod 數量
    • 確保高搜尋流量時提供穩定效能
  3. 訂單或交易處理
    • 監控 Elasticsearch 中的待處理訂單,根據積壓數量擴展 API 或背景工作者
    • 確保系統能應對突發的大量交易請求

KEDA 的彈性伸縮:0→1 及 1→0 縮放解析

KEDA(Kubernetes Event-Driven Autoscaling)是一種基於事件驅動的自動伸縮機制,能夠根據特定指標自動調整 Pod 的數量。 與 HPA(Horizontal Pod Autoscaler)不同,KEDA 可以將 Deployment 完全縮減至 0,並在負載到達門檻時 從 0 啟動 Pod,實現真正的動態資源調配。


0 → 1:從 0 啟動 Pod

這個過程通常發生在系統空閒一段時間後,當觸發條件達到指定門檻時,KEDA 會從 0 個 Pod 啟動 1 個 Pod,使應用程式開始處理請求。

步驟解析

  1. KEDA 監聽 Scaler 指標(如 Kafka 訊息數、Elasticsearch 查詢結果、RabbitMQ 佇列長度等)。
  2. 當指標超過 threshold 值時,KEDA 啟動 ScaledObject,並通知 Kubernetes 增加 scaleTargetRef(目標 Deployment)的副本數。
  3. Kubernetes 啟動第一個 Pod,拉取所需的應用程式映像並開始執行。
  4. Pod 開始處理負載,同時 KEDA 繼續監測指標數據,以決定是否需要進一步擴展。

關鍵影響因素

  • 冷啟動時間(Cold Start):從 0 啟動一個 Pod 需要時間,包含**容器拉取映像(image pull)、初始化(init)、健康檢查(readiness probe)**等,因此應用程式應優化啟動速度,例如:
    • 使用 輕量級映像(如 Alpine) 減少拉取時間。
    • 預熱應用程式(Warm-Up Strategy)。
    • 使用 readinessProbe 避免服務未就緒時收到請求。
  • Polling 間隔影響啟動時機:KEDA 預設 pollingInterval30 秒(可調整),這代表 KEDA 最多可能要等待 30 秒才會偵測到負載,這點在即時性應用(如 Webhook 或事件處理系統)中需要特別留意。

1 → 0:從 1 縮減至 0

當負載下降時,KEDA 會將 Pod 數量降至 0,釋放資源,避免不必要的計算成本。

步驟解析

  1. KEDA 持續監測 Scaler 指標,如果數值低於 threshold(通常是 0 或極低值),則進行縮減。
  2. 等待 cooldownPeriod 過後(預設 5 分鐘),KEDA 觸發 scaleTargetRef,將 Deployment 副本數降至 0。
  3. Kubernetes 停止所有 Pod,釋放 CPU、記憶體等資源,節省成本。

關鍵影響因素

  • cooldownPeriod 控制何時縮減
    • 預設值為 300 秒(5 分鐘),但可根據應用需求調整。
    • 設定過短可能導致頻繁縮放,影響服務穩定性。
  • 應用程式狀態保持
    • 如果應用程式需要維持持續性連線(如 WebSocket),縮減至 0 可能會中斷連線,這時可以考慮:
      • 設定 minReplicaCount: 1,避免完全關閉。
      • 配合 外部快取(如 Redis) 存儲狀態資訊,避免影響後續請求。
  • 與 HPA 的衝突
    • 若 HPA 設定了 minReplicas: 1,則即使 KEDA 嘗試縮減到 0,HPA 仍可能維持 1 個 Pod。
    • 因此,若希望完全縮減至 0,應停用 HPA 或將 minReplicas: 0

KEDA 的激活階段與縮放階段解析

KEDA 的核心運作機制包含兩個主要階段:激活階段(Activation Phase)縮放階段(Scaling Phase),這兩個階段的運作決定了 Pod 何時應該啟動或縮減,並且可以透過 激活閥值(Activation Thresholds)縮放閥值(Scaling Thresholds) 來精確控制。


激活階段(Activation Phase)

當 KEDA 監聽的 Scaler 指標 超過激活閥值(Activation Threshold),並且 minReplicaCount = 0 時,系統進入 激活階段,開始從 0 啟動 Pod,準備處理即將到來的負載。

關鍵點

  • activationThreshold:定義 Pod 從 0 啟動的負載門檻,低於此值時 Pod 保持為 0,不進入激活階段。
  • Scaler 監聽目標負載(如 RabbitMQ 佇列長度、Kafka 訊息數、Elasticsearch 查詢結果)。
  • 當負載達到 activationThreshold,KEDA 啟動 Deployment,Pod 數量變為 1(或 minReplicaCount)。

影響因素

  1. 冷啟動(Cold Start)
    • 由於 Pod 需要時間啟動,設定 activationThreshold 時需考慮應用程序的冷啟動時間。
    • 解決方案:使用較小的 activationThreshold 以提前觸發擴展,或者優化應用啟動速度。
  2. minReplicaCount >= 1 的影響
    • minReplicaCount >= 1 時,KEDA Scaler 將 永遠處於激活狀態,此時 activationThreshold 將被忽略
    • 這意味著即使負載為 0,Pod 也不會縮減到 0,而是至少保持 minReplicaCount 個 Pod 存在。

縮放階段(Scaling Phase)

當負載達到 縮放閥值(Scaling Threshold),KEDA 會調整 Pod 數量,確保應用程式的資源利用最佳化。

關鍵點

  • scalingThreshold:定義 Pod 數量變動的負載門檻,低於該值時 Pod 可能縮減,高於該值時 Pod 可能擴展。
  • 根據 scalingThreshold 動態調整 Pod 數量,確保應用程式擁有足夠的資源來處理當前負載。
  • 當負載低於 scalingThreshold,並且 cooldownPeriod 到期後,Pod 可能被縮減(但不會小於 minReplicaCount

影響因素

  1. cooldownPeriod 控制 Pod 何時縮減
    • 預設 cooldownPeriod5 分鐘,即當負載低於 scalingThreshold 並持續 5 分鐘後,Pod 才會縮減。
    • cooldownPeriod 設定過短,可能導致 Pod 頻繁縮放,影響穩定性。
    • 解決方案:根據負載特性適當調整 cooldownPeriod,避免過度縮放導致資源波動。
  2. Pod 數量變化
    • Pod 數量的變動範圍在 minReplicaCountmaxReplicaCount 之間。
    • 當負載增加,Pod 數量可以擴展到 maxReplicaCount;當負載下降,Pod 數量最少縮減至 minReplicaCount(若 minReplicaCount = 0,則可以縮減到 0)。

minReplicaCount 的影響

minReplicaCount 的設定影響 KEDA Scaler 是否會完全進入激活與縮放狀態:

minReplicaCount 設定行為
0Scaler 可以縮減到 0,activationThreshold 有效,Pod 會在負載達到門檻時啟動。
≥ 1Scaler 永遠處於激活狀態,Pod 不會縮減至 0,activationThreshold 將被忽略

這表示:

  • 若希望 Deployment 在低負載時完全關閉,應設置 minReplicaCount: 0,並根據 activationThreshold 控制 Pod 啟動。
  • 若應用程式需要 持續運行至少 1 個 Pod,則應設置 minReplicaCount ≥ 1,但這樣 activationThreshold 就不再影響縮放行為。

實作一個 KEDA

安裝 KEDA

  1. 首先我們透過 helm 來安裝 KEDA,我們增加 KEDA 的 repo 位置,接下來跟新 helm 的 repo,就可以下載 KEDA。
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace keda --create-namespace

部屬 Redis

  1. 首先,我們建立一個 Redis 作為簡單的訊息佇列,提供事件驅動的資料來源。
redis.yaml
apiVersion: v1
kind: Service
metadata:
name: redis
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis
ports:
- containerPort: 6379
  1. 透過指令來運行他。
kubectl.exe apply -f redis.yaml
---

service/redis created
deployment.apps/redis created

部屬 Worker 和 ScaledObject

  1. 我們定義一個 worker 的 Pod,用來處理 Redis 的任務。
worker.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
spec:
replicas: 0
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
containers:
- name: worker
image: redis:alpine # 使用 Redis 輕量級 Alpine 版本
command:
[
'sh',
'-c',
'while true; do job=$(redis-cli -h redis -p 6379 LPOP jobs); if [ "$job" != "" ]; then echo "Processing job: $job"; else echo "No jobs in the queue"; fi; sleep 5; done',
]
  1. 接下來我們定義 ScaledObject,我希望讓 KEDA 監聽 Redis 佇列的長度,當訊息數超過 5,Pod 會擴展,必且沒有用到的時候保持 Replica 數量為 0。
scaledobject.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: redis-scaledobject
spec:
scaleTargetRef:
name: worker # 這個是要縮放的 Deployment 名稱
minReplicaCount: 0
maxReplicaCount: 5
pollingInterval: 5
cooldownPeriod: 60
triggers:
- type: redis
metadata:
address: redis.default.svc.cluster.local:6379
listName: jobs # Redis 監聽的 key
listLength: '5' # 當 jobs 佇列長度超過 5 時,觸發擴展
activationListLength: '5' # 當 jobs 佇列長度超過 5 時,觸發0變成1
  1. 然後透過指令執行這兩個物件。
kubectl.exe apply -f worker.yaml,scaledobject.yaml
---

deployment.apps/worker created
scaledobject.keda.sh/redis-scaledobject created
  1. 透過指令來觀察 pod 的建立,確認會不會在訊息長度大於 5 的時候啟動一個 Pod。
kubectl.exe get pods -w
---

NAME READY STATUS RESTARTS AGE
redis-9cfbb989d-77ffk 1/1 Running 0 43m

測試 KEDA 自動縮放

  1. 首先進到 Redis 的 Pod 裡面,我們需要透過指令的方式增加訊息。
kubectl exec -it $(kubectl get pod -l app=redis -o jsonpath="{.items[0].metadata.name}") -- redis-cli
---

127.0.0.1:6379>
  1. 接下來我們 LPUSH 三個訊息。
127.0.0.1:6379> LPUSH jobs "task1" "task2" "task3"

---
(integer) 3
  1. 我們可以發現 Pod 沒有被建立,因為還未超過規定的長度 5。

  2. 接下來我們在 LPUSH 三個訊息,這個時候訊息的長度就超過 5。

127.0.0.1:6379> LPUSH jobs "task4" "task5" "task6"
---

(integer) 6
  1. 我們可以看到當長度大於5,Pod就會被建立,replica從0變成1,每60秒會檢查如果長度小於5,replica就會從1變成0,Pod也都被關閉。
kubectl.exe get pods -w
---

NAME READY STATUS RESTARTS AGE
redis-9cfbb989d-77ffk 1/1 Running 0 43m
worker-84d6b8f7bd-5pwmz 0/1 Pending 0 0s
worker-84d6b8f7bd-5pwmz 0/1 Pending 0 0s
worker-84d6b8f7bd-5pwmz 0/1 ContainerCreating 0 0s
worker-84d6b8f7bd-5pwmz 1/1 Running 0 1s
worker-84d6b8f7bd-5pwmz 1/1 Terminating 0 60s
worker-84d6b8f7bd-5pwmz 0/1 Terminating 0 91s
worker-84d6b8f7bd-5pwmz 0/1 Terminating 0 91s
worker-84d6b8f7bd-5pwmz 0/1 Terminating 0 91s
worker-84d6b8f7bd-5pwmz 0/1 Terminating 0 91s
  1. 我們可以同時觀察pod的輸出,可以看到他從task6一直消化道task1,最後沒有,之後就被刪除了。
kubectl.exe logs pods/worker-84d6b8f7bd-5pwmz -f
---

Processing job: task6
Processing job: task5
Processing job: task4
Processing job: task3
Processing job: task2
Processing job: task1
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
No jobs in the queue
rpc error: code = Unknown desc = Error: No such container: dab99425c4efafbefb39717d0815adfff495049a944c290e65be4e0cd2d6d6ac
  1. 接下來我們一次建立100個task。
kubectl exec -it $(kubectl get pod -l app=redis -o jsonpath="{.items[0].metadata.name}") -- /bin/sh
---

#
# redis-cli eval "return redis.call('LPUSH', KEYS[1], unpack(ARGV))" 1 jobs $(seq -f "task%g" 1 100)
---

(integer) 100
  1. 透過觀察deployment,我們可以看到Pod從0個建立到5個,直到消耗完畢後變回0個。
kubectl.exe get deployments worker -w
---


kubectl.exe get deployments worker -w
NAME READY UP-TO-DATE AVAILABLE AGE
worker 0/0 0 0 51m
worker 0/1 0 0 51m
worker 0/1 0 0 51m
worker 0/1 0 0 51m
worker 0/1 1 0 51m
worker 1/1 1 1 51m
worker 1/4 1 1 52m
worker 1/4 1 1 52m
worker 1/4 1 1 52m
worker 1/4 4 1 52m
worker 2/4 4 2 52m
worker 3/4 4 3 52m
worker 4/4 4 4 52m
worker 4/5 4 4 53m
worker 4/5 4 4 53m
worker 4/5 4 4 53m
worker 4/5 5 4 53m
worker 5/5 5 5 53m
worker 5/0 5 5 54m
worker 5/0 5 5 54m
worker 0/0 0 0 54m

結論

KEDA 讓 Kubernetes 能夠以事件為驅動,動態調整應用程式的資源配置,特別適合處理非持續性的負載,如訊息佇列、監控警報等。透過與 HPA 的無縫整合,它為開發者提供了一種靈活且高效的自動擴展方案,使 Kubernetes 應用更具彈性與可擴展性。