← semua materi
15:00 Teknis · 15 Oct 2026 · 03:01

Kubernetes Scheduling: Requests, Limits, Eviction

Sesi: 10:00 Technical Exploration · 15 Okt 2026 · Dif 3 (Intermediate–Advanced)


Tujuan Belajar

Setelah sesi ini Bapak memahami: 1. Bagaimana kube-scheduler memilih node (filter → score → bind) dan apa bedanya dari kubelet. 2. Bedanya requests vs limits di dua level: scheduler (scheduling) dan kubelet/cgroup (runtime). 3. Bagaimana QoS class ditentukan mesin, bukan manual, dan kenapa itu menentukan urutan eviction. 4. Node-pressure eviction: signal, threshold hard/soft, grace period, urutan victim. 5. Perubahan terkini: In-Place Pod Resize GA di v1.35 dan scheduler preemption for resize (Alpha) di v1.37 (Sep 2026).


Arsitektur & Alur Teknis

Dua "otak" berbeda

Komponen Peran Keputusan
kube-scheduler Placement Pod ini jalan di node mana
kubelet (per node) Enforcement Berapa resource nyata yang diberi; siapa dibunuh kalau kurang

Scheduler tidak pernah melihat pemakaian aktual (metrics). Ia hanya melihat jumlah requests yang sudah ter-commit di tiap node. Inilah sumber utama salah paham operasional: node "penuh" menurut scheduler artinya jumlah requests = allocatable, bukan CPU benar-benar terpakai.

Alur scheduling cycle

Pod masuk queue (unscheduled)
   │
   ├─ Filtering  → buang node yang tidak feasible
   │     NodeResourcesFit (requests > allocatable = buang)
   │     NodeAffinity, Taints/Tolerations, PodTopologySpread,
   │     inter-pod affinity, PVC topology, node conditions
   │
   ├─ Scoring    → skor node feasible
   │     NodeResourcesBalancedAllocation (biar seimbang CPU/mem)
   │     NodeResourcesFit (LeastAllocated / MostAllocated / RequestedToCapacityRatio)
   │     ImageLocality (image sudah ada di node = skor tinggi)
   │     + plugin custom (mis. Kueue, Volcano untuk batch)
   │
   └─ Binding    → tulis spec.nodeName

Setelah binding, kubelet yang mulai kerja:

kubelet admit pod
   ├─ cek requests vs allocatable (kubelet punya view sendiri, dari cAdvisor + cgroup)
   ├─ buat cgroup: cpu.max / cpu.weight / memory.max / memory.high
   ├─ jalankan container via CRI
   └─ loop periodik: sinkronkan status, jalankan eviction manager

Requests vs Limits — apa yang sebenarnya terjadi

Konsekuensi penting:

# requests rendah + limits tinggi = Burstable, bisa burst tapi rapuh saat pressure
resources:
  requests:
    cpu: 100m
    memory: 256Mi
  limits:
    cpu: "2"          # 2 core ceiling; di atas ini → throttled
    memory: 512Mi     # di atas ini → OOMKilled

QoS class — mesin yang menentukan, bukan kita

QoS Syarat (semua container di pod) Prioritas hidup
Guaranteed requests == limits untuk CPU dan memory di setiap container Terakhir di-evict
Burstable Ada minimal satu request/limit, tapi tidak memenuhi syarat Guaranteed Tengah
BestEffort Tidak ada request maupun limit sama sekali Pertama di-evict

Cek cepat:

kubectl get pod <name> -o jsonpath='{.status.qosClass}{"\n"}'

Node-Pressure Eviction (kubelet)

Ini bagian yang paling sering bikin insiden produksi malam-malam. Kubelet memantau signal dan membandingkan dengan threshold:

Signal Sumber nilai
memory.available node.status.capacity[memory] − node.stats.memory.workingSet (dihitung dari cgroupfs, bukan free -m)
nodefs.available / nodefs.inodesFree filesystem node
imagefs.available / containerfs.available filesystem image/container runtime
pid.available rlimit.maxpid − curproc

