Request dan Limit

Semantik request dan limit CPU serta memori dan alasan request yang mendorong keputusan penjadwalan.

Daftar isi

Request dan limit adalah dua batas sumber daya yang dideklarasikan pada setiap container di dalam Pod. Request menyatakan jumlah CPU dan memori yang dijamin bagi container, sedangkan limit menyatakan batas tertinggi yang boleh dikonsumsi saat container berjalan. Topik ini membahas penulisan kedua batas tersebut, satuan yang dipahami Kubernetes, peran request dalam keputusan penjadwalan, serta akibat yang muncul ketika konsumsi melebihi limit. Perhitungan satuan dan uji kapasitas Node disimulasikan dengan Python sehingga setiap hasil dapat dihitung ulang tanpa kluster.

Deklarasi request dan limit pada manifest#

Request dan limit ditulis pada kunci resources di dalam spesifikasi setiap container, dengan subkunci requests dan limits untuk CPU serta memori. Nilai CPU dinyatakan dalam core, sedangkan nilai memori dinyatakan dalam byte beserta akhiran satuan. Kedua batas bersifat opsional, tetapi beban kerja nyata hampir selalu menetapkannya karena kedua nilai menentukan kelas Quality of Service (QoS) Pod dan mutu penempatannya. API server menolak manifest apabila nilai request pada satu sumber daya lebih besar daripada limitnya.

Manifest berikut mendeklarasikan Pod web-1 dengan request 250m CPU dan 128Mi memori, serta limit 500m CPU dan 256Mi memori. Pola ini lazim untuk aplikasi web karena request yang lebih rendah membuat Pod mudah ditempatkan, sedangkan limit yang lebih tinggi memberi ruang untuk menghadapi lonjakan beban. Nilai pada kedua kunci bersifat per container, sehingga total kebutuhan Pod adalah jumlah seluruh container di dalamnya. Kedua kunci dituliskan untuk CPU dan memori sekaligus agar perilaku Pod mudah dinalar saat kapasitas dinilai maupun saat container berjalan.

yaml pod-resources.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web-1
  labels:
    app: web
spec:
  containers:
    - name: web
      image: nginx:1.27
      resources:
        requests:
          cpu: 250m
          memory: 128Mi
        limits:
          cpu: 500m
          memory: 256Mi

Satuan CPU dan memori#

Kubernetes menggunakan dua sistem satuan yang berbeda untuk CPU dan memori. Satu core CPU setara 1000 milicore, ditulis 1000m, sehingga nilai 250m berarti seperempat core dan nilai 1 berarti satu core utuh. Nilai memori dapat ditulis dengan akhiran biner Ki, Mi, dan Gi yang berpangkat 1024, atau akhiran desimal K, M, dan G yang berpangkat 1000. Karena pangkatnya berbeda, 1Gi sama dengan 1073741824 byte, sedangkan 1G hanya sama dengan 1000000000 byte.

Skrip Python berikut mengonversi beberapa nilai CPU dan memori ke satuan dasarnya. Skrip yang sama menguji penempatan dua Pod kandidat pada Node dengan kapasitas 2 CPU dan 4 Gi memori. Uji dilakukan dengan menjumlahkan request Pod terpasang ditambah request kandidat, lalu membandingkannya dengan kapasitas Node.

python simulasi-request.py
"""Simulasi konversi satuan sumber daya dan pemeriksaan kapasitas node."""

# --- konversi satuan CPU ---
# CPU dinyatakan dalam core, akhiran m berarti milicore dengan 1000m = 1 core.
cpu_contoh = ["100m", "250m", "500m", "1", "1500m"]
print("Konversi CPU:")
for nilai in cpu_contoh:
    if nilai.endswith("m"):
        core = int(nilai[:-1]) / 1000
    else:
        core = float(nilai)
    print(f"  {nilai:>6} = {core} core")

# --- konversi satuan memori ---
# Akhiran biner (Ki, Mi, Gi) berpangkat 1024, akhiran desimal (K, M, G) berpangkat 1000.
FAKTOR = {"Ki": 1024, "Mi": 1024**2, "Gi": 1024**3,
          "K": 1000, "M": 1000**2, "G": 1000**3}
mem_contoh = ["128Mi", "256Mi", "1Gi", "500M", "1G"]
print("Konversi memori:")
for nilai in mem_contoh:
    for akhiran, faktor in FAKTOR.items():
        if nilai.endswith(akhiran):
            byte = int(nilai[:-len(akhiran)]) * faktor
            break
    print(f"  {nilai:>6} = {byte} byte")

print()
print("Perbandingan akhiran biner dan desimal:")
print(f"  1Gi = {1024**3} byte")
print(f"  1G  = {1000**3} byte")
print(f"  Selisih = {1024**3 - 1000**3} byte")

