Prioritas dan Preemption

Prioritas Pod dan mekanisme preemption saat sumber daya kluster tidak mencukupi.

Daftar isi

Prioritas menyatakan tingkat kepentingan sebuah Pod dibandingkan Pod lain saat sumber daya kluster tidak mencukupi. Tingkat tersebut ditetapkan melalui objek PriorityClass dan dirujuk Pod melalui kunci priorityClassName. Apabila Pod berprioritas tinggi tidak muat pada Node mana pun, scheduler menjalankan preemption, yaitu pengusiran Pod berprioritas lebih rendah untuk membuka ruang. Topik ini membahas penulisan PriorityClass, cara preemption memilih korban, serta batas mekanisme tersebut, dengan simulasi pemilihan korban dalam Python.

PriorityClass dan nilai prioritas#

PriorityClass adalah objek di tingkat kluster yang menetapkan angka prioritas pada kunci value. Nilai yang lebih besar berarti lebih penting. Pod merujuk kelas tersebut melalui kunci priorityClassName di spesifikasinya, sedangkan Pod tanpa rujukan memperoleh nilai nol atau nilai kelas yang ditandai globalDefault: true. Kubernetes membawa dua kelas bawaan untuk komponen sistem, yaitu system-cluster-critical dengan nilai 2000000000 dan system-Node-critical dengan nilai 2000001000, sehingga nilai kelas buatan pengguna sebaiknya diletakkan jauh di bawah angka tersebut.

Berkas berikut mendefinisikan dua PriorityClass untuk ranah analitik, yaitu prio-analitik dengan nilai 100 dan prio-laporan dengan nilai 500, kemudian menerapkannya pada Pod laporan-1. Kunci preemptionPolicy pada keduanya bernilai PreemptLowerPriority, yang berarti Pod berhak mengusiri Pod berprioritas lebih rendah. Nilai lainnya adalah Never, yang berarti Pod berprioritas tinggi hanya menunggu kekosongan kapasitas tanpa mengusiri Pod lain.

yaml priorityclass.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: prio-analitik
value: 100
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "Kelas untuk pekerjaan analitik yang boleh menunggu."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: prio-laporan
value: 500
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "Kelas untuk laporan terjadwal yang wajib selesai tepat waktu."
---
apiVersion: v1
kind: Pod
metadata:
  name: laporan-1
spec:
  priorityClassName: prio-laporan
  containers:
    - name: laporan
      image: busybox:1.36
      command: ["sleep", "3600"]
      resources:
        requests:
          cpu: 2000m
          memory: 2048Mi
        limits:
          cpu: 2000m
          memory: 2048Mi

Kelas prioritas umumnya diterapkan melalui template Pod pada workload controller, misalnya Deployment atau DaemonSet, sehingga seluruh Pod yang dibuatnya mewarisi prioritas yang sama. Nilai request pada template menentukan seberapa besar ruang yang harus dibuka saat preemption terjadi. Konsistensi rujukan pada satu lingkup beban kerja membuat keputusan preemption mudah dinilai saat kapasitas menipis.

Cara preemption memilih korban#

Preemption berjalan apabila Pod Pending berprioritas tinggi tidak muat pada Node mana pun. Scheduler mencari Node yang dapat memuat Pod tersebut setelah sejumlah Pod berprioritas lebih rendah diusiri, lalu menandai Pod yang harus diusiri sebagai korban. Kandidat korban hanya diambil dari Pod berprioritas lebih rendah, diawali Pod dengan prioritas terendah, sehingga jumlah korban ditekan serendah mungkin.

Sebelum preemption dijalankan, scheduler terlebih dahulu mengulang penilaian seluruh Node agar Pod yang tertahan benar-benar tidak dapat ditempatkan. Penilaian mencakup kapasitas, kendali penempatan, dan sebab-sebab lain yang membuat Pod menunggu. Dengan urutan tersebut, preemption menjadi langkah terakhir setelah seluruh opsi penempatan biasa habis.