Catatan teknis: kubelet meng-exclude inactive_file dari perhitungan memory.available karena dianggap reclaimable di bawah pressure. v1.37 (Beta, default on): pada node dengan hugepages, kubelet mengurangi kapasitas hugepage dari memory.available — sebelum ini hugepage-reserved RAM menggelembungkan AvailableBytes dan bikin eviction decision keliru.

Hard vs Soft threshold

Hard eviction → kubelet langsung kill dengan grace period 0s. Tidak sopan sama sekali. Soft eviction → kubelet tunggu eviction-max-pod-grace-period sebelum kill.

# KubeletConfiguration
evictionHard:
  memory.available: "100Mi"
  nodefs.available: "10%"
  nodefs.inodesFree: "5%"
evictionSoft:
  memory.available: "200Mi"
evictionSoftGracePeriod:
  memory.available: "60s"
evictionMaxPodGracePeriod: 120
memoryThrottlingFactor: 0.9
memoryReservationPolicy: TieredReservation   # cgroup v2 memory QoS

⚠️ Krusial: node-pressure eviction TIDAK menghormati PodDisruptionBudget dan TIDAK menghormati terminationGracePeriodSeconds pod. PDB hanya berlaku untuk API-initiated eviction (kubectl drain, Eviction API). Banyak orang salah paham di sini.

Urutan victim (ranking)

1. Pod dengan usage > requests  (kandidat pertama)
2. Di antara kandidat → urutkan per QoS class:
      BestEffort  →  Burstable (yang paling "melampaui requests")  →  Guaranteed
3. Tie-break: PodPriority → usage relatif terhadap requests

Poin penting yang sering dilewatkan: hanya pod yang melebihi requests-nya yang jadi kandidat ketika eviction dipicu resource pressure. Pod Burstable yang diam di bawah requests-nya relatif aman.

Eviction ranking pseudocode

candidates = pods where usage > requests

sort candidates by:
    key1 = qos_rank(pod)            # BestEffort=0, Burstable=1, Guaranteed=2
    key2 = -(usage - requests)      # yang paling melampaui dibunuh dulu
    key3 = pod.priority             # angka lebih kecil dibunuh dulu

evict first candidate
# kubelet lakukan bertahap, re-evaluate signal tiap interval
# berhenti begitu signal kembali di atas threshold

Priority & Preemption — beda mekanisme, beda pelaku

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "Latency-critical services"

⚠️ Anti-pattern fatal: system-cluster-critical (value 2000000000) dipakai untuk app biasa. Saat pressure, app sembarangan bisa membunuh kube-dns / CoreDNS karena priority-nya lebih tinggi. Ini penyebab outage DNS di banyak cluster.


Perkembangan 2025–2026 (terverifikasi)

Fitur Versi Status
In-Place Pod Resize v1.33 (Mei 2025) Beta
In-Place Pod Resize v1.35 (Des 2025) GA/Stable
Scheduler Preemption for In-Place Resize v1.37 (Sep 2026) Alpha, gate InPlacePodVerticalScalingSchedulerPreemption

Masalah yang dipecahkan fitur v1.37: setelah in-place resize GA, muncul celah — kalau pod minta scale-up melebihi headroom node, kubelet menandai resize sebagai Deferred (bukan Infeasible). Pod nyangkut di situ tanpa batas waktu, menunggu resource bebas secara natural. Sebelum v1.37, opsi operator cuma: evict manual, atau tunggu cluster autoscaler (disrupsi, melanggar janji "no restart").

Sekarang scheduler ikut memantau pod ber-Deferred dan bisa mem-preempt workload prioritas rendah di node yang sama untuk membebaskan headroom.