# --- pemeriksaan kapasitas node ---
# Scheduler menempatkan pod hanya jika jumlah request pod terpasang ditambah
# request pod kandidat tidak melebihi allocatable node.
node = {"nama": "node-a", "cpu": 2, "mem": 4 * FAKTOR["Gi"]}
terpasang = [
    {"nama": "api-1", "cpu": 0.25, "mem": 256 * FAKTOR["Mi"]},
    {"nama": "api-2", "cpu": 0.25, "mem": 256 * FAKTOR["Mi"]},
    {"nama": "batch", "cpu": 1.0, "mem": 2 * FAKTOR["Gi"]},
]


def jumlah(pods, kunci):
    return sum(p[kunci] for p in pods)


cpu_terpakai = jumlah(terpasang, "cpu")
mem_terpakai = jumlah(terpasang, "mem")
print()
print(f"Node {node['nama']}: allocatable {node['cpu']} CPU, "
      f"{node['mem']} byte ({node['mem'] / FAKTOR['Gi']} Gi)")
print(f"Terpasang: {cpu_terpakai} CPU, {mem_terpakai // FAKTOR['Mi']} Mi dari {len(terpasang)} pod")

kandidat = [
    {"nama": "web-1", "cpu": 0.5, "mem": 1 * FAKTOR["Gi"]},
    {"nama": "web-2", "cpu": 0.5, "mem": 1 * FAKTOR["Gi"]},
]
for k in kandidat:
    cpu_baru = cpu_terpakai + k["cpu"]
    mem_baru = mem_terpakai + k["mem"]
    cukup_cpu = cpu_baru <= node["cpu"]
    cukup_mem = mem_baru <= node["mem"]
    if cukup_cpu and cukup_mem:
        print(f"Kandidat {k['nama']}: LOLOS "
              f"(request {k['cpu']} CPU dan {k['mem'] // FAKTOR['Mi']} Mi termuat)")
        terpasang.append(k)
        cpu_terpakai = cpu_baru
        mem_terpakai = mem_baru
    else:
        alasan = []
        if not cukup_cpu:
            alasan.append(f"CPU {cpu_baru} > allocatable {node['cpu']}")
        if not cukup_mem:
            alasan.append("memori tidak cukup")
        print(f"Kandidat {k['nama']}: PENDING ({', '.join(alasan)})")
text Keluaran
Konversi CPU:
    100m = 0.1 core
    250m = 0.25 core
    500m = 0.5 core
       1 = 1.0 core
   1500m = 1.5 core
Konversi memori:
   128Mi = 134217728 byte
   256Mi = 268435456 byte
     1Gi = 1073741824 byte
    500M = 500000000 byte
      1G = 1000000000 byte

Perbandingan akhiran biner dan desimal:
  1Gi = 1073741824 byte
  1G  = 1000000000 byte
  Selisih = 73741824 byte

Node node-a: allocatable 2 CPU, 4294967296 byte (4.0 Gi)
Terpasang: 1.5 CPU, 2560 Mi dari 3 pod
Kandidat web-1: LOLOS (request 0.5 CPU dan 1024 Mi termuat)
Kandidat web-2: PENDING (CPU 2.5 > allocatable 2, memori tidak cukup)

Keluaran tersebut memperlihatkan dua hal. Konversi satuan menegaskan bahwa 100m hanya bernilai sepersepuluh core dan bahwa 128Mi sama dengan 134217728 byte, sehingga nilai yang tampak mirip dapat berbeda jauh setelah dikonversi. Uji kapasitas meloloskan web-1 karena jumlah request menjadi 2.0 CPU dan 3584 Mi, sedangkan web-2 tertolak karena jumlah request CPU mencapai 2.5 dan memori 4608 Mi, keduanya melampaui kapasitas Node.

Request sebagai dasar keputusan penjadwalan#

scheduler adalah komponen control plane yang memilih Node bagi setiap Pod baru. Node yang dinilai harus memiliki sisa kapasitas yang mencukupi. Scheduler menjumlahkan request seluruh Pod yang sudah ditempatkan pada Node, menambahkan request Pod kandidat, kemudian membandingkan hasilnya dengan nilai allocatable Node. allocatable adalah kapasitas Node dikurangi kebutuhan komponen sistem seperti kubelet, runtime container, dan agen pemantauan. Limit tidak ikut dinilai pada tahap ini karena limit adalah batas runtime, bukan jaminan kapasitas.

Apabila tidak ada Node yang memenuhi, Pod tetap berstatus Pending dan penempatan dicoba kembali setiap kali keadaan kluster berubah. Keadaan tersebut sering membingungkan karena Node yang terlihat kosong dapat menolak Pod, padahal sisa kapasitas requestnya memang tidak cukup. Peristiwa FailedScheduling pada Pod biasanya menyebut sumber daya yang kurang, sehingga penelusurannya dimulai dari perbandingan request dengan allocatable. Diagram berikut merangkum perbedaan peran kedua batas tersebut pada CPU dan memori.

