Kendali Penempatan

Kendali penempatan Pod melalui Node affinity, taint, dan toleration.

Daftar isi

Secara bawaan, scheduler bebas menempatkan Pod pada Node mana pun yang sisa kapasitasnya mencukupi. Kubernetes menyediakan tiga kendali untuk mempersempit pilihan tersebut, yaitu nodeSelector, Node affinity, serta taint dan toleration. nodeSelector mencocokkan label Node secara persis, affinity menyatakan syarat mutlak dan preferensi dengan operator yang lebih kaya, sedangkan taint membiarkan Node menolak Pod yang tidak menoleransinya. Topik ini membahas ketiga mekanisme beserta aturan pencocokannya, dan logikanya disimulasikan dengan Python terhadap tiga Node.

Penempatan sederhana dengan nodeSelector#

nodeSelector adalah kendali penempatan paling sederhana, berupa peta label yang wajib cocok dengan Node tujuan. Label ditetapkan pada Node terlebih dahulu, misalnya disk=ssd, kemudian kunci nodeSelector pada spesifikasi Pod menyebut pasangan label yang disyaratkan. Scheduler hanya memilih Node yang seluruh pasangan pada nodeSelector terpenuhi, sehingga jumlah kandidat dapat menyusut sampai satu Node apabila labelnya jarang.

bash
# tidak-dijalankan
# memberi label disk=ssd pada node penyimpanan cepat
kubectl label node node-ssd-1 disk=ssd
# menampilkan pod beserta node penempatannya
kubectl get pod -o wide
text Contoh keluaran
node/node-ssd-1 labeled

NAME    READY   STATUS    RESTARTS   AGE   IP           NODE
web-1   1/1     Running   0          10s   10.244.0.7   node-ssd-1

Label Node dikelola bersama oleh operator kluster dan penyedia infrastruktur. Label bawaan seperti nama zona, arsitektur prosesor, atau sistem operasi diberikan otomatis pada Node yang dibuat penyedia layanan, sedangkan label kustom seperti disk=ssd ditetapkan manual melalui kubectl. Label yang dipilih untuk kendali penempatan sebaiknya menyatakan dimensi yang jarang berubah agar manifest tetap sah dalam waktu lama.

Kesederhanaan nodeSelector sekaligus menjadi batasnya. Kendali ini tidak mengenal operator selain kesamaan persis, tidak mendukung bobot preferensi, dan tidak dapat menyatakan keberadaan kunci tanpa nilai tertentu. Kebutuhan seperti nilai di dalam daftar, keberadaan kunci, atau preferensi berbobot memerlukan Node affinity.

Node affinity: syarat mutlak dan preferensi#

Node affinity memperluas nodeSelector melalui kunci affinity.nodeAffinity dengan dua bentuk. requiredDuringSchedulingIgnoredDuringExecution menyatakan syarat mutlak, sedangkan preferredDuringSchedulingIgnoredDuringExecution menyatakan preferensi yang diberi bobot antara 1 sampai 100. Bagian IgnoredDuringExecution berarti evaluasi hanya dilakukan saat penjadwalan, sehingga perubahan label Node setelah Pod berjalan tidak mengusiri Pod tersebut.

Syarat ditulis sebagai matchExpressions dengan operator In, NotIn, Exists, DoesNotExist, serta Gt dan Lt untuk perbandingan angka. Seluruh ekspresi pada satu nodeSelectorTerms dihubungkan dengan logika DAN, sedangkan beberapa nodeSelectorTerms dihubungkan dengan logika ATAU. Preferensi berbobot tidak menggugurkan kandidat mana pun, melainkan mengurutkan kandidat yang sudah lolos sehingga Node dengan skor tertinggi diutamakan.

Berkas berikut mendeklarasikan Pod web-1 dengan syarat mutlak disk In (ssd) beserta toleration untuk taint gpu. Karena syarat mutlak hanya menyaring kandidat, preferensi tambahan dinyatakan pada bentuk preferred sebagaimana fragmen yang mengikutinya. Toleration pada berkas tersebut mengizinkan taint gpu, sehingga Pod tetap dapat ditempatkan pada Node yang dibubuhi taint sekalipun.