Resize flow (v1.37):
  1. ResizeDeferred  → kubelet tolak: OutOfcpu
  2. scheduler preempt low-priority-pod di node yang sama
  3. ResizeStarted   → kubelet actuate
  4. ResizeCompleted → cgroup di-update, pod TIDAK restart
# Manual edit (v1.33+)
kubectl edit pod <pod-name> --subresource resize

# Cek CPU yang benar-benar dialokasikan setelah resize
kubectl get pod high-priority-pod \
  -o jsonpath='{.status.containerStatuses[0].allocatedResources.cpu}{"\n"}'

Pilihan bin-packing sekarang jadi berbeda: dengan preemption ini, Bapak bisa agresif mengisi sisa kapasitas node dengan batch/low-priority workload tanpa takut memblokir scale-up workload kritis. Trade-off-nya: preemption = restart untuk pod yang di-preempt (bukan in-place), jadi Pastikan pod batch punya restart policy/checkpoint yang benar.


Trade-off & Pendekatan Alternatif

Requests sizing — over vs under

Strategi Risiko Kapan dipakai
Over-request Cluster boros, bin-packing jelek, pod pending Workload kritikal, VPA belum tersedia
Under-request Throttle/OOM, sasaran empuk eviction Batch, best-effort
Right-size via VPA VPA restart pod saat update (mode Auto) Workload stabil
In-place resize / KEDA Kompleksitas tambahan, versi cluster Workload dengan pola trafik dinamis

NodeResourcesFit strategy

LeastAllocated   → sebar merata (default-ish). Tahan node failure.
MostAllocated    → bin-pack rapat. Hemat biaya, rapuh saat spike.
RequestedToCapacityRatio → kontrol manual shapes.

Kombinasi MostAllocated + lower-priority overcommit = resep bin-packing agresif. Kalau kube ini punya lima pod replika dengan profil trafik mirip, pakai LeastAllocated biar kegagalan satu node tidak menjatuhkan semuanya sekaligus.

Alternatif cluster autoscaler

Solusi Model Catatan
Cluster Autoscaler Scale node saat pod pending Lambat (menit), rebutan quota cloud
Karpenter Just-in-time provisioning, instance fleksibel Lebih cepat, lebih granular
VPA Scale requests pod Restart untuk skema lama; in-place kini tersedia
In-place resize + preemption Scale tanpa restart Alpha (v1.37), butuh desain priority yang matang

Use Case Produksi

Skenario: API server latency-critical + batch ETL di node pool sama.

# Workload kritikal — jadikan Guaranteed, priority tinggi
apiVersion: v1
kind: Pod
spec:
  priorityClassName: high-priority
  containers:
    - name: api
      resources:
        requests: { cpu: "1",   memory: "1Gi" }
        limits:   { cpu: "1",   memory: "1Gi" }   # == requests → Guaranteed
---
# Workload batch — BestEffort/Burstable, priority rendah
apiVersion: batch/v1
kind: Job
spec:
  template:
    spec:
      priorityClassName: low-priority
      containers:
        - name: etl
          resources:
            requests: { cpu: "100m" }             # Burstable
            # tidak ada memory limit → risiko OOM internal app, tapi tidak OOMKilled

Dengan preemption v1.37, saat API perlu scale-up > headroom, scheduler bisa membunuh job ETL untuk membebaskan CPU. ETL tadinya jalan sebagai Burstable, akan dibunuh dulu (rank 1) sebelum pod API yang Guaranteed (rank 2) tersentuh.


Failure Mode Umum