Korban menjalani penghentian teratur beserta masa tenggangnya, dan Pod preemptor ditempatkan setelah korban benar-benar berhenti. Controller pemilik korban, misalnya Deployment, kemudian membuat Pod pengganti yang dicoba ditempatkan di Node lain, sehingga preemption memindahkan beban, bukan menghapusnya. Peristiwa semacam ini tercatat pada kluster dengan alasan Preempted beserta pesan yang menyebut Pod preemptor dan Node sasaran, sehingga dapat ditelusuri lewat kubectl.

bash
# tidak-dijalankan
# menampilkan peristiwa preemption pada kluster
kubectl get events --field-selector reason=Preempted
# menampilkan PriorityClass yang tersedia
kubectl get priorityclass
text Contoh keluaran
LAST SEEN   TYPE      REASON      OBJECT        MESSAGE
2m          Normal    Preempted   pod/batch-a   Preempted by pod "laporan-1" on node "node-a"
NAME                 VALUE   GLOBAL-DEFAULT   AGE   PREEMPTIONPOLICY
prio-analitik        100     false            3d    PreemptLowerPriority
prio-laporan         500     false            3d    PreemptLowerPriority

Simulasi pemilihan korban#

Urutan pemilihan korban dapat ditiru dengan aritmetika sederhana. Skrip berikut menyimpan satu Node dengan tiga Pod terpasang, kemudian menilai empat Pod kandidat berprioritas 500 secara berurutan. Setiap skenario dinilai terhadap keadaan Node yang sama, dan kandidat korban diurutkan dari prioritas terendah. Nilai sumber daya dinyatakan dalam milicore dan Mi agar aritmetikanya mudah diikuti.

python simulasi-preemption.py
"""Simulasi preemption pod berprioritas tinggi pada satu node."""

# Kapasitas dinyatakan dalam milicore CPU dan Mi memori agar mudah dihitung.
node = {"nama": "node-a", "cpu": 4000, "mem": 8192}
terpasang = [
    {"nama": "batch-a", "prio": 100, "cpu": 1500, "mem": 2048},
    {"nama": "batch-b", "prio": 100, "cpu": 1000, "mem": 1024},
    {"nama": "monitor-1", "prio": 1000, "cpu": 1000, "mem": 512},
]


def muat(pods):
    """Jumlah request seluruh pod pada daftar."""
    return sum(p["cpu"] for p in pods), sum(p["mem"] for p in pods)


def muat_bersama(pods, pod_baru):
    """Benar bila request pod_baru muat di samping pod yang dipertahankan."""
    cpu, mem = muat(pods)
    return (cpu + pod_baru["cpu"] <= node["cpu"]
            and mem + pod_baru["mem"] <= node["mem"])


def preempt(terpasang, pod_baru):
    """Memilih korban eviksi berprioritas terendah lebih dahulu hingga muat."""
    kandidat = sorted((p for p in terpasang if p["prio"] < pod_baru["prio"]),
                      key=lambda p: (p["prio"], p["nama"]))
    korban = []
    dipertahankan = list(terpasang)
    while not muat_bersama(dipertahankan, pod_baru):
        if not kandidat:
            return None
        terpilih = kandidat.pop(0)
        korban.append(terpilih)
        dipertahankan.remove(terpilih)
    return korban


cpu, mem = muat(terpasang)
print(f"Node {node['nama']}: allocatable {node['cpu']}m CPU dan {node['mem']} Mi memori")
print(f"Terpasang: {cpu}m CPU dan {mem} Mi memori ({len(terpasang)} pod)")
print()

# Setiap skenario dinilai terhadap keadaan node yang sama.
kandidat = [
    {"nama": "cache-1", "prio": 500, "cpu": 500, "mem": 256},
    {"nama": "laporan-1", "prio": 500, "cpu": 2000, "mem": 2048},
    {"nama": "analitik-1", "prio": 500, "cpu": 3000, "mem": 2048},
    {"nama": "backup-1", "prio": 500, "cpu": 1000, "mem": 8192},
]

