Ringkasan Utama
Kubernetes 1.37 menjadikan HorizontalPodAutoscaler scale to zero sebagai fitur Beta yang aktif secara default. Dengan spec.minReplicas 0, queue consumer atau GPU worker bisa idle saat metric external atau object seperti queue lag bernilai nol. Fitur ini tidak bisa memakai CPU atau memori, tidak mem-buffer request HTTP, dan butuh metrics pipeline yang sehat untuk bangun kembali.
Queue worker yang cepat atau lambat dibuat tiap tim menghabiskan sebagian besar hidupnya untuk menunggu. Job datang bergelombang, Deployment diam di satu replica sepanjang hari, dan kalau tiap replica menahan satu GPU, jam-jam idle itulah yang paling mahal. Sampai rilis ini, HorizontalPodAutoscaler bawaan tidak bisa menyelesaikannya karena batas bawahnya satu replica.
Kubernetes v1.37 memindahkan batas bawah itu. Tulisan ini adalah pembacaan teliti atas apa yang dikatakan release post, dokumentasi HPA, blog post fitur ini, dan KEP-2021 tentang scale to zero: config yang bekerja, condition yang menjaganya tetap jujur, delay yang harus dibayar, dan di mana fitur ini berhenti. Saya belum menjalankan 1.37 di production, jadi semua angka di bawah dikutip dari sumber-sumber itu, dan ketika saya menalar melampaui sumber, saya katakan terus terang.
Apa yang berubah di 1.37, dan apa yang tidak
Release notes v1.37 menyebut HPA scale to zero pertama kali diperkenalkan di Kubernetes v1.16 dan naik ke Beta di v1.37, yang kini aktif secara default. Feature gate-nya HPAScaleToZero, dan dokumentasi HPA menyatakan gate itu harus aktif di kube-apiserver, yang mengizinkan minReplicas 0, dan di kube-controller-manager, yang melakukan scaling. Defaultnya adalah perubahan sesungguhnya: blog post fitur ini menyebut bahwa sebelum 1.37, scale from zero membutuhkan add-on, komponen eksternal, atau gate Alpha.
| Rilis | Yang masuk | Disebut di mana |
|---|---|---|
| v1.16 | Pertama kali diperkenalkan. Timeline di blog post menyebutnya implementasi Alpha pertama. | Release notes v1.37 dan blog post HPA |
| v1.36 | Condition ScaledToZero, dan perilaku controller yang membedakan scale-down otomatis dari pause manual. | Blog post HPA, bagian timeline |
| v1.37 | Beta, aktif secara default. HPAScaleToZero menyala di kube-apiserver dan kube-controller-manager. | Dokumentasi HPA |
| GA | Belum ada versi. Langkah berikutnya yang disebut adalah mengumpulkan operational feedback. | Blog post HPA dan tracking issue KEP |
Perubahan yang kurang kentara hadir satu rilis lebih awal. Timeline di blog post menyebut v1.36 menambahkan condition ScaledToZero dan perilaku controller yang membedakan scale-down otomatis dari pause manual. Condition itu, bukan jumlah replica, yang membuat fitur ini aman, dan bagian keempat menjelaskan alasannya. Beta juga belum GA: post tersebut menyebut langkah berikutnya adalah mengumpulkan operational feedback sebelum mempertimbangkan GA, dan tracking issue KEP tidak mencantumkan versi stable.
Kenapa hanya metric object dan external yang memenuhi syarat
Scale to zero berdasarkan CPU atau memori tidak didukung, karena metric itu bergantung pada Pod yang aktif. Dokumentasi HPA mengatakan hal yang sama: resource metrics hanya bisa diukur pada Pod yang berjalan, sehingga pada nol replica tidak ada yang bisa diukur dan tidak ada yang bisa meminta replica pertama kembali. Mengatur minReplicas 0 mewajibkan minimal satu metric object atau external, dan API server menolak HPA yang tidak memilikinya.
Metric object dan external tidak punya masalah itu. Dokumentasi menggambarkannya sebagai satu metric yang diambil untuk object yang dimaksud lalu dibandingkan dengan target. Panjang queue hidup di broker, bukan di worker, sehingga ia tetap melapor setelah worker hilang. Itulah inti desainnya: sinyal yang membangunkan workload harus hidup lebih lama dari workload itu. KEP-2021 merumuskan tujuannya secara sempit, scale to dan from zero untuk metric object dan external, dan mencantumkan resource metrics serta request buffering di level Service sebagai non-goal.
Config yang bekerja: queue lag, Prometheus Adapter, satu HPA
Contoh di blog Kubernetes mengasumsikan Prometheus sudah mengumpulkan series bernama queue_consumer_lag dengan label namespace dan name, lalu mengeksposnya lewat externalRules milik Prometheus Adapter. Dokumentasi adapter menjelaskan detail yang sering menjebak: HPA bersifat namespaced, sehingga adapter menambahkan label namespace ke query, dan path External Metrics API membawa namespace. Periksa metric lewat API server sebelum menulis HPA.
# 1. prometheus-adapter values: expose queue_consumer_lag as an EXTERNAL metric.
# Shape taken from the Kubernetes v1.37 HPA post and the adapter's
# external-metrics doc (v0.12.0).
externalRules:
- seriesQuery: '{__name__="queue_consumer_lag",name!=""}'
metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (name)
resources:
overrides:
namespace:
resource: namespace # HPAs are namespaced, so the adapter scopes by namespace
# 2. Prove the metric through the API server before any HPA exists.
# If this does not print the current value, an HPA cannot wake from zero.
kubectl get --raw \
'/apis/external.metrics.k8s.io/v1beta1/namespaces/default/queue_consumer_lag?labelSelector=name%3Dworker_tasks'HPA di bawah mengikuti contoh blog: Deployment bernama queue-worker, nol sampai sepuluh replica, satu replica diminta per 30 task dalam antrean. Deployment, limit GPU, dan grace period adalah tambahan saya untuk menunjukkan kasus yang menjadi alasan fitur ini dijual. Ganti baris GPU dengan extended resource apa pun yang diiklankan device plugin Anda.
# Deployment: leave spec.replicas OUT. It defaults to 1, which gives the HPA
# a workload to scale down, and a later kubectl apply will not reset the count.
apiVersion: apps/v1
kind: Deployment
metadata:
name: queue-worker
spec:
selector:
matchLabels:
app: queue-worker
template:
metadata:
labels:
app: queue-worker
spec:
terminationGracePeriodSeconds: 600 # my choice: long enough to finish a task
containers:
- name: worker
image: registry.example.com/queue-worker:1.0.0
resources:
limits:
nvidia.com/gpu: 1 # swap for whatever extended resource your device plugin advertises
---
# HPA: the shape of the Kubernetes v1.37 blog example. One replica per 30
# queued tasks, from 0 up to 10.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: queue-worker
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: queue-worker
minReplicas: 0 # needs at least one object or external metric below
maxReplicas: 10
metrics:
- type: External
external:
metric:
name: queue_consumer_lag
selector:
matchLabels:
name: worker_tasks
target:
type: Value
value: "30"Tiga detail di blog post lebih penting daripada YAML-nya. Mulai Deployment dengan minimal satu replica, karena HPA tidak akan membangunkan workload yang tidak ia turunkan sendiri. Downscale stabilization window default-nya lima menit. Dan API server menolak minReplicas 0 pada HPA yang hanya memakai resource metrics. Dokumentasi juga menyarankan menghapus spec.replicas dari manifest begitu HPA memegang Deployment, karena kubectl apply berikutnya akan mengembalikan jumlah ke nilai itu dan menyebabkan thrashing.
Uji jalur metric secara terpisah lebih dulu. Blog menyebut bahwa jika query raw external metrics tidak mengembalikan nilai saat ini, perbaiki metrics pipeline sebelum membuat HPA, karena HPA tidak bisa scale from zero ketika metric-nya tidak tersedia.
Nol versus paused: condition ScaledToZero
Sebelum fitur ini, replicas 0 hanya berarti satu hal: seseorang mem-pause autoscaling. KEP menyebut cara itu bekerja karena HPA tidak pernah bisa mencapai nol sendiri. Begitu minReplicas 0 diizinkan, Deployment di nol menjadi ambigu, sehingga HPA mencatat condition ScaledToZero. Nilainya True ketika HPA memindahkan workload dari satu replica ke nol, dan False dengan reason NotScaledToZero setelah ia naik lagi. Workload di nol tanpa ScaledToZero True tetap dianggap paused.
# Who put this workload at zero: the HPA, or a human?
kubectl get hpa queue-worker \
-o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'
# Reading it, per the HPA docs and the v1.37 blog post:
# ScaledToZero=True the HPA scaled it down and will wake it
# ScaledToZero=False NotScaledToZero it was scaled back up
# no ScaledToZero=True, replicas 0 a manual pause: the HPA will NOT wake it
# ScalingActive=False FailedGetExternalMetric
# the metric is gone; nothing can scale up
kubectl describe hpa queue-worker # same conditions, plus recent eventsKEP jujur soal sisi tajam yang tersisa. Menerapkan Deployment baru dengan replicas 0 di samping HPA dengan minReplicas 0 tidak akan pernah scale, karena HPA bukan yang menaruhnya di sana. KEP menyebut ini bisa dideteksi lewat condition ScalingActive yang sudah ada, dan bahwa fitur pause eksplisit bisa menggantikan yang implisit nanti. Saya akan menjadikannya pemeriksaan pertama setiap kali worker macet di nol padahal ada pekerjaan menunggu.
Anggaran cold start: dari mana delay datang
Release blog menyebut waktu cold start sebagai trade-off: HPA harus mengamati metric, men-schedule Pod, dan menjalankan aplikasi. Sumber Kubernetes hanya memberi angka untuk bagian HPA sendiri, dan saya tidak akan mengarang sisanya. Tabel ini memuat apa yang memang mereka nyatakan.
| Tahap | Default | Dampak pada queue consumer |
|---|---|---|
| Kesegaran metrics pipeline | Tidak ada angka; tergantung adapter dan setup scrape Anda | Replica pertama baru muncul setelah metric yang Anda sajikan melewati threshold. |
| Reconcile loop HPA | 15 detik (--horizontal-pod-autoscaler-sync-period) | KEP-2021 membatasi langkah dari nol ke satu oleh sync period ini ditambah kesegaran pipeline. |
| Stabilization scale-up | Tidak ada | Dokumentasi menyebut scale-up terjadi langsung ketika metric menuntutnya. |
| Stabilization scale-down | 300 detik | Nilai desired tertinggi dalam window yang menang, jadi nol baru tercapai setelah lima menit tenang kecuali Anda mengubahnya. |
| Scheduling Pod dan startup aplikasi | Tidak ada; Anda yang mengukur | Release blog menyebut ini, bersama pengamatan metric, sebagai trade-off cold start. |
Ada dua pembacaan. Pertama, catatan SLO di KEP-2021 menyebut replica pertama muncul pada reconcile berikutnya setelah metric melewati threshold, ditambah kesegaran metrics pipeline, tanpa delay scale-up tambahan. Kedua, semua setelah itu berada di luar HPA: image pull, ketersediaan node GPU, model load. Dokumentasi menunjuk node autoscaling untuk jumlah node yang tepat; inferensi saya, node GPU yang ikut menyusut menambah waktu tunggunya sendiri, yang tidak dikuantifikasi sumber-sumber ini. Ukur seluruh jalurnya di cluster Anda sendiri sebelum menjanjikan latency kepada siapa pun.
# Added to the HPA spec above. The default scaleDown window is 300 s and the
# docs describe it as a rolling maximum of past desired states. 900 is MY
# number for bursty queues, not a recommendation from the Kubernetes docs:
# it trades a longer idle tail for fewer cold starts between bursts.
behavior:
scaleDown:
stabilizationWindowSeconds: 900
scaleUp:
stabilizationWindowSeconds: 0 # the documented default: scale up at onceHPA native atau KEDA: apa yang dilepas dan apa yang tidak perlu dipasang lagi
KEDA sudah menawarkan scale to zero, jadi pertanyaan jujurnya adalah apa yang diubah fitur native ini. Dokumentasi KEDA menjelaskan dua fase. Pada fase activation, operator KEDA memutuskan apakah workload naik dari atau turun ke nol, dan itu hanya berlaku untuk 0 ke 1. Pada fase scaling, HPA mengambil alih untuk 1 ke N berdasarkan metric yang diekspos KEDA. Fitur native menaruh keputusan 0 ke 1 di dalam controller HPA sendiri, tetapi hanya untuk metric yang sudah Anda sajikan.
| Aspek | HPA native (1.37 Beta) | KEDA ScaledObject (docs 2.19) |
|---|---|---|
| Siapa yang memutuskan 0 ke 1 | Controller HPA, memakai condition ScaledToZero untuk tahu bahwa ia sendiri yang menyebabkan nol | Operator KEDA, dari hasil activation scaler; HPA menangani 1 ke N |
| Cara mengaktifkan | spec.minReplicas: 0 pada HPA, dengan gate aktif secara default di 1.37 | Sebuah ScaledObject, dengan minReplicaCount default 0 |
| Sumber metric | API metric external atau object yang sudah Anda sajikan, misalnya lewat Prometheus Adapter | KEDA memantau event source dan menyuplai datanya ke HPA |
| Threshold bangun terpisah | Tidak ada di docs yang saya baca: satu target menggerakkan semuanya | activationThreshold, harus lebih besar dari nilainya, default 0, dan menang atas nilai scaling |
| Floor idle dan aktif terpisah | Tidak terdokumentasi. Ditanyakan di issue KEP dan diarahkan ke KEP baru | idleReplicaCount, yang harus lebih kecil dari minReplicaCount |
| Pengatur waktu | Downscale stabilization window, default 300 detik | pollingInterval 30 detik dan cooldownPeriod 300 detik secara default |
Pembacaan saya: jika Anda sudah menjalankan metrics adapter dan punya satu queue yang dipahami baik, fitur native mengurangi satu komponen. Jika Anda butuh threshold bangun yang berbeda dari target scaling, atau idle floor, docs KEDA menampilkan keduanya sedangkan docs HPA yang saya baca tidak menampilkan satu pun. Seorang assignee di issue KEP menjawab pertanyaan tentang floor idle dan aktif terpisah dengan mengatakan hal itu lebih baik dilacak sebagai KEP baru.
Di mana fitur ini berhenti bekerja
Lima failure mode, masing-masing diambil dari sumber, bukan dari insiden saya:
- Metric hilang. HPA melaporkan ScalingActive False dengan reason seperti FailedGetExternalMetric, dan workload di nol tetap di sana sampai Anda memulihkan metric atau menaikkannya manual.
- Seseorang atau pipeline mengatur replicas ke 0. Tanpa ScaledToZero True, HPA membacanya sebagai pause dan tidak akan membangunkannya, dan Deployment baru di nol di samping minReplicas 0 tidak akan pernah scale.
- Control plane baru setengah di-upgrade. Controller manager dengan gate mati menganggap replicas 0 sebagai pause manual, jadi blog menyarankan menunggu sampai kedua komponen mendukung fitur ini sebelum membuat HPA dengan minReplicas 0.
- Workload menerima traffic HTTP. Service tidak mem-buffer request, dan request buffering adalah non-goal eksplisit KEP, sehingga workload berbasis request butuh lapisan buffering terpisah.
- Anda melakukan rollback. Atur HPA terkait ke minReplicas 1 atau lebih dan naikkan workload yang di nol ke minimal satu replica sebelum mematikan gate atau downgrade, atau workload bisa terjebak di nol.
Ada satu kegagalan lagi yang tidak disebut sumber, dan ia muncul dari cara bagian-bagian yang terdokumentasi saling bertemu. Metric di contoh adalah queue lag, yaitu task yang masih menunggu. Task yang sudah diambil worker tidak lagi menunggu. Dokumentasi menyebut stabilization window memakai nilai desired tertinggi selama window sebelumnya, lima menit secara default, sehingga job panjang bisa melewati window itu saat queue kosong dan HPA bebas menghapus Pod yang sedang mengerjakannya. Ini penalaran saya dari perilaku yang terdokumentasi, bukan sesuatu yang saya ukur.
Jika job GPU bisa berjalan lebih lama dari downscale window Anda, ekspos metric yang menghitung task queued ditambah in-flight, dan beri worker termination grace period yang cukup untuk menyelesaikan atau mengembalikan task-nya. Kalau tidak, scale to zero bisa membunuh pekerjaan yang tidak terlihat oleh metric queue mana pun.
Yang dikerjakan hari Senin
Jika Anda sudah di 1.37, fitur ini sudah menyala, jadi mulai dengan audit read-only baru kemudian pilih satu workload. Lima langkah, berurutan:
- Cari setiap HPA yang sudah mengatur minReplicas 0 dan setiap Deployment yang berada di nol replica. Yang pertama kini bisa scale sendiri; yang kedua mungkin paused atau mungkin sedang menunggu.
- Pilih satu queue consumer dengan idle panjang dan Pod mahal, dan buktikan metric-nya lewat External Metrics API sebelum menyentuh HPA apa pun.
- Terapkan HPA dengan minReplicas 0 di staging, kosongkan queue, perhatikan ScaledToZero menjadi True, lalu masukkan satu task dan ukur jeda dari pesan sampai replica Ready pertama.
- Periksa skew dan rollback. Pada control plane campuran, tunggu sampai kedua komponen mendukung fitur ini. Sebelum downgrade apa pun, atur minReplicas ke 1 dan naikkan workload yang berada di nol replica.
- Tambahkan alert di bawah: queue punya pekerjaan, tetapi HPA tidak bisa bertindak. Ia menangkap adapter yang rusak sebelum pengguna menyadari job yang macet.
# Step 1: every HPA that may already scale to zero, and every Deployment at zero.
kubectl get hpa -A -o json \
| jq -r '.items[] | select(.spec.minReplicas == 0) | .metadata.namespace + "/" + .metadata.name'
kubectl get deploy -A -o json \
| jq -r '.items[] | select(.spec.replicas == 0) | .metadata.namespace + "/" + .metadata.name'
# Step 5: page when the queue has work but the HPA cannot wake the workers.
# Metric names are the ones KEP-2021 points at (kube-state-metrics); the label
# names are kube-state-metrics' own, so confirm them on your version.
groups:
- name: scale-to-zero
rules:
- alert: QueueHasWorkButWorkersCannotWake
expr: |
max(queue_consumer_lag{name="worker_tasks"}) > 0
and on()
max(kube_horizontalpodautoscaler_status_condition{
horizontalpodautoscaler="queue-worker",
condition="ScalingActive", status="false"}) == 1
for: 5mPilih workload pertama dengan dua tes: satu jam idle memakan biaya nyata, dan cold start tidak merugikan siapa pun. Release blog membingkai kecocokannya sebagai pekerjaan yang bisa menunggu di durable queue, dan itulah filter yang akan saya pakai.
Aturan yang saya bawa dari sini: scale to zero adalah janji tentang metric, bukan tentang workload. HPA hanya bisa mengidlekan queue consumer karena panjang queue hidup lebih lama dari worker, jadi miliki dulu jalur metric itu, kesegarannya, dan alert-nya, lalu periksa apakah ada job yang melewati downscale window. Jika keduanya benar, 1.37 menghapus satu add-on dari cluster Anda. Jika salah, kegagalannya akan terlihat persis seperti queue yang kosong.
Sumber dan bacaan lanjutan
- Blog Kubernetes: v1.37 Scale Workloads to Zero with HorizontalPodAutoscaler
- Blog Kubernetes: pengumuman rilis v1.37 Garhwal
- Dokumentasi Kubernetes: Horizontal Pod Autoscaling, scaling to and from zero
- KEP-2021: HPA supports scaling to and from zero pods
- kubernetes/enhancements issue 2021: tracking issue
- Prometheus Adapter v0.12.0: panduan external metrics
- Dokumentasi KEDA: scaling Deployments dan StatefulSets
- Dokumentasi KEDA: spesifikasi ScaledObject

