KEDA — Kubernetes Event-Driven Autoscaling
Khoảng trống mà KEDA lấp
HPA (file 1, 2) là công cụ mạnh nhưng có hai giới hạn cố hữu với kiến trúc event-driven:
- HPA không scale-to-zero.
minReplicascủa HPA tối thiểu là 1 — nó không bao giờ đưa workload về 0 replica. Với một worker chỉ chạy khi có message trong hàng đợi, giữ tối thiểu 1 Pod chạy 24/7 dù hàng đợi trống là lãng phí thuần túy. - External metric của HPA cồng kềnh. Để HPA scale theo độ sâu hàng đợi Pub/Sub, kích thước Cloud Tasks queue, hay lag Kafka, bạn phải tự dựng và vận hành một external metrics adapter cho từng nguồn — phức tạp và dễ vỡ.
KEDA (Kubernetes Event-Driven Autoscaling) lấp đúng hai khoảng trống này. Nó cho phép scale-to-zero và cung cấp sẵn hàng chục scaler cho các nguồn sự kiện phổ biến (Pub/Sub, Cloud Tasks, Kafka, Prometheus, BigQuery...). KEDA không thay thế HPA — như sẽ thấy, nó dùng HPA bên dưới — mà mở rộng nó để xử lý đúng lớp workload event-driven.
Điểm khác biệt vận hành quan trọng so với mọi autoscaler khác trong chương: KEDA là add-on bạn tự cài và tự nâng cấp, không phải component do Google quản lý trên control plane. GKE hỗ trợ KEDA (có thể cài qua add-on hoặc Helm), nhưng vòng đời của nó nằm ở phía bạn.
Kiến trúc KEDA: ba thành phần và mối quan hệ với HPA
KEDA gồm các thành phần chính:
- keda-operator (controller): theo dõi các
ScaledObject/ScaledJob, và tạo/quản lý một đối tượng HPA tương ứng cho mỗiScaledObject. Đây là phần "agent" của KEDA. - keda-operator-metrics-apiserver (metrics adapter): expose metric từ các external source dưới dạng API
external.metrics.k8s.iođể HPA (do KEDA tạo) đọc. Đây là cầu nối giữa nguồn sự kiện và HPA. - admission webhooks: validate
ScaledObject/ScaledJoblúc tạo (ví dụ ngăn nhiều ScaledObject cùng trỏ một workload).
Cơ chế cốt lõi cần khắc sâu: KEDA tạo HPA bên dưới. Theo tài liệu KEDA (Scaling Deployments), KEDA giám sát nguồn sự kiện và đưa dữ liệu cho Kubernetes và HPA để thực hiện scale. Cụ thể: với mỗi ScaledObject, KEDA tạo một HPA tên keda-hpa-{scaled-object-name}, và HPA đó scale dựa trên metric mà metrics-apiserver của KEDA cung cấp.
Hệ quả của thiết kế này:
- Khi replica từ 1 trở lên, chính HPA điều khiển scale — nghĩa là toàn bộ kiến thức ở file 1, 2 (thuật toán, behavior, stabilization) vẫn áp dụng. Bạn vẫn cấu hình
behaviorqua KEDA. - KEDA chỉ đặc biệt ở ranh giới 0↔1 — phần mà HPA không làm được. Đây là "pha activation" (bên dưới).
ScaledObject vs ScaledJob
KEDA có hai đối tượng cho hai mô hình workload khác nhau:
ScaledObject: scale một workload chạy liên tục (Deployment, StatefulSet, hoặc custom resource có subresource/scale). Phù hợp worker tiêu thụ message liên tục — số replica tăng/giảm theo backlog, kể cả về 0. Đây là loại phổ biến nhất.ScaledJob: tạo Kubernetes Job cho mỗi đơn vị công việc rời rạc. Phù hợp khi mỗi message cần một lần xử lý độc lập, dài, không idempotent với việc chia sẻ process — mỗi event sinh một Job riêng, chạy tới hoàn thành rồi biến mất. Khác biệt bản chất:ScaledObjectđiều chỉnh replica của một workload đang chạy;ScaledJobsinh ra các Job mới.
Chọn sai gây vấn đề: dùng ScaledObject cho công việc batch dài (Pod bị scale-down giết giữa chừng) hay dùng ScaledJob cho stream message liên tục (overhead tạo Job cho mỗi message) đều sai mô hình.
Scale-to-zero: hai pha activation và scaling
Đây là tính năng đặc trưng và là lý do chính người ta dùng KEDA. Theo tài liệu KEDA (Scaling Deployments), có hai pha tách biệt:
Pha activation (deactivation/activation)
Là thời điểm KEDA quyết định scale từ 0 lên hay về 0. Đây là phần KEDA tự xử lý, không phải HPA (HPA không làm được 0↔1). KEDA poll nguồn sự kiện theo pollingInterval; khi phát hiện có việc (metric vượt activationThreshold), nó kích hoạt workload từ 0 lên 1. Khi không còn việc và qua cooldownPeriod, nó đưa về 0.
Pha scaling
Sau khi đã ở 1 replica trở lên, HPA tiếp quản việc quyết định scale (1→N→1). KEDA chỉ cung cấp metric; HPA tính desiredReplicas theo đúng thuật toán file 1.
activationThreshold vs threshold — phân biệt then chốt
Đây là điểm gây nhầm lẫn nhất:
threshold(giá trị mục tiêu của scaler): dùng trong pha scaling để HPA tính số replica — tương tự target của HPA.activationThreshold(tùy chọn, mặc định 0): dùng trong pha activation để quyết định có rời khỏi 0 hay không.
Ví dụ cụ thể: một Pub/Sub scaler với value: 5 (mỗi replica xử lý ~5 message tồn) và activationThreshold: 10. Nghĩa là: KEDA chỉ kích hoạt từ 0 lên 1 khi backlog vượt 10 message; sau khi đã chạy, HPA scale để giữ ~5 message/replica. Đặt activationThreshold cao hơn 0 tránh "flapping quanh 0" — không bật dậy chỉ vì 1 message lẻ.
Các default quan trọng của ScaledObject
Theo spec KEDA (ScaledObject spec):
| Trường | Mặc định | Ý nghĩa |
|---|---|---|
pollingInterval | 30 giây | KEDA poll nguồn sự kiện mỗi 30s (quyết định độ nhạy activation) |
cooldownPeriod | 300 giây | Chờ sau lần cuối có hoạt động trước khi về 0 |
initialCooldownPeriod | 0 giây | Cooldown ngay sau khi tạo ScaledObject |
minReplicaCount | 0 | Số replica tối thiểu — 0 nghĩa là scale-to-zero |
maxReplicaCount | 100 | Trần replica |
idleReplicaCount | (bỏ qua) | Phải nhỏ hơn minReplicaCount nếu đặt |
fallback.failureThreshold / replicas | (bắt buộc nếu có fallback) | Số replica dùng khi scaler lỗi |
advanced.restoreToOriginalReplicaCount | false | Khi xóa ScaledObject, có khôi phục replica gốc không |
Hai điểm vận hành quan trọng:
pollingIntervalquyết định độ trễ activation từ 0. Mặc định 30s nghĩa là trong trường hợp xấu nhất, một message tới hàng đợi trống có thể đợi tới ~30s trước khi KEDA kích hoạt Pod đầu tiên — cộng thêm cold start của Pod. Đây là cái giá của scale-to-zero (xem trade-off bên dưới).fallbackcực kỳ quan trọng cho production: nếu nguồn metric (Pub/Sub API, Prometheus) tạm thời lỗi, không có fallback nghĩa là KEDA không lấy được metric → workload có thể bị kẹt sai số replica.fallbackcho phép "giữ N replica an toàn" khi scaler lỗi.
Cấu hình behavior của HPA bên dưới vẫn được, qua advanced.horizontalPodAutoscalerConfig.behavior — nghĩa là toàn bộ behavior policy ở file 2 áp dụng cho phần scaling của KEDA.
Pub/Sub scaler: scale theo backlog
Scaler GCP-native phổ biến nhất. Nó scale dựa trên độ trễ/backlog của subscription — tín hiệu phản ánh đúng "lượng việc đang chờ", chứ không phải CPU. Theo tài liệu KEDA (GCP Pub/Sub scaler):
mode: metric để scale, là PascalCase của tên metric GCP. Phổ biến nhất:SubscriptionSize(mặc định) — số message chưa giao (num_undelivered_messages), tức backlog.OldestUnackedMessageAge— tuổi của message cũ nhất chưa ack, phản ánh độ trễ xử lý.
value: mục tiêu mỗi replica (mặc định 10). Ví dụvalue: 5nghĩa là KEDA/HPA cố giữ ~5 message tồn cho mỗi replica.subscriptionName(hoặctopicName): nguồn cần theo dõi.aggregation: hàm tổng hợp (mean, sum, percentileX...) — bắt buộc cho metric kiểu distribution.
Ví dụ:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: pubsub-worker
spec:
scaleTargetRef:
name: pubsub-worker
minReplicaCount: 0
maxReplicaCount: 50
cooldownPeriod: 120
triggers:
- type: gcp-pubsub
metadata:
mode: "SubscriptionSize"
value: "5"
subscriptionName: "orders-subscription"
authenticationRef:
name: keda-gcp-authXác thực: nên dùng GCP Workload Identity (podIdentity: provider: gcp) thay vì credential JSON trong env — KEDA gọi Cloud Monitoring API để đọc metric subscription, và Workload Identity (Chương 13) là cách an toàn không cần key dài hạn.
Vì sao scale theo backlog đúng hơn CPU: một worker tiêu thụ message có thể CPU thấp nhưng backlog đang phình (ví dụ nghẽn ở downstream/DB). Scale theo CPU sẽ không phản ứng; scale theo SubscriptionSize hoặc OldestUnackedMessageAge phản ánh đúng áp lực thật và scale đúng lúc. OldestUnackedMessageAge đặc biệt tốt khi SLO của bạn là về độ trễ xử lý ("không message nào chờ quá X giây").
TriggerAuthentication và Workload Identity
Để tách cấu hình xác thực khỏi ScaledObject, KEDA dùng đối tượng TriggerAuthentication (hoặc ClusterTriggerAuthentication). Với GCP, pattern an toàn nhất là Workload Identity:
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: keda-gcp-auth
spec:
podIdentity:
provider: gcpScaledObject tham chiếu qua authenticationRef. KEDA operator (hoặc Pod được scale) dùng Google Service Account gắn qua Workload Identity (Chương 13) để gọi Cloud Monitoring API đọc metric Pub/Sub/Cloud Tasks. Không có key JSON dài hạn nào nằm trong cluster — đây là điểm hardening quan trọng so với credentialsFromEnv.
ScaledJob: một Job cho mỗi đơn vị công việc
Khi mỗi message là một công việc dài, độc lập, nên chạy tới hoàn thành (xử lý video, training ngắn, ETL một file lớn), ScaledJob đúng hơn ScaledObject. Nó tạo một Kubernetes Job cho mỗi đơn vị công việc thay vì điều chỉnh replica của một workload chạy liên tục:
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: video-encoder
spec:
jobTargetRef:
template:
spec:
containers:
- name: encoder
image: my-encoder:1.0
restartPolicy: Never
pollingInterval: 30
maxReplicaCount: 100 # tối đa số Job song song
scalingStrategy:
strategy: "default"
triggers:
- type: gcp-pubsub
metadata:
mode: "SubscriptionSize"
value: "1" # mỗi Job xử lý 1 message
subscriptionName: "encode-jobs"
authenticationRef:
name: keda-gcp-authKhác biệt bản chất với ScaledObject:
- Không bị scale-down giết giữa chừng: mỗi Job chạy tới hoàn thành rồi tự kết thúc.
ScaledObjectđiều chỉnh replica của workload đang chạy, nên scale-down có thể giết Pod đang xử lý dở — sai cho công việc dài không idempotent với việc bị ngắt. scalingStrategykiểm soát cách KEDA tính số Job cần tạo dựa trên số message tồn và số Job đang chạy.- Phù hợp workload "mỗi message = một lần chạy rời rạc"; không phù hợp stream message liên tục (overhead tạo Job mỗi message quá lớn — dùng
ScaledObject).
Prometheus scaler và external scalers khác
Prometheus scaler
Cho phép scale theo bất kỳ truy vấn PromQL nào. Đây là scaler linh hoạt nhất: nếu metric của bạn đã ở Prometheus (kể cả Google Cloud Managed Service for Prometheus), bạn scale theo một biểu thức PromQL tùy ý — RPS, p99 latency, queue depth nội bộ, business metric. Cấu hình gồm serverAddress, query (PromQL), và threshold.
Trên GKE, kết hợp KEDA Prometheus scaler với Managed Service for Prometheus (Chương 14) cho một pipeline scale rất mạnh: workload expose metric → Managed Prometheus thu thập → KEDA query PromQL → scale. Đây là cách scale theo "đơn vị công việc nghiệp vụ" mà không tự dựng custom metrics adapter.
Ví dụ Prometheus scaler scale theo p99 latency từ Managed Prometheus:
triggers:
- type: prometheus
metadata:
serverAddress: "https://monitoring.googleapis.com/v1/projects/PROJECT/location/global/prometheus"
query: "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[2m])) by (le))"
threshold: "0.5" # giữ p99 latency ~500ms
activationThreshold: "0.1"
authenticationRef:
name: keda-gcp-authĐây là dạng scale theo SLO trực tiếp: thay vì scale theo CPU/RPS và hy vọng latency ổn, bạn scale theo chính metric latency mà SLO cam kết. Khi p99 vượt ngưỡng, KEDA/HPA thêm Pod để kéo nó xuống. Cần thận trọng: scale theo latency là vòng phản hồi gián tiếp (thêm Pod không phải lúc nào cũng giảm latency nếu bottleneck ở downstream), nên thường kết hợp với một trigger thứ hai (RPS/queue) làm tín hiệu chính.
Các external scaler GCP khác
- Cloud Tasks queue: scale theo số task tồn trong một Cloud Tasks queue — tương tự Pub/Sub nhưng cho mô hình task/job. Phù hợp khi kiến trúc dùng Cloud Tasks làm hàng đợi công việc.
- BigQuery: scale theo kết quả một truy vấn BigQuery (ví dụ số dòng cần xử lý, độ trễ pipeline tính từ một bảng trạng thái). Hữu ích cho data pipeline mà tín hiệu tải nằm trong chính dữ liệu.
Mẫu hình chung của external scaler: chúng dịch một tín hiệu bên ngoài cluster hoàn toàn thành quyết định scale, kể cả scale-to-zero — điều HPA thuần rất khó làm gọn.
Trade-off cốt lõi: cold start của scale-to-zero
Scale-to-zero không miễn phí. Cái giá là độ trễ của request/message đầu tiên khi workload đang ở 0:
message tới (hàng đợi trống)
→ đợi tới chu kỳ poll tiếp theo (tối đa ~pollingInterval, mặc định 30s)
→ KEDA kích hoạt 1 replica
→ Pod Pending → có thể cần CA tạo node (phút, file 5)
→ Pod khởi động + kéo image + warm-up
→ bắt đầu xử lýTổng cold start có thể từ vài giây tới vài phút. Hệ quả thiết kế:
- Phù hợp: workload async/batch nơi độ trễ vài giây–phút cho item đầu chấp nhận được (xử lý hàng đợi, job định kỳ, pipeline). Tiết kiệm lớn khi idle nhiều.
- Không phù hợp: API đồng bộ phục vụ người dùng cần latency thấp ngay cả request đầu. Với loại này, đặt
minReplicaCount >= 1(KEDA vẫn dùng được cho phần scaling, chỉ không về 0), hoặc dùngidleReplicaCountgiữ một mức nền tối thiểu. - Giảm cold start: hạ
pollingInterval(đánh đổi: poll nguồn metric thường xuyên hơn, tốn API call/quota); kết hợp capacity buffer (file 7) để Pod không phải đợi tạo node; giữ image nhỏ và startup nhanh.
Real-world: hệ thống xử lý đơn hàng event-driven
Xét một hệ thống thương mại điện tử: đơn hàng đẩy vào Pub/Sub, một fleet worker xử lý (validate, tính thuế, gọi payment, ghi DB). Tải dao động cực mạnh — gần như 0 lúc 3 giờ sáng, spike khủng khiếp giờ flash sale.
Với HPA theo CPU: giữ tối thiểu vài Pod 24/7 (lãng phí ban đêm), và scale theo CPU không phản ánh backlog (worker chờ payment downstream thì CPU thấp dù đơn dồn).
Với KEDA Pub/Sub scaler:
minReplicaCount: 0→ ban đêm hàng đợi trống, fleet về 0, không tốn gì.mode: SubscriptionSize, value: 30→ mỗi replica giữ ~30 đơn tồn; backlog tăng thì scale ngang theo đúng lượng việc thật.OldestUnackedMessageAgelàm trigger thứ hai → đảm bảo không đơn nào chờ quá SLO (ví dụ 60s), kể cả khi tổng backlog chưa lớn.cooldownPeriodđủ dài để không flap khi traffic lác đác.- Kết hợp capacity buffer (file 7) để khi flash sale spike, Pod mới có node sẵn, cắt cold start.
Kết quả: chi phí gần 0 lúc nhàn rỗi, scale đúng theo backlog thật lúc cao điểm, và SLO độ trễ xử lý được bảo vệ bằng trigger tuổi message.
Hiểu đúng cooldownPeriod và quá trình về 0
cooldownPeriod (mặc định 300s) là một trong những trường hay bị hiểu sai nhất. Nó không phải khoảng giữa các lần scale thông thường (đó là việc của HPA stabilization, file 1). Nó là khoảng KEDA chờ sau lần cuối cùng có hoạt động (metric trên activationThreshold) trước khi đưa workload về 0.
Diễn giải chuỗi về 0:
- Backlog cạn, metric tụt dưới
activationThreshold. - HPA (do KEDA tạo) scale dần xuống
minReplicaCount— nhưng vìminReplicaCount: 0không hợp lệ cho HPA thuần (HPA tối thiểu 1), KEDA giữ ở 1 cho tới khi quyết định deactivate. - Sau
cooldownPeriodkhông có hoạt động, KEDA deactivate: xóa HPA tạm thời và đưa workload về 0.
Hệ quả: cooldownPeriod quá ngắn → workload bị đưa về 0 rồi lại bật dậy liên tục khi traffic lác đác (flapping quanh 0, tốn cold start lặp lại). Quá dài → giữ 1 Pod chạy không cần thiết lâu hơn mức tối ưu. Đặt cooldownPeriod dựa trên pattern "khoảng lặng" thật của hàng đợi: nếu message thường tới theo cụm cách nhau vài phút, đặt cooldown đủ dài để không tắt giữa hai cụm.
idleReplicaCount là biến thể tinh tế: thay vì về hẳn 0, giữ một mức nền (ví dụ 1) khi idle nhưng vẫn cho phép scale cao khi bận — dung hòa giữa tiết kiệm và cold start cho workload không thể chịu cold start hoàn toàn nhưng vẫn muốn tiết kiệm lúc nhàn.
Anti-patterns thường gặp
- Dùng scale-to-zero cho API đồng bộ latency-thấp. Cold start làm request đầu chậm/timeout. Đặt
minReplicaCount >= 1cho loại này. - Quên
fallback. Khi Pub/Sub/Prometheus API lỗi tạm thời, KEDA mất metric → scale sai.fallbackgiữ mức an toàn. - Scale theo CPU trong khi backlog là tín hiệu thật. Lặp lại sai lầm của HPA. Dùng
SubscriptionSize/OldestUnackedMessageAge/PromQL phản ánh việc thật. - Đặt
activationThreshold: 0(mặc định) rồi flap quanh 0. Một message lẻ bật cả workload dậy. Đặt ngưỡng activation hợp lý. - Dùng credential JSON dài hạn thay vì Workload Identity. Rủi ro bảo mật không cần thiết trên GKE.
- Để nhiều ScaledObject/HPA cùng trỏ một workload. Xung đột điều khiển; admission webhook của KEDA chặn phần lớn nhưng đừng cố lách.
- Quên rằng phần scaling vẫn là HPA. Mọi đặc tính stabilization/behavior (file 1, 2) áp dụng; cấu hình chúng qua
advanced.horizontalPodAutoscalerConfig.