iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Cloud Native

EKS in Practice:IaC × GitOps 實戰 30 天系列 第 20

[Day 20] 事件驅動的魔法:KEDA 讓 Pod 自己長大縮小

  • 分享至 

  • xImage
  •  

前言

在 Day 19 我們談到監控與告警,讓我們可以「看到」叢集發生了什麼事,也能在異常時即時收到提醒。但除了被動監控之外,叢集其實還需要一種 主動調整資源 的能力 —— 這就是 KEDA (Kubernetes Event-Driven Autoscaler) 的價值所在。

在我們的架構中,其實同時存在 Karpenter 和 KEDA 兩種 autoscaler:Karpenter 關注 Node 層級(決定要開幾台 EC2),KEDA 關注 Pod 層級(決定 Deployment 要幾個副本)。兩者搭配能實現從業務事件到基礎設施的 end-to-end 自動化。

今天我們聚焦在 KEDA,深入了解它的核心概念與實作。文章將從 KEDA 與 HPA 的關係開始,接著詳細探討 ScaledObject 的兩階段運作機制(Activation Phase 與 Scaling Phase),並說明如何實現 scale-to-zero 功能。我們也會介紹 KEDA 豐富的 Trigger 生態系統,透過 Cron Trigger 的實戰範例了解具體應用,最後分享如何在多叢集架構中部署和管理 KEDA。

KEDA 是什麼?它和 HPA 又有什麼關係?

在 Kubernetes 裡,大家最熟悉的自動擴縮機制是 HPA (Horizontal Pod Autoscaler),它根據 CPU、Memory 使用率調整 Pod 副本數。但 HPA 只看「資源使用率」這個角度。

KEDA 則是把 HPA 擴充到「事件驅動」:它透過各種 Trigger 觀察事件來源(Queue 長度、Kafka 訊息數等),當條件達到臨界值時,動態產生或更新 HPA。可以把 KEDA 想像成替 HPA 長出更多感知能力的外掛。

核心概念與運作原理

KEDA 的架構圍繞著一個核心概念:ScaledObject。他是 event 和 HPA 的介面,讓我們能透過定義一到多個 event triggers 來操控 HPA,達到 scaling 的效果。概念上分為兩個階段:

  1. Activation Phase:由 KEDA operator 負責 0 ↔ 1 scaling(上圖中灰線箭頭)
  2. Scaling Phase:由 HPA 負責 1 ↔ N scaling(上圖中黃線箭頭)

會這樣切分是因為 HPA 本身不支援 minReplicaCount = 0。KEDA 部分會取決於 IsActive function 的執行結果,來判斷現在是否要處理 0 → 1 scaling。

那使用 KEDA 要怎麼 scale 到 0?

根據前面的邏輯,如果要 scale 到 0,必須要讓現狀滿足以下兩個條件:

  1. 現狀不符合任何 triggers:若現狀有符合任一個 trigger 條件,KEDA 會把目標 workload 的 replicas 從 0 喚醒到 1,然後交給 HPA 去處理 desired 數量。

  2. minReplicaCountidleReplicaCount 必須設置為 0:接著 KEDA 會 disable HPA,並且直接把 Deployment replica 設置為 0。

    minReplicaCount vs idleReplicaCount 的差異

    • minReplicaCount:在 Scaling Phase(有任何 trigger 被觸發時)最小的 replicaCount
    • idleReplicaCount:在 Activation Phase(沒有觸發任何 trigger 時)時的 replicaCount(現在這個值只能是 0,詳見 GitHub Issue

在這個情況下,儘管我們使用 kubectl 把 replica count 設置成 0 以外的數字,KEDA 還是會在下一輪 polling trigger 時(發現沒有觸發任何 trigger 後),一樣將 replica count 設置為 0。

重要觀念:基本上寫 ScaledObject triggers 要以 scale up 的角度來寫,所有條件都不符合才會 scale down。

Applying Order:基於現狀選最大

因為我們在同個 ScaledObject 裡面可以同時設定一到多個 triggers,幾本上只要現狀有符合任何一個 trigger,那現在這個 ScaledObject 就會處於 Scaling Phase,也就是交由 HPA 來做 scaling。

而在一個 ScaledObject 裡面,所有的 triggers 都會被轉成 HPA 的 metrics(ie, ScaledObject 裡面有五個 triggers,那他長出來的 HPA 就會有五個 metrics)。HPA 會對每個指標各自算出期望副本數,最後取「最大值」作為本輪期望副本數;若有任一指標無法取到數值而其他指標建議縮容,會跳過縮容

細節可以看 HPA 官方文件的解釋:Horizontal Pod Autoscaling

KEDA 的運作流程

概念介紹的最後讓我用流程圖來 sum up KEDA 的完整運作機制:

[Start: KEDA ScaledObject]
   │
   ▼
(1) KEDA 輪詢每個 trigger (pollingInterval)
   ├─ Trigger A → active 嗎?
   ├─ Trigger B → active 嗎?
   └─ Trigger C → active 嗎?
   │
   ▼
(2) 判斷「是否有任何一個 trigger active?」
   ├─ 是 → 進入「活動模式 Scaling Phase」
   │       │
   │       ├─ 如果目前 replicas = 0 → KEDA 直接把目標 workload 設成 ≥1
   │       └─ 將所有 triggers 轉換成 HPA 指標 (external metrics)
   │       └─ HPA 接管:
   │             ├─ 計算每個指標的期望 replicas,取最大值
   │             └─ 若計算結果 < minReplicaCount → 強制維持在 minReplicaCount
   │
   └─ 否 → 進入「非活動模式 Activation Phase」
   │       │
   │       └─ 有設定 idleReplicaCount ?
   │               ├─ 是 → replicas = idleReplicaCount
   │               └─ 否 → replicas = minReplicaCount
   │                         (如果 =0,就縮到 0)
   │
   ▼
(3) 重複下一輪 pollingInterval / HPA syncPeriod

KEDA 的 Triggers

KEDA 支援的事件來源非常多,常見的有:

  • 消息佇列:Azure Queue Storage、RabbitMQ、Kafka、AWS SQS
  • 資料庫:MySQL、PostgreSQL、MongoDB 查詢數量
  • 雲服務:AWS CloudWatch、Azure Monitor、Google Pub/Sub
  • HTTP / gRPC:請求速率
  • 時間排程:Cron 表達式
  • 自訂:任何符合 Prometheus 格式的 metrics

完整清單可以參考官方文件 👉 KEDA Scalers

這也是 KEDA 的強大之處 —— 幾乎任何「能被量化的事件」都可以成為伸縮依據。

實戰範例:Cron Trigger

讓我們看一個實際的例子:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: gitlab-runner-arm-1
  namespace: gitlab-runner
spec:
  cooldownPeriod: 10
  idleReplicaCount: 0
  initialCooldownPeriod: 0
  maxReplicaCount: 4
  minReplicaCount: 0
  pollingInterval: 30
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: arm-gitlab-runner-gitlab-runner
  triggers:
    - type: cron
      metadata:
        desiredReplicas: "1"  *# 如果現況大於 1 且符合 trigger 條件,則不會改變數量*
        end: 0 18 * * mon-fri
        start: 0 10 * * mon-fri
        timezone: Asia/Taipei

效果:週一到週五的 10:00 ~ 18:00,HPA 會確保我們的 replica ≥ 1。超過這些時間段以外,KEDA 會確保我們的 replica = 0。

但是他會直接去把 deployment replica count 改成 0,就算我們手動改成 1 還是會被 KEDA 改回去。解決方法為,暫時讓 KEDA 的 ScaledObject 停止作用:

*# ScaledObject 停止作用*
kubectl annotate scaledobject gitlab-runner-arm-1 autoscaling.keda.sh/paused=true -n gitlab-runner --overwrite

*# 加班完之後 ScaledObject 開始作用*
kubectl annotate scaledobject gitlab-runner-arm-1 autoscaling.keda.sh/paused=false -n gitlab-runner --overwrite

部署策略:ApplicationSet for Multi-cluster

因為 KEDA 是基礎設施的一部分,我們選擇在多個叢集都安裝它。延續 Day 15 Karpenter 和 Day 19 Prometheus Stack 的架構模式,我們的做法是:

  • Shared values:安裝 KEDA 的基礎設定(例如 controller replica、namespace、nodeSelector)
  • Cluster values:在 values.yaml 中寫一個 ScaledObject list,每一個元素就是一個 workload 的 autoscaling 定義。當然也會在 cluster values 中設定 KEDA 所需要的 cluster 資訊。

在 Helm chart templating 時,會 iterate 這個 list,幫我們自動建立多個 ScaledObject。

實際的 Values 配置

*# https://github.com/kedacore/charts/tree/v2.17.2/keda*
keda:
  *# -- Kubernetes cluster name. Used in features such as emitting CloudEvents*
  clusterName: example-internal
  nodeSelector:
    usage: system
  tolerations:
    - key: SystemOnly
      operator: Exists
  prometheus:
    metricServer:
      enabled: true
      serviceMonitor:
        enabled: true

scaledObjects:
  - name: gitlab-runner-arm
    namespace: gitlab-runner
    target:
      kind: Deployment
      name: arm-gitlab-runner-gitlab-runner
    minReplicaCount: 1
    maxReplicaCount: 3
    triggers:
      - type: cron
        metadata:
          timezone: Asia/Taipei
          start: 0 10 * * mon-fri  *# At 10:00 AM*
          end: 0 18 * * mon-fri    *# At 6:00 PM*
          desiredReplicas: "2"
  - name: gitlab-runner-x86
    namespace: gitlab-runner
    target:
      kind: Deployment
      name: x86-gitlab-runner-gitlab-runner
    minReplicaCount: 1
    maxReplicaCount: 3
    triggers:
      - type: cron
        metadata:
          timezone: Asia/Taipei
          start: 0 10 * * mon-fri  *# At 10:00 AM*
          end: 0 18 * * mon-fri    *# At 6:00 PM*
          desiredReplicas: "2"

和 Karpenter、Prometheus Stack 一樣,因為我們希望在部署 KEDA Helm Chart 的同時一併部署 ScaledObject CRD,所以 values file 才會包含 ScaledObject 的定義。

這樣做的好處是:

  • 集中管理:所有 autoscaling 設定都可以版本化放在 repo
  • 跨叢集一致:相同的 workload autoscaling 策略能快速套用到不同環境
  • 彈性擴充:要新增新的 ScaledObject,只要改 values file,不用自己手動套 yaml
  • GitOps 友善:所有配置都在版控中,符合我們的 GitOps 理念

小結

KEDA 讓我們的叢集從「資源驅動」走向「事件驅動」的自動化:

  • 它不是取代 HPA,而是替 HPA 增加新的眼睛
  • 支援各種觸發條件,讓 autoscaling 能夠基於更貼近業務的訊號(例如 queue 長度、API 呼叫速率、時間排程)
  • 搭配 ApplicationSet + Helm templating,就能把 autoscaling 策略統一、版本化,並套用到多叢集
  • 與 Karpenter 形成完美配合,實現從 Pod 到 Node 的全方位自動化

透過 ScaledObject 的兩階段機制(Activation Phase + Scaling Phase),KEDA 解決了 HPA 無法縮放到 0 的限制,讓我們能實現真正的 serverless 體驗。配合豐富的 Trigger 生態系統,幾乎任何可量化的事件都能成為擴縮依據。

在 Day 21,我們將轉向另一個重要主題 —— CI/CD 流程的建構,看看如何在我們的 GitOps 架構中實現自動化的持續整合與部署。


上一篇
[Day 19] 叢集的千里眼:Prometheus & Grafana 讓一切被看見
系列文
EKS in Practice:IaC × GitOps 實戰 30 天20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言