QoS Class #
Di dalam kluster produksi, kehabisan memori pada sebuah node worker (node memory pressure) merupakan skenario darurat yang pasti akan terjadi cepat atau lambat. Ketika hal ini terjadi, Kubelet yang bertindak sebagai penjaga node tidak boleh membiarkan node worker tersebut mengalami crash total. Kubelet harus secara aktif memilih dan mengorbankan (evict) Pod tertentu untuk membebaskan ruang memori.
Bagaimana Kubernetes menentukan Pod mana yang harus dibunuh terlebih dahulu dan Pod mana yang harus dilindungi dengan segala cara? Keputusan krusial ini diambil berdasarkan kelas Quality of Service (QoS) yang melekat pada masing-masing Pod. Sistem QoS di Kubernetes beroperasi secara otomatis di bawah kap, menerjemahkan konfigurasi resource request/limit yang kita tulis menjadi prioritas ketahanan fisik kontainer di tingkat kernel Linux. Artikel ini akan membahas secara mendalam tiga kelas QoS di Kubernetes, mekanisme perhitungan oom_score_adj, alur pengusiran (eviction), era modern Memory QoS di cgroups v2, serta kesalahan konfigurasi (anti-pattern) yang wajib kita hindari.
Tiga Kelas QoS Kubernetes Secara Mendalam #
Kubernetes membagi beban kerja ke dalam tiga kategori QoS: Guaranteed, Burstable, dan BestEffort. Kita tidak dapat menuliskan kelas QoS ini secara langsung di dalam manifes YAML (tidak ada field spec.qosClass). Sistem Kubernetes akan mengevaluasi secara deklaratif blok resources di seluruh kontainer di dalam Pod dan menetapkan kelasnya secara otomatis.
Berikut adalah visualisasi alur pengambilan keputusan penentuan kelas QoS:
flowchart TD
PodArrival["Pod Baru Dibuat"] --> ReadSpec["Evaluasi Blok spec.resources Seluruh Kontainer"]
ReadSpec --> CheckGuaranteed{"Apakah SEMUA kontainer mendefinisikan\nRequests & Limits secara IDENTIK\nuntuk CPU DAN Memori?"}
CheckGuaranteed -- "Ya" --> ClassGuaranteed["QoS Class: Guaranteed (oom_score_adj: -997)"]
CheckGuaranteed -- "Tidak" --> CheckBestEffort
CheckBestEffort{"Apakah SEMUA kontainer TIDAK mendefinisikan\nRequests maupun Limits sama sekali?"}
CheckBestEffort -- "Ya" --> ClassBestEffort["QoS Class: BestEffort (oom_score_adj: 1000)"]
CheckBestEffort -- "Tidak" --> ClassBurstable["QoS Class: Burstable (oom_score_adj: 2 s.d. 999)"]
1. Guaranteed: Kelas Proteksi Maksimal #
Pod mendapatkan kelas Guaranteed ketika kita memberikan jaminan resource yang mutlak dan tidak berselisih sedikit pun antara request dan limit.
Syarat Penentuan: #
- Setiap kontainer di dalam Pod (termasuk init containers dan sidecars) wajib mendefinisikan request dan limit untuk CPU.
- Setiap kontainer di dalam Pod wajib mendefinisikan request dan limit untuk Memori.
- Nilai request dan limit untuk masing-masing resource tersebut harus sama persis (identik) dan tidak boleh bernilai nol.
Contoh Manifes Pod Guaranteed: #
apiVersion: v1
kind: Pod
metadata:
name: critical-db-app
namespace: database
spec:
containers:
- name: postgres
image: postgres:15
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "2" # Sama persis dengan request CPU
memory: "4Gi" # Sama persis dengan request memori
- name: backup-sidecar
image: backup-agent:v1
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "100m" # Kontainer kedua juga wajib disetel identik
memory: "128Mi"
Implikasi Operasional: #
Pod Guaranteed merupakan kasta tertinggi dalam sistem Kubernetes. Kubelet akan memberikan prioritas tertinggi untuk melindungi Pod ini. Mereka adalah target paling terakhir yang akan disentuh oleh proses eviction maupun Kernel OOM-Killer. Kubernetes hanya akan membunuh Pod Guaranteed jika node worker benar-benar kehabisan pilihan lain, atau jika kontainer di dalam Pod Guaranteed itu sendiri yang melakukan kebocoran memori melampaui batas limit yang telah ia deklarasikan.
2. Burstable: Kelas Fleksibel dengan Skalabilitas Dinamis #
Pod mendapatkan kelas Burstable jika kita mendefinisikan resource request dan limit, namun memberikan ruang bagi kontainer untuk berkembang (bursting) melebihi request-nya saat diperlukan.
Syarat Penentuan: #
- Pod tidak memenuhi syarat kelas Guaranteed (misalnya karena nilai limit disetel lebih tinggi dari request).
- Setidaknya ada satu kontainer di dalam Pod yang mendefinisikan request memori atau CPU.
Contoh Manifes Pod Burstable: #
apiVersion: v1
kind: Pod
metadata:
name: api-web-service
namespace: production
spec:
containers:
- name: web-app
image: company/web-api:v2.1
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1000m" # Limit CPU lebih tinggi dari request -> Burstable
memory: "512Mi" # Limit RAM lebih tinggi dari request -> Burstable
Implikasi Operasional: #
Pod Burstable dirancang untuk menyeimbangkan efisiensi pemakaian resource kluster. Saat kluster memiliki kapasitas sisa, Pod Burstable diizinkan memakai CPU dan RAM hingga mencapai batas limit-nya. Namun, jika node mengalami tekanan memori, Pod Burstable berada di baris kedua dalam daftar pengusiran. Kubelet akan mengurutkan Pod Burstable berdasarkan seberapa jauh penggunaan memori riil mereka melampaui angka request yang dijanjikan.
3. BestEffort: Kelas Oportunistis Tanpa Jaminan #
Pod mendapatkan kelas BestEffort ketika kita sama sekali tidak memberikan informasi apa pun kepada scheduler mengenai kebutuhan resource kontainer.
Syarat Penentuan: #
- Tidak ada satu pun kontainer di dalam Pod yang mendefinisikan request maupun limit, baik untuk CPU maupun memori.
Contoh Manifes Pod BestEffort: #
apiVersion: v1
kind: Pod
metadata:
name: log-scraper
namespace: tools
spec:
containers:
- name: scraper
image: company/log-scraper:latest
# Tanpa adanya blok resources sama sekali
Implikasi Operasional: #
Pod BestEffort adalah warga kelas terendah. Mereka tidak mendapatkan jaminan kapasitas komputasi apa pun dari kluster. Scheduler akan menjadwalkan Pod ini ke node mana saja yang memiliki slot kosong, tanpa memperhitungkan ketersediaan RAM fisik. Begitu node worker mengalami sedikit saja tekanan resource, Kubelet akan segera membunuh seluruh Pod BestEffort secara paksa tanpa ampun untuk menyelamatkan node worker dan Pod Guaranteed/Burstable lainnya.
Mekanisme Eviction dan Skor Prioritas Kernel (oom_score_adj)
#
Untuk memahami bagaimana Kubelet mengendalikan prioritas pembunuhan kontainer saat node kehabisan memori, kita harus melihat bagaimana Kubernetes berinteraksi dengan mekanisme internal Linux Out of Memory (OOM) Killer.
Di sistem operasi Linux, setiap proses memiliki nilai oom_score (skor kerentanan dibunuh) dan nilai kontrol oom_score_adj (penyesuai skor) yang berkisar antara -1000 (tidak boleh dibunuh) hingga 1000 (korban pertama). Kubelet menghitung dan menuliskan nilai oom_score_adj untuk masing-masing kontainer berdasarkan kelas QoS mereka:
[Rumus Perhitungan oom_score_adj oleh Kubelet]
Guaranteed: Kunci permanen di angka -997.
(Hanya selangkah di atas proses sistem kritis seperti Kubelet / Docker daemon).
BestEffort: Kunci permanen di angka 1000.
(Korban pertama yang akan langsung dieksekusi oleh Kernel).
Burstable: Dihitung dinamis secara terbalik berdasarkan kapasitas request:
oom_score_adj = 1000 - (10 * % Request Terhadap Memori Node)
Penjelasan Detail Rumus Burstable: #
Jika sebuah node worker memiliki RAM total 32GB, dan Pod Burstable kita meminta memori request sebesar 3.2GB (yang setara dengan 10% dari total memori node), Kubelet menghitung:
$$\text{oom_score_adj} = 1000 - (10 \times 10) = 900$$
- Jika Pod Burstable lain di node yang sama meminta request sebesar 16GB (50% dari total memori node), nilai adjustment-nya adalah: $$\text{oom_score_adj} = 1000 - (10 \times 50) = 500$$
- Semakin besar memori request yang diajukan oleh Pod Burstable, semakin rendah nilai
oom_score_adj-nya, yang berarti Pod tersebut lebih terlindungi dari OOM Killer dibandingkan Pod Burstable yang meminta resource kecil.
Alur Eviction Saat Node Mengalami Tekanan Resource: #
Ketika memori fisik node worker menyusut hingga menembus ambang batas bahaya (eviction threshold, misalnya sisa RAM < 100Mi), Kubelet akan mengaktifkan loop penyelamatan dengan urutan pengusiran berikut:
flowchart TD
NodeStress["Node Mengalami Tekanan Memori (Memory Pressure)"] --> EvictLoop["Kubelet Mengaktifkan Eviction Loop"]
subgraph EvictHierarchy ["Hierarki Pengusiran Pod (Eviction Hierarchy)"]
EvictLoop --> Tier1["1. Evaluasi Pod BestEffort (oom_score_adj: 1000)"]
Tier1 --> EvictBestEffort["Usir Semua Pod BestEffort Segera"]
EvictBestEffort --> CheckPressure1{"Apakah memori\nsudah aman?"}
CheckPressure1 -- "Ya" --> StopEvict["Hentikan Eviction. Node Kembali Sehat."]
CheckPressure1 -- "Tidak" --> Tier2["2. Evaluasi Pod Burstable (oom_score_adj: 2 s.d. 999)"]
Tier2 --> SortBurstable["Urutkan Pod Burstable berdasarkan:\nPenggunaan memori yang paling jauh melampaui request"]
SortBurstable --> EvictBurstable["Usir Pod Burstable Terburuk Satu Per Satu"]
EvictBurstable --> CheckPressure2{"Apakah memori\nsudah aman?"}
CheckPressure2 -- "Ya" --> StopEvict
CheckPressure2 -- "Tidak" --> Tier3["3. Evaluasi Pod Guaranteed (oom_score_adj: -997)"]
Tier3 --> EvictGuaranteed["Usir Pod Guaranteed (Hanya Jika Sangat Terpaksa)\nTerutama jika Pod itu sendiri yang memory leak"]
end
Memory QoS di Era Linux Cgroups v2 #
Pada Kubernetes versi modern (1.22+), diperkenalkan fitur Memory QoS yang memanfaatkan subsistem kontrol cgroups v2 pada kernel Linux modern. Pada arsitektur cgroups v1 lama, pembatasan memori hanya bersifat biner: kontainer dibiarkan berjalan bebas atau langsung dibunuh seketika oleh OOM-killer jika menyentuh limit.
Cgroups v2 memperkenalkan mekanisme kontrol yang jauh lebih halus (gradual control) menggunakan tiga parameter parameter:
memory.min(Jaminan Mutlak): Mengunci jumlah memori minimum yang dijamin tidak akan pernah boleh diambil oleh sistem page-reclaim kernel dalam kondisi apa pun. Kubernetes memetakan nilai ini darirequests.memoryuntuk Pod Guaranteed.memory.low(Proteksi Lunak): Memberikan jaminan perlindungan memori selama sistem tidak mengalami stres berat. Jika terjadi stres, kernel boleh mengambil memori ini secara perlahan. Kubernetes memetakan ini darirequests.memorypada Pod Burstable.memory.high(Batas Throttling): Berfungsi sebagai katup pengaman sebelum kontainer menyentuh limit keras. Ketika penggunaan memori kontainer menyentuh ambang batasmemory.high(biasanya disetel otomatis pada kisaran 80-90% dari limits), kernel Linux akan secara aktif melambatkan proses alokasi memori kontainer tersebut (direct page reclaim throttling) alih-alih langsung membunuhnya. Hal ini memberikan waktu bagi aplikasi untuk melakukan pembersihan memori internal (Garbage Collection) secara mandiri.
Strategi Penerapan QoS Class di Lingkungan Produksi #
Pemilihan QoS Class tidak boleh dilakukan secara seragam di seluruh aplikasi. Kita harus menyesuaikan kelas berdasarkan tingkat kekritisan beban kerja terhadap jalannya bisnis:
1. Kapan Menggunakan Guaranteed QoS? #
Gunakan kelas Guaranteed hanya untuk beban kerja yang sangat kritis, sensitif terhadap latensi, dan membutuhkan jaminan performa CPU/RAM yang stabil secara konstan.
- Contoh Use Case: Kluster database utama (PostgreSQL, MariaDB, Elasticsearch), API Gateway pintu masuk utama trafik, atau Microservices otorisasi/pembayaran.
- Trade-off: Mengakibatkan biaya cloud kluster lebih mahal karena Kubernetes akan memesan (reserve) kapasitas penuh di memori node, tidak mengizinkan pemakaian memori tersebut dibagikan ke aplikasi lain meskipun aplikasi kita sedang menganggur.
2. Kapan Menggunakan Burstable QoS? #
Ini adalah kelas de-facto standar untuk sebagian besar aplikasi mikro (microservices) stateless di produksi.
- Contoh Use Case: Aplikasi web API (Node.js, Go, Python), aplikasi background worker pemroses antrean antarmuka, atau server cache Redis.
- Keuntungan: Memungkinkan efisiensi biaya yang tinggi melalui teknik overcommitment (kita dapat menjadwalkan total limits Pod melebihi kapasitas memori node, karena berasumsi tidak semua aplikasi akan mengalami lonjakan trafik secara bersamaan).
3. Kapan Menggunakan BestEffort QoS? #
Gunakan kelas ini secara eksklusif hanya untuk beban kerja non-kritis yang sifatnya oportunistik dan tidak memiliki dampak bisnis jika tiba-tiba mati di tengah jalan.
- Contoh Use Case: Script scraper data harian, agen monitor log non-realtime, atau lingkungan pengembangan (development sandbox).
- Keamanan: Selalu pastikan aplikasi kita memiliki mekanisme retry dan auto-resume jika prosesnya terhenti secara mendadak akibat eviction.
Namespace Hardening: Mencegah Terbentuknya Pod BestEffort #
Di dalam kluster multi-tenant yang digunakan bersama oleh banyak tim developer, membiarkan developer mendeploy Pod BestEffort di namespace produksi adalah bahaya besar.
Kita dapat memblokir pembentukan Pod BestEffort secara otomatis dengan memasang kebijakan LimitRange yang mewajibkan penyuntikan nilai request default:
apiVersion: v1
kind: LimitRange
metadata:
name: prevent-best-effort-policy
namespace: production
spec:
limits:
- type: Container
# Jika developer tidak menuliskan request memori/CPU di manifes YAML mereka:
# LimitRange secara otomatis menyuntikkan nilai di bawah ini:
defaultRequest:
cpu: "100m"
memory: "128Mi"
default:
cpu: "200m"
memory: "256Mi"
Dengan terpasangnya LimitRange di atas:
- Setiap Pod yang dideploy di namespace
productiondijamin minimal akan bertipe Burstable karena memiliki request default. - Tidak akan ada Pod yang berstatus BestEffort secara tidak sengaja, sehingga melindungi kluster dari ancaman penggusuran tak terkendali.
Anti-Pattern Terkait QoS Class dan Solusinya #
Berikut adalah dua kesalahan fatal yang sering terjadi di kluster produksi akibat salah memahami perilaku QoS Class:
Anti-Pattern 1: Mendeploy Database Stateful Menggunakan Kelas Burstable atau BestEffort #
Mendeploy database penting produksi (seperti PostgreSQL Master) tanpa menyamakan nilai request dan limit memori demi menghemat kuota namespace.
ANTI-PATTERN: Menyetel requests.memory: "2Gi" tetapi limits.memory: "8Gi" pada Pod PostgreSQL Master
// KITA MELAKUKAN:
- Kita membiarkan Pod database PostgreSQL berjalan sebagai kelas QoS Burstable.
- Ketika trafik normal, database menggunakan RAM sekitar 3GB (melebihi request 2GB).
// KONSEKUENSI DI PRODUKSI:
- Saat node worker mengalami memori pressure akibat ada aplikasi Java lain yang bocor memori,
Kubelet akan memindai seluruh Pod Burstable.
- Karena PostgreSQL menggunakan RAM melampaui request-nya, PostgreSQL menjadi target utama pengusiran.
- Database PostgreSQL dibunuh seketika di tengah-tengah transaksi penulisan aktif,
mengakibatkan kerusakan berkas database (*data corruption*) dan downtime sistem.
✓ SOLUSI YANG BENAR:
- Selalu setel database stateful produksi sebagai kelas QoS Guaranteed.
- Pastikan requests.memory == limits.memory dan requests.cpu == limits.cpu secara ketat.
Anti-Pattern 2: Menyetel Sidecar Container Tanpa Resource Specs di dalam Pod Guaranteed #
Lupa menuliskan blok resource pada kontainer sidecar (seperti logging agent atau service mesh proxy), sementara kontainer utama sudah Guaranteed.
ANTI-PATTERN: Menulis spec resources lengkap untuk app kontainer, tapi membiarkan kontainer proxy kosong
// KITA MELAKUKAN:
- Kontainer utama: requests = limits (Guaranteed).
- Kontainer sidecar: tidak menuliskan spec resources sama sekali (BestEffort).
// KONSEKUENSI DI PRODUKSI:
- Penurunan Kelas Otomatis: Kubernetes mengevaluasi seluruh kontainer di dalam Pod.
Karena ada satu kontainer sidecar yang tidak memiliki resource request/limit,
QoS Class seluruh Pod diturunkan secara drastis dari **Guaranteed** menjadi **Burstable**.
- Pod penting kita kehilangan hak proteksi maksimalnya dan dapat dievict kapan saja
oleh Kubelet hanya karena kelalaian konfigurasi pada kontainer pembantu.
✓ SOLUSI YANG BENAR:
- Pastikan seluruh kontainer (termasuk kontainer pembantu) di dalam manifes YAML Pod memiliki
deklarasi request dan limit yang identik tanpa terkecuali untuk menjaga status Guaranteed.
Ringkasan #
- QoS Ditentukan Otomatis — Kelas QoS ditentukan sepenuhnya oleh Kubernetes berdasarkan konfigurasi resource request dan limit; tidak ada parameter khusus untuk memilih kelas secara manual.
- Guaranteed (Teraman) — Memerlukan kesamaan nilai requests dan limits untuk CPU dan memori di seluruh kontainer; paling aman dari ancaman penggusuran node (eviction).
- Burstable (Fleksibel) — Terbentuk jika request dan limit tidak identik; dievict berdasarkan seberapa jauh penggunaan memori riil melewati angka request.
- BestEffort (Paling Rentan) — Tidak mendefinisikan resource request dan limit sama sekali; menjadi korban pertama yang akan langsung dibunuh saat node mengalami tekanan memori.
- Proteksi oom_score_adj — Kubelet menuliskan skor adjustmen ke kernel Linux: Guaranteed mendapat proteksi hampir mutlak (-997), sedangkan BestEffort disiapkan untuk mati (1000).
- Hardening LimitRange — Selalu pasang
LimitRangedengan default request di namespace produksi guna mencegah lolosnya Pod BestEffort yang membahayakan stabilitas node.- Sidecar Wajib Diisi — Jangan biarkan kontainer sidecar kosong tanpa resources, karena akan merusak status Guaranteed dari kontainer utama.
← Sebelumnya: Resource Request & Limit Berikutnya: Ephemeral vs Persistent Storage →