Gejala Penyebab Perbaikan
Pod Pending terus, node "kosong" di monitoring Requests ketat + fragmentasi. Scheduler lihat requests, bukan usage Turunkan requests, aktifkan bundling / LeastAllocated, cek VPA rekomendasi
Latency p99 naik, CPU usage rendah CPU limit → CFS throttling Hapus CPU limit untuk service latency-sensitive, andalkan requests saja
OOMKilled (exit 137) sporadis Memory limit terlalu ketat, spike alami Naikkan limit, aktifkan memoryReservationPolicy, atau perbaiki leak
Pod mati tiap beberapa menit di satu node Node-pressure eviction (memory.available) Turunkan requests, perbaiki overcommit, cek node allocatable
CoreDNS mati saat malam Priority class app > system-critical Turunkan priority app, atau beri reservasi eksplisit untuk system pods
Pod Deferred nyangkut (v1.35+) Jadi headroom penuh, preemption belum aktif Update ke v1.37 + aktifkan gate, atau sisakan buffer
Pod Burstable dibunuh saat disk pressure, bukan memory Eviction signal berbeda (nodefs), QoS yang sama tetap ranking Cek semua signal, tidak hanya memory
Node "melaporkan memory penuh" padahal page cache besar Kubelet hitung memory.available dari workingSet Pahami rumus; pertimbangkan memoryReservationPolicy

Latihan Praktis Kecil

Latihan 1 — Verifikasi QoS. Ambil pod yang sedang jalan, ubah manifestnya jadi Burstable vs Guaranteed, apply, cek .status.qosClass. Konfirmasi di cgroup: cat /sys/fs/cgroup/.../cpu.weight.

Latihan 2 — Reproduksi throttling. Limit CPU 200m, jalankan beban yes > /dev/null. Cek container_cpu_cfs_throttled_periods_total di endpoints kubelet /metrics. Lalu hapus CPU limit dan bandingkan.

Latihan 3 — Peta priority. Buat tiga PriorityClass (low/medium/high), jalankan tiga pod di satu node, lalu isi node sampai penuh. Amati siapa yang di-preempt (kubelet event Preempted).

Latihan 4 — Baca eviction. Set evictionHard.memory.available: 200Mi, dorong node ke pressure, baca /var/log/pods dan event kubelet. Catat urutan eviction vs QoS yang Bapak harapkan.


Poin Kunci

  1. Scheduler = requests. Kubelet = limits + usage. Dua sumber kebenaran yang berbeda; kebingungan di sini pangkal 80% masalah kapasitas.
  2. QoS bukan pilihan, ia turunan. Turunan dari hubungan requests vs limits di seluruh container.
  3. Eviction ≠ Preemption. Eviction dipicu resource pressure (kubelet); preemption dipicu pod pending (scheduler). PDB hanya melindungi dari preemption/API eviction, tidak dari node-pressure eviction.
  4. CPU limit = throttle, memory limit = OOM. Menaikkan CPU limit tidak pernah memperbaiki throughput yang sudah cukup requests.
  5. Hanya pod di atas requests yang di-evict. Requests adalah "pelindung"; tidak ada cara lain.
  6. In-place resize kini GA (v1.35), dan preemption-nya Alpha (v1.37) — memungkinkan bin-packing agresif tanpa memblokir scale-up workload kritis.
  7. Priority class adalah senjata. Salah pakai = membunuh komponen sistem sendiri.

Eksplorasi Lanjut

  1. Descheduler — kapan pod perlu dikutip ulang dari node yang tidak lagi optimal? Bandingkan strategi RemoveDuplicates, LowNodeUtilization, RemovePodsViolatingNodeAffinity.
  2. Topology spread constraints — jamin replika tersebar antar zone/rack, dan bagaimana interaksinya dengan eviction satu node.
  3. Kueue / Volcano — scheduling antrian untuk batch/ML, perbandingan dengan PriorityClass biasa.
  4. cgroup v2 memory.high vs memory.max — di mana kubelet menetapkan memoryThrottlingFactor dan implikasinya.
  5. Node allocatable reservation — kubeReserved, systemReserved, evictionHard — dan bagaimana ketiganya menentukan daya tampung nyata node.
  6. Scheduler plugin framework — tulis custom scoring plugin (mis. scoring berbasis biaya listrik/idle timeout) sebagai latihan lanjutan.

Referensi terverifikasi: