Scheduler #
Di dalam kluster Kubernetes, setiap kali sebuah Pod baru dideklarasikan, Pod tersebut tidak langsung dikaitkan dengan mesin server (node) mana pun. Pod akan berada di dalam status tanpa tujuan (unscheduled) dan antre untuk mendapatkan tempat tinggal yang layak. Komponen makelar penempatan yang bertugas menganalisis, menyaring, menilai, dan akhirnya menetapkan Pod ke Worker Node terbaik di dalam kluster adalah kube-scheduler (Scheduler).
Scheduler bertindak sebagai arsitek tata ruang kluster. Tanpa adanya Scheduler, seluruh kontainer aplikasi kita hanya akan tertahan selamanya pada status Pending. Bagi tim pengembang dan DevOps, memahami algoritma internal Scheduler sangatlah penting untuk merancang ketersediaan tinggi lintas pusat data (cross-zone high availability), mengalokasikan server GPU khusus secara efisien, serta mendiagnosis mengapa suatu Pod gagal ditempatkan dan macet di status Pending.
Dua Fase Utama Penjadwalan: Filtering dan Scoring #
Ketika Scheduler mendeteksi adanya Pod baru yang belum memiliki tugas penempatan (spec.nodeName kosong), Scheduler akan mengambil Pod tersebut dari antrean penjadwalan. Scheduler kemudian menjalankan algoritma evaluasi yang sangat ketat melalui dua fase utama: Filtering (Penyaringan) dan Scoring (Penilaian).
Berikut adalah visualisasi alur pipa (pipeline) penjadwalan dari awal Pod terdeteksi hingga keputusan ditulis secara permanen:
flowchart TD
PodQueue["Pod Baru di Antrean Penjadwalan"] --> Fetch["Scheduler Mengambil Pod"]
Fetch --> FilterPhase["1. Fase Filtering (Predicates)\nEliminasi node yang tidak layak"]
FilterPhase --> CheckQualified{Apakah ada node\nyang layak?}
CheckQualified -- "Tidak" --> PendingState["Pod Tertahan di Status Pending\n(Event: FailedScheduling)"]
CheckQualified -- "Ya" --> ScorePhase["2. Fase Scoring (Priorities)\nNilai sisa node layak (skor 0-100)"]
ScorePhase --> SelectBest["Pilih Node dengan Skor Tertinggi"]
SelectBest --> Binding["3. Fase Binding\nTulis keputusan penempatan ke API Server"]
Binding --> Success["Kubelet Node Terpilih Menjalankan Pod"]
PendingState -->|Retry Loop| Fetch
1. Fase Filtering (Predicates / Kriteria Kelayakan) #
Pada fase filtering, Scheduler menyaring seluruh Worker Node di kluster untuk menemukan node mana saja yang memiliki kapasitas dan kapabilitas fisik untuk menjalankan Pod tersebut. Setiap kriteria penyaringan disebut Predicate. Jika sebuah node gagal memenuhi satu saja aturan Predicate, node tersebut langsung dicoret dari daftar kelayakan.
Beberapa filter Predicate bawaan yang paling penting meliputi:
- NodeResourcesFit: Memeriksa apakah kapasitas CPU, RAM, dan penyimpanan sementara (ephemeral-storage) yang tersedia di node masih cukup untuk menampung nilai deklarasi resource requests dari Pod.
- NodeName: Memeriksa apakah pengembang meminta secara spesifik nama node tertentu di dalam manifest menggunakan field
spec.nodeName. - NodePorts: Memeriksa apakah port jaringan host (hostPort) yang diminta oleh kontainer di dalam Pod sudah digunakan oleh kontainer lain di node tersebut. Jika ada bentrokan port, node akan dieliminasi.
- PodTopologySpread: Memeriksa apakah penempatan Pod di node tersebut melanggar aturan distribusi penyebaran topologi kluster.
- NodeAffinity: Memeriksa apakah label-label fisik yang menempel pada node cocok dengan aturan seleksi node (Node Selector/Affinity) yang dideklarasikan di manifest Pod.
Jika hasil fase filtering menghasilkan nol node yang layak, Pod akan terdampar di status Pending dan Scheduler menuliskan event log FailedScheduling ke API Server. Pod tersebut akan tetap di sana hingga ada resource node baru yang tersedia (misalnya melalui proses scaling otomatis kluster).
2. Fase Scoring (Priorities / Kriteria Penilaian) #
Setelah mendapatkan daftar node yang lolos dari fase filtering, Scheduler akan menilai masing-masing node tersebut guna menemukan node dengan kualitas terbaik. Fase ini disebut sebagai scoring, di mana setiap node layak akan diberikan nilai dari 0 hingga 100 berdasarkan sekumpulan aturan prioritas (Priorities).
Beberapa parameter penilaian prioritas meliputi:
- ImageLocalityPriority: Node yang sudah selesai mengunduh (caching) image kontainer yang dibutuhkan oleh Pod akan mendapatkan skor jauh lebih tinggi. Hal ini sangat bermanfaat untuk mempercepat waktu booting aplikasi karena node tidak perlu membuang waktu mengunduh image raksasa dari internet.
- NodeAffinityPriority: Memberikan skor lebih tinggi pada node yang memenuhi preferensi seleksi label non-wajib (preferredDuringScheduling).
- ResourceBalancedAllocation: Menilai rasio keseimbangan penggunaan CPU dan RAM di setiap node. Scheduler lebih menyukai node yang memiliki keseimbangan utilisasi resource (misal: CPU terpakai 50% dan RAM terpakai 50%) dibanding node yang load CPU-nya 90% namun RAM-nya kosong melompong.
- SelectorSpreadPriority: Berusaha menyebarkan Pod-Pod yang berada di bawah naungan ReplicaSet atau Service yang sama agar tidak berkumpul di node yang sama demi meminimalkan dampak kegagalan jika node tersebut mati.
Skor dari setiap aturan prioritas akan dikalikan dengan bobot (weight) masing-masing, lalu diakumulasikan. Node yang mendapatkan nilai total tertinggi akan dipilih sebagai pemenang. Jika ada beberapa node mendapatkan nilai yang sama persis, Scheduler akan memilih salah satunya secara acak. Setelah terpilih, Scheduler menjalankan fase Binding dengan mengirimkan objek binding ke API Server untuk menuliskan nama node terpilih ke kolom spec.nodeName milik Pod.
Pola Penjadwalan Lanjutan (Advanced Scheduling) #
Kubernetes memberikan fleksibilitas tanpa batas bagi kita untuk mengendalikan penempatan Pod secara presisi di lingkungan produksi melalui beberapa fitur berikut:
1. Node Affinity & Anti-Affinity #
Node Affinity memungkinkan kita mengikat Pod ke node tertentu berdasarkan label yang terpasang pada node. Terdapat dua jenis aturan:
- Hard Affinity (
requiredDuringSchedulingIgnoredDuringExecution): Aturan wajib yang harus terpenuhi pada fase filtering. Jika tidak terpenuhi, Pod gagal dijadwalkan. - **Soft Affinity
** (preferredDuringSchedulingIgnoredDuringExecution`): Berupa preferensi prioritas pada fase scoring. Jika terpenuhi skor bertambah, jika tidak terpenuhi Pod tetap bisa berjalan di node lain.
Contoh deklarasi Node Affinity wajib:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- ap-southeast-1a # Pod wajib diletakkan di Availability Zone 1a
2. Pod Affinity & Anti-Affinity #
Berbeda dengan Node Affinity yang menyeleksi label pada node, Pod Affinity dan Anti-Affinity mencocokkan label pada Pod-Pod lain yang sudah berjalan aktif di dalam kluster.
- Pod Affinity: Mendekatkan penempatan Pod kita dengan Pod lain (misal: menaruh Pod frontend di node atau zona yang sama dengan Pod Redis Cache guna mengurangi latensi jaringan).
- Pod Anti-Affinity: Menjauhkan penempatan Pod kita dari Pod sejenis (misal: melarang dua replika Pod aplikasi pembayaran berjalan di node atau Availability Zone yang sama). Ini adalah syarat wajib di lingkungan produksi guna menjamin redundansi fisik sistem.
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- payment-api
topologyKey: topology.kubernetes.io/zone # Menjamin Pod payment-api tersebar di zona berbeda
3. Taints & Tolerations #
Jika affinity digunakan untuk menarik Pod ke node, Taints dan Tolerations bekerja sebaliknya—digunakan untuk menolak Pod agar tidak menempati node tertentu secara sembarangan.
- Taint: Diberikan pada objek Node (misalnya server GPU diberi taint
sku=gpu:NoSchedule). Node yang diberi taint menolak seluruh Pod, kecuali jika Pod tersebut memiliki toleransi yang cocok. - Toleration: Dideklarasikan di dalam manifest Pod untuk menyatakan izin melewati taint node tersebut.
Ada tiga jenis efek Taint:
NoSchedule: Pod tanpa toleration yang cocok tidak akan pernah dijadwalkan di node ini.PreferNoSchedule: Scheduler akan berusaha menghindari penjadwalan di node ini, namun jika seluruh node lain penuh, Pod tetap boleh ditempatkan di sini.NoExecute: Jika node diberi taint ini secara dinamis, seluruh Pod tanpa toleration yang sedang berjalan di node tersebut akan langsung diusir (evicted) seketika.
4. Topology Spread Constraints #
Digunakan untuk menyebarkan replika Pod secara proporsional dan merata lintas domain topologi (seperti ketersediaan zona, region, atau rak server) guna mencegah penumpukan beban di satu titik.
spec:
topologySpreadConstraints:
- maxSkew: 1 # Selisih maksimum jumlah Pod antar zona adalah 1
topologyKey: topology.kubernetes.io/zone # Domain pembagian adalah Availability Zone
whenUnsatisfiable: DoNotSchedule # Jika tidak bisa merata, jangan dijadwalkan
labelSelector:
matchLabels:
app: web-server
Pod Disruption Budget (PDB) di Produksi #
Selama operasional harian, administrator kluster sering kali harus melakukan pemeliharaan node (misal: melakukan upgrade kernel OS host). Proses pemeliharaan ini dijalankan dengan perintah kubectl drain <node>, yang secara paksa akan menghapus (evict) seluruh Pod dari node tersebut agar node dapat dimatikan dengan aman.
Untuk memastikan tindakan pemeliharaan sistem tersebut tidak merusak ketersediaan layanan kita ke pengguna (menyebabkan downtime tidak disengaja), kita wajib mendefinisikan Pod Disruption Budget (PDB). PDB bertindak sebagai kesepakatan tingkat layanan (SLA) internal yang membatasi jumlah Pod yang boleh mati secara bersamaan selama proses pemeliharaan.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-api-pdb
namespace: production
spec:
minAvailable: 2 # Wajib ada minimal 2 Pod payment-api yang aktif dan sehat di kluster setiap saat
selector:
matchLabels:
app: payment-api
Dengan konfigurasi PDB di atas, jika administrator menjalankan perintah kubectl drain, API Server akan berkoordinasi dengan Scheduler untuk menolak penghapusan Pod jika tindakan tersebut menyebabkan jumlah replika aktif dari payment-api turun di bawah 2. Perintah drain akan tertahan (block) hingga ada replika baru yang berhasil berdiri tegak di Worker Node lain.
Anti-Pattern dalam Penjadwalan Pod #
Kesalahan-kesalahan konfigurasi penjadwalan yang sering kali mengakibatkan gangguan performa serius hingga kematian kluster.
Anti-Pattern 1: Mengabaikan Resource Requests (Scheduling Starvation) #
Mendeploy aplikasi ke kluster tanpa mendeklarasikan spesifikasi minimum resource yang dibutuhkan.
ANTI-PATTERN: Mendeploy Pod Tanpa Mengisi Kolom resources.requests
// KITA MELAKUKAN:
- Membuat manifes Deployment untuk 10 replika aplikasi java microservice.
- Di dalam kolom template container, kita mengabaikan pengisian properti `resources.requests`.
// KONSEKUENSI DI PRODUKSI:
- Blind Scheduling: Karena requests tidak diisi, Scheduler menganggap Pod membutuhkan 0 CPU dan 0 RAM.
- Scheduler akan menumpuk seluruh 10 Pod tersebut ke satu Worker Node yang sama karena menganggap node masih kosong.
- Begitu kontainer-kontainer mulai booting secara riil dan memakan memori fisik, RAM server host akan habis seketika.
- Fitur kernel Linux OOM (Out Of Memory) Killer akan aktif dan mulai membunuh proses Java secara acak.
- Node mengalami crash total, memicu kepanikan massal di kluster karena seluruh aplikasi di dalamnya mati bersamaan.
✓ SOLUSI YANG BENAR:
- Selalu lakukan uji beban (*load testing*) untuk mengukur kebutuhan komputasi riil aplikasi kita.
- Tuliskan nilai kebutuhan minimum tersebut secara akurat di kolom `resources.requests`:
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
- Ini memandu Scheduler untuk membagikan beban kerja secara seimbang ke seluruh node yang tersedia.
Anti-Pattern 2: Menggunakan Pod Anti-Affinity Hard (Required) Secara Berlebihan #
Menerapkan aturan isolasi ketat yang membatasi fleksibilitas kluster secara ekstrem.
ANTI-PATTERN: Menggunakan requiredDuringScheduling untuk Seluruh Aturan Anti-Affinity
// KITA MELAKUKAN:
- Mengonfigurasi 5 replika Pod API dengan aturan Pod Anti-Affinity bertipe `requiredDuringSchedulingIgnoredDuringExecution`
berdasarkan `topologyKey: kubernetes.io/hostname`.
- Kluster kita saat ini hanya memiliki 3 Worker Node fisik.
// KONSEKUENSI DI PRODUKSI:
- Kebuntuan Penjadwalan: Scheduler hanya berhasil menempatkan 3 Pod (1 Pod di setiap node).
2 Pod sisa akan terjebak selamanya di status `Pending` karena tidak ada node fisik ke-4 dan ke-5 yang tersedia.
- Kegagalan Pemulihan: Jika salah satu node mengalami crash, Pod yang mati tidak akan pernah bisa dijadwalkan ulang
ke node yang tersisa karena melanggar aturan wajib anti-affinity. Redundansi aplikasi kita justru menurun drastis.
✓ SOLUSI YANG BENAR:
- Gunakan aturan tipe Soft (`preferredDuringSchedulingIgnoredDuringExecution`) untuk aturan anti-affinity hostname.
- Hal ini memberi tahu Scheduler untuk berusaha menjauhkan Pod satu sama lain, namun jika terpaksa
(misal server lain mati atau kluster penuh), Scheduler tetap diizinkan menempatkan beberapa Pod di node yang sama.
Ringkasan #
- Makelar Penempatan Kluster — Scheduler bertanggung jawab menganalisis Pod baru tanpa penugasan node dan menentukan Worker Node terbaik berdasarkan resource dan batasan kebijakan.
- Pipa Filtering & Scoring — Proses penjadwalan terbagi menjadi dua: Filtering (menyaring node yang tidak layak berdasarkan Predicates) dan Scoring (memberikan nilai 0-100 untuk memilih node terbaik berdasarkan Priorities).
- Redundansi via Anti-Affinity — Gunakan Pod Anti-Affinity dengan topology ketersediaan zona (zone) guna menjamin replika aplikasi menyebar lintas pusat data untuk ketersediaan tinggi.
- Proteksi Node dengan Taints — Gunakan kombinasi Taints pada Node dan Tolerations pada Pod untuk mengamankan node khusus (seperti GPU) agar tidak dimasuki oleh kontainer biasa.
- Batas Toleransi Pemeliharaan — Definisikan Pod Disruption Budget (PDB) dengan nilai
minAvailableataumaxUnavailableguna melindungi aplikasi dari downtime selama proses pemeliharaan server host.- Hindari Starvation — Selalu deklarasikan nilai
resources.requestsdi setiap manifest aplikasi guna memandu Scheduler melakukan pembagian beban secara proporsional.- Gunakan Soft Affinity — Hindari penggunaan Hard Anti-Affinity secara berlebihan guna mencegah Pod tersangkut di status Pending saat kluster kekurangan jumlah node fisik.