for k in kandidat:
    print(f"Kandidat {k['nama']} (prioritas {k['prio']}, "
          f"request {k['cpu']}m CPU dan {k['mem']} Mi):")
    if muat_bersama(terpasang, k):
        print("  MUAT TANPA PREEMPTION, langsung ditempatkan")
        print()
        continue
    korban = preempt(terpasang, k)
    if korban is None:
        print("  PREEMPTION GAGAL, pod berprioritas lebih rendah tidak cukup")
        print()
        continue
    nama = ", ".join(p["nama"] for p in korban)
    print(f"  DITEMPATKAN setelah eviksi: {nama}")
    print()
text Keluaran
Node node-a: allocatable 4000m CPU dan 8192 Mi memori
Terpasang: 3500m CPU dan 3584 Mi memori (3 pod)

Kandidat cache-1 (prioritas 500, request 500m CPU dan 256 Mi):
  MUAT TANPA PREEMPTION, langsung ditempatkan

Kandidat laporan-1 (prioritas 500, request 2000m CPU dan 2048 Mi):
  DITEMPATKAN setelah eviksi: batch-a

Kandidat analitik-1 (prioritas 500, request 3000m CPU dan 2048 Mi):
  DITEMPATKAN setelah eviksi: batch-a, batch-b

Kandidat backup-1 (prioritas 500, request 1000m CPU dan 8192 Mi):
  PREEMPTION GAGAL, pod berprioritas lebih rendah tidak cukup

Keluaran tersebut menegaskan tiga perilaku. cache-1 muat langsung karena sisa kapasitas cukup, sehingga preemption tidak diperlukan. laporan-1 dan analitik-1 ditempatkan setelah korban diusiri, diawali batch-a sebagai Pod berprioritas terendah, sedangkan monitor-1 berprioritas 1000 tidak pernah tersentuh. backup-1 gagal karena memori yang dapat dibebaskan lebih kecil daripada request backup-1, sehingga Pod tersebut tetap Pending.

Urutan di antara Pod berprioritas sama pada simulasi ini diatur berdasarkan nama agar hasilnya deterministik. Scheduler yang sesungguhnya menimbang faktor lain pada prioritas yang sama, seperti jumlah sumber daya yang dapat dibebaskan, untuk memperkecil gangguan. Perbedaan urutan itu tidak mengubah aturan utamanya, yaitu Pod berprioritas lebih tinggi selalu dipertahankan.

Penggunaan prioritas yang terkendali#

Nilai prioritas hanya bermakna sebagai perbandingan relatif, sehingga kuncinya adalah menjaga sebagian kecil Pod berprioritas sangat tinggi. Apabila hampir semua Pod menuntut prioritas tinggi, preemption saling berdesakan dan jaminan yang dijanjikan tidak terlindungi. Praktik yang lazim adalah memberi nilai tinggi bagi layanan yang gangguannya paling terasa bagi pengguna, nilai sedang untuk laporan terjadwal, dan nilai rendah untuk pekerjaan batch yang boleh menunggu.

Preemption juga memiliki batas yang perlu diantisipasi. Masa tenggang korban menunda penempatan Pod preemptor, sehingga Pod berprioritas tinggi dapat menunggu beberapa saat sebelum berjalan. Scheduler menghormati PodDisruptionBudget sebaik mungkin, sehingga pelanggaran kecil masih mungkin terjadi apabila tidak ada kandidat korban lain. Pod korban yang dibuat ulang oleh controller pemiliknya juga bersaing kembali untuk kapasitas, sehingga preemption bukan pengganti perencanaan kapasitas.

Ringkasan#

  • PriorityClass menetapkan angka prioritas di tingkat kluster, dan Pod merujuknya melalui kunci priorityClassName.
  • Preemption berjalan apabila Pod berprioritas tinggi tidak muat, dengan korban dipilih dari Pod berprioritas lebih rendah diawali yang terendah.
  • Kunci preemptionPolicy bernilai Never membuat Pod hanya menunggu kapasitas kosong tanpa mengusiri Pod lain.
  • Korban diusiri secara teratur dengan masa tenggang, dan controller pemiliknya dapat membuat Pod pengganti di Node lain.

Referensi#