Request menentukan penjadwalan, limit membatasi konsumsi runtimeCPU0request 250mlimit 500mkonsumsi di atas limit dibatasi (throttle), container tetap berjalanMemori0request 128Milimit 256Mikonsumsi di atas limit mengakhiri proses dengan status OOMKilledBagian pekat adalah ruang yang dijamin request, bagian muda adalah ruang tambahan sampai limit
Peran request dan limit pada CPU serta memori

Perilaku limit saat runtime#

Limit ditegakkan kernel melalui cgroup, yaitu mekanisme pembatasan sumber daya proses pada Linux, sejak container mulai berjalan. Akibat konsumsi di atas limit berbeda antara CPU dan memori karena sifat kedua sumber daya itu berbeda. CPU bersifat dapat dikompresi, sehingga konsumsi di atas limit hanya membuat proses menunggu giliran waktu proses dan aplikasi terasa lambat. Pembatasan semacam ini dikenal sebagai throttle.

Sumber dayaSifatKonsumsi di atas limit
CPUdapat dikompresidibatasi (throttle), container tetap berjalan
Memoritidak dapat dikompresiproses diakhiri dengan status OOMKilled

Memori bersifat kebalikan dari CPU karena tidak dapat dikompresi. Apabila konsumsi melebihi limit, kernel mengakhiri proses di dalam container dan statusnya menjadi OOMKilled, yaitu keadaan ketika proses diakhiri oleh kernel karena memori yang tersedia habis. Container yang diakhiri kemudian dijalankan ulang menurut kunci restartPolicy dengan nilai bawaan Always. Pod yang terus-menerus melampaui limit memori berpindah dari status OOMKilled ke CrashLoopBackOff setelah beberapa kegagalan beruntun.

bash
# tidak-dijalankan
# menampilkan request dan limit container dalam format JSON
kubectl get pod web-1 -o jsonpath='{.spec.containers[0].resources}'
# menampilkan status pod yang prosesnya diakhiri kernel
kubectl get pod web-1
text Contoh keluaran
{"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"250m","memory":"128Mi"}}
NAME    READY   STATUS      RESTARTS   AGE
web-1   0/1     OOMKilled   1          2m

Kesalahan umum#

Kesalahan pertama adalah menetapkan request yang jauh lebih besar daripada kebutuhan nyata. Pod yang tidak muat tetap berstatus Pending meskipun Node terlihat kosong, karena keputusan penjadwalan hanya menghitung request, bukan konsumsi aktual. Perbaikannya adalah mengukur konsumsi aplikasi selama beban normal, menyesuaikan request dengan data tersebut, kemudian memantau ulang setelah manifest diterapkan.

Kesalahan kedua adalah menetapkan limit memori tanpa ruang untuk lonjakan beban. Aplikasi yang konsumsinya naik sesaat, misalnya saat cache berkembang, langsung diakhiri kernel ketika menyentuh limit, sehingga Pod berpindah-pindah antara status OOMKilled dan Running. Memberi jarak antara request dan limit memori menyeimbangkan kedua kebutuhan tersebut, yaitu jaminan penempatan yang stabil dan ruang yang cukup untuk menyerap lonjakan.

Kesalahan ketiga adalah membiarkan Pod yang membutuhkan isolasi berjalan tanpa limit sama sekali. Pod tanpa limit memori dapat menghabiskan memori Node dan memicu pengusiran Pod lain, sedangkan Pod tanpa limit CPU dapat berebut waktu proses dengan komponen sistem. Batas atas diperlukan bukan hanya untuk melindungi Pod itu sendiri, tetapi juga untuk menjaga kapasitas yang tersisa bagi Pod lain pada Node yang sama.

Ringkasan#

  • Request menyatakan jaminan CPU dan memori yang dihitung scheduler saat penempatan, sedangkan limit membatasi konsumsi sumber daya saat container berjalan.
  • CPU ditulis dalam core dengan akhiran m untuk milicore, sedangkan memori menggunakan akhiran biner Mi dan Gi berpangkat 1024 atau akhiran desimal M dan G berpangkat 1000.
  • Pod ditempatkan hanya apabila jumlah request seluruh Pod di Node ditambah request Pod kandidat tidak melebihi allocatable, sehingga Pod berstatus Pending bila tidak ada Node yang memenuhi.
  • Konsumsi CPU di atas limit dibatasi (throttle) dengan container tetap berjalan, sedangkan konsumsi memori di atas limit mengakhiri proses dengan status OOMKilled.

Referensi#