yaml pod-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web-1
  labels:
    app: web
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disk
                operator: In
                values:
                  - ssd
  tolerations:
    - key: gpu
      operator: Exists
      effect: NoSchedule
  containers:
    - name: web
      image: nginx:1.27
      resources:
        requests:
          cpu: 250m
          memory: 128Mi
        limits:
          cpu: 500m
          memory: 256Mi
yaml Contoh: preferred nodeAffinity berbobot
preferredDuringSchedulingIgnoredDuringExecution:
  - weight: 80
    preference:
      matchExpressions:
        - key: zona
          operator: In
          values:
            - barat

Taint dan toleration#

Taint mengizinkan Node menolak Pod, berbanding balik dengan affinity yang menyatakan preferensi dari sisi Pod. Taint ditetapkan pada Node dengan pasangan kunci dan nilai beserta effect, sedangkan toleration ditulis pada spesifikasi Pod untuk mengizinkan taint tersebut. Node bertaint hanya menerima Pod yang membawa toleration yang cocok, sehingga pola ini digunakan untuk memisahkan Node khusus, misalnya Node GPU untuk beban kerja yang memang memerlukan GPU.

EffectAkibat bagi Pod tanpa toleration
NoSchedulePod baru tidak ditempatkan, Pod yang sudah berjalan tetap bekerja
PreferNoSchedulepenempatan dihindari, tetapi tidak dilarang mutlak
NoExecutePod baru tidak ditempatkan dan Pod yang sudah berjalan diusiri

Toleration mencocokkan taint dengan dua operator. Operator Equal menuntut nilai taint sama dengan nilai toleration, sedangkan operator Exists hanya menuntut kunci taint berada pada Node tanpa memandang nilai. Effect NoExecute membedakan dirinya dengan mengusiri Pod yang sudah berjalan, sehingga perubahan taint pada Node dapat memicu eviksi Pod yang tidak menoleransinya.

Toleration terhadap effect NoExecute dapat disertai kunci tolerationSeconds untuk membatasi lama Pod bertahan setelah taint ditambahkan. Apabila waktu tersebut tercapai, Pod diusiri sebagaimana Pod tanpa toleration. Kunci ini berguna bagi Pod yang memerlukan waktu untuk menyelesaikan pekerjaannya sebelum Node benar-benar ditarik dari kluster.

Simulasi evaluasi penempatan#

Ketiga kendali tersebut dievaluasi berurutan oleh scheduler, yaitu penyaringan syarat mutlak dan taint terlebih dahulu, kemudian pemeringkatan kandidat. Skrip berikut meniru urutan itu terhadap tiga Node, dengan Pod yang membawa syarat mutlak disk In (ssd), preferensi zona barat berbobot 80, dan toleration gpu dengan operator Exists. Simulasi ini menyederhanakan dua hal. Effect PreferNoSchedule tidak dihitung karena sifatnya menghindari, bukan memblokir, sehingga Node yang membawanya tetap ditampilkan sebagai lolos. Penilaian kapasitas juga tidak dilakukan di sini, sehingga hasil simulasi hanya menyatakan kelayakan berdasarkan label dan taint.

python simulasi-penempatan.py
"""Simulasi evaluasi node affinity dan taint terhadap beberapa node."""

EFFECT_BLOKIR = ("NoSchedule", "NoExecute")


def cocok_ekspresi(label, e):
    """Menilai satu matchExpressions: In, NotIn, Exists, DoesNotExist."""
    kunci, op = e["key"], e["operator"]
    if op == "In":
        return label.get(kunci) in e["values"]
    if op == "NotIn":
        return label.get(kunci) not in e["values"]
    if op == "Exists":
        return kunci in label
    if op == "DoesNotExist":
        return kunci not in label
    return False


def terpenuhi(label, ekspresi):
    """Seluruh ekspresi pada required nodeAffinity wajib terpenuhi."""
    return all(cocok_ekspresi(label, e) for e in ekspresi)


def skor_preferred(label, preferred):
    """Skor preferred nodeAffinity adalah jumlah bobot yang terpenuhi."""
    return sum(p["bobot"] for p in preferred
               if all(cocok_ekspresi(label, e) for e in p["matchExpressions"]))


def taint_tak_ditoleransi(taints, tolerations):
    """Mengembalikan taint pertama yang memblokir penempatan."""
    for t in taints:
        if t["effect"] not in EFFECT_BLOKIR:
            continue  # PreferNoSchedule tidak memblokir, hanya mengurangi prioritas
        diterima = False
        for tol in tolerations:
            if tol["key"] != t["key"]:
                continue
            if tol["operator"] == "Exists":
                diterima = True
            elif tol["operator"] == "Equal" and tol.get("value") == t.get("value"):
                diterima = True
        if not diterima:
            return t
    return None


nodes = [
    {"nama": "node-ssd-1", "label": {"disk": "ssd", "zona": "timur"}, "taints": []},
    {"nama": "node-gpu-1", "label": {"disk": "ssd", "zona": "barat", "gpu": "ya"},
     "taints": [{"key": "gpu", "value": "ya", "effect": "NoSchedule"}]},
    {"nama": "node-hdd-1", "label": {"disk": "hdd", "zona": "barat"}, "taints": []},
]

pod = {
    "nama": "web-1",
    "required": [{"key": "disk", "operator": "In", "values": ["ssd"]}],
    "preferred": [{"bobot": 80, "matchExpressions":
                   [{"key": "zona", "operator": "In", "values": ["barat"]}]}],
    "tolerations": [{"key": "gpu", "operator": "Exists", "effect": "NoSchedule"}],
}

print(f"Pod {pod['nama']}: required disk In (ssd), preferred zona In (barat) bobot 80")
print("          toleration gpu Exists untuk effect NoSchedule")
print()
for node in nodes:
    if not terpenuhi(node["label"], pod["required"]):
        print(f"{node['nama']}: TOLAK (affinity required tidak terpenuhi)")
        continue
    taint = taint_tak_ditoleransi(node["taints"], pod["tolerations"])
    if taint is not None:
        print(f"{node['nama']}: TOLAK (taint {taint['key']}={taint['value']}:{taint['effect']})")
        continue
    skor = skor_preferred(node["label"], pod["preferred"])
    if node["taints"]:
        t = node["taints"][0]
        print(f"{node['nama']}: LOLOS (taint {t['key']}={t['value']}:{t['effect']} "
              f"ditoleransi, skor preferred {skor})")
    else:
        print(f"{node['nama']}: LOLOS (skor preferred {skor})")
text Keluaran
Pod web-1: required disk In (ssd), preferred zona In (barat) bobot 80
          toleration gpu Exists untuk effect NoSchedule

node-ssd-1: LOLOS (skor preferred 0)
node-gpu-1: LOLOS (taint gpu=ya:NoSchedule ditoleransi, skor preferred 80)
node-hdd-1: TOLAK (affinity required tidak terpenuhi)

Keluaran tersebut menunjukkan peran setiap kendali. Node-ssd-1 lolos tanpa nilai preferensi karena zonanya timur, sedangkan Node-gpu-1 lolos dengan skor preferensi 80 karena taintnya ditoleransi dan zonanya barat. Node-hdd-1 tertolak hanya karena syarat mutlaknya, padahal preferensi zonanya terpenuhi, sehingga urutan evaluasi bergerak dari penyaringan mutlak ke pemeringkatan. Skor preferensi hanya berlaku di antara Node yang lolos, sehingga nilai 80 pada Node yang tertolak tidak pernah dipertimbangkan.

Ringkasan#

  • nodeSelector mencocokkan label Node secara persis dan menjadi kendali penempatan paling sederhana.
  • Node affinity menyatakan syarat mutlak pada bentuk required dan preferensi berbobot pada bentuk preferred, dengan operator In, NotIn, Exists, DoesNotExist, Gt, dan Lt.
  • taint dengan effect NoSchedule, PreferNoSchedule, atau NoExecute membuat Node menolak Pod, sedangkan toleration dengan operator Equal atau Exists membuka aksesnya kembali.
  • Scheduler mengevaluasi syarat mutlak dan taint terlebih dahulu, kemudian menyusun urutan kandidat dari skor preferensi.

Referensi#