Memahami bagaimana constraint scheduling seperti nodeSelector, toleration, topologySpreadConstraints, dan pod anti-affinity menentukan pilihan NodePool; serta cara Karpenter menangani DaemonSet, kube-proxy, CNI, dan pod critical saat melakukan provisioning dan drain.

Di episode 7 kalian sudah belajar bagaimana Karpenter memangkas node lewat disruption budget dan consolidation. Episode 8 ini bergeser ke sisi lain dari kubernetes scheduling: constraint yang datang dari pod itu sendiri. Kalian mungkin berpikir NodePool yang menentukan segalanya, tetapi sebenarnya sebagian besar keputusan justru lahir dari bagaimana pod ditulis — nodeSelector, toleration, topologySpreadConstraints, dan pod anti-affinity.
Setelah episode ini, kalian akan paham bagaimana constraint ini memengaruhi pemilihan NodePool, dan bagaimana Karpenter memperlakukan DaemonSet serta pod critical saat node sedang di-drain atau di-terminate.
nodeSelector adalah constraint paling sederhana: pod hanya boleh dijadwalkan ke node yang memiliki label tertentu. Karpenter memanfaatkannya dengan cara yang unik — label yang dipasang pada template NodePool bisa dijadikan target oleh nodeSelector di sisi pod.
apiVersion: v1
kind: Pod
metadata:
name: gpu-inference
spec:
nodeSelector:
node-pool: gpu
containers:
- name: inference
image: tensorflow/serving
resources:
requests:
cpu: 500m
memory: 1GiJika sebuah NodePool menempelkan label node-pool: gpu pada template-nya, hanya NodePool itulah yang bisa menjadwalkan pod ini. Constraint ini mempersempit kandidat NodePool secara langsung.
Taints dan tolerations bekerja sebagai gerbang. Node yang diberi taint menolak pod yang tidak memiliki toleration yang cocok. Karpenter memanfaatkan ini untuk memisahkan workload: NodePool untuk workload khusus bisa diberi taint, sehingga hanya pod dengan toleration yang sesuai yang masuk.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: gpu-pool
spec:
template:
spec:
taints:
- key: nvidia.com/gpu
effect: NoSchedule
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["g5.xlarge", "g5.2xlarge"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: defaultapiVersion: v1
kind: Pod
metadata:
name: gpu-worker
spec:
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
containers:
- name: worker
image: nvidia/cuda:12.0-baseTip
Ingat kombinasi keduanya: toleration saja tidak cukup jika tidak ada NodePool yang menyediakan node yang cocok. Taint NodePool nvidia.com/gpu memastikan workload biasa tidak pernah berjalan di node GPU.
Saat pod dijadwalkan, kubernetes bisa menyeimbangkan penyebarannya lewat topologySpreadConstraints. Ini penting untuk ketersediaan: jika semua replika berada di satu availability zone lalu zona tersebut bermasalah, aplikasi ikut tumbang.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 6
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: web
containers:
- name: web
image: nginxConstraint ini memengaruhi Karpenter dalam dua arah. Pertama, saat memilih instance type dan subnetwork, Karpenter mempertimbangkan availability zone yang tersedia. Kedua, saat consolidation, Karpenter memastikan pemindahan pod tidak melanggar constraint penyebaran ini.
Pod anti-affinity melarang beberapa pod berjalan di node yang sama atau berdekatan. Ini kerap dipakai untuk workload yang saling mengganggu, atau untuk memisahkan dua komponen agar kegagalan tidak menimpa keduanya sekaligus. Aturan seperti topologyKey: kubernetes.io/hostname memaksa replika tersebar ke hostname yang berbeda, sehingga satu node yang bermasalah tidak membawa semua replika sekaligus.
Keseluruhan constraint di atas mempersempit daftar NodePool yang layak. Karpenter mengevaluasi pod dengan aturan yang sama seperti scheduler Kubernetes: pod hanya dianggap schedulable jika ada NodePool yang menghasilkan node yang memenuhi semua constraint. Jika tidak ada NodePool yang cocok, pod tetap dalam status Pending dan tidak ada node yang dibuat.
| Constraint | Pengaruh terhadap NodePool |
|---|---|
| nodeSelector | Membatasi NodePool yang memasang label cocok |
| Toleration | Membuka akses ke NodePool yang bertaint |
| topologySpreadConstraints | Memengaruhi pilihan AZ dan subnetwork |
| Pod anti-affinity | Membatasi kepadatan pod dalam satu node |
DaemonSet dirancang berjalan di setiap node. Saat Karpenter meluncurkan node baru, DaemonSet seperti kube-proxy dan CNI otomatis dijadwalkan ke node tersebut oleh DaemonSet controller — tanpa campur tangan Karpenter. Begitu juga saat node di-terminate, pod DaemonSet tidak perlu dipindahkan karena akan lahir kembali di node pengganti.
Important
Pod DaemonSet diabaikan dalam penghitungan utilisasi dan eligibility consolidation. Sebuah node yang hanya menjalankan pod DaemonSet tetap dianggap kosong oleh kebijakan WhenEmpty.
Kube-proxy dan CNI adalah DaemonSet yang harus ada sebelum pod biasa bisa berjalan. Karpenter memperhitungkan kebutuhan resource mereka saat menentukan ukuran instance: sebagian kapasitas node selalu disisihkan untuk komponen sistem ini. Inilah mengapa pod tidak pernah dihitung memakai seluruh kapasitas node.
Pod yang diberi priorityClassName: system-cluster-critical atau system-node-critical mendapat perlakuan istimewa. Saat node akan di-terminate, Karpenter melakukan drain dengan hormat: pod diberi waktu untuk selesai, kemudian di-terminate satu per satu. Untuk pod yang tidak bisa dipindahkan, Karpenter menunggu sampai kondisi aman sebelum node benar-benar dimatikan.
kubectl get pod -A --field-selector=spec.priorityClassName!= -o wide
kubectl drain node-xyz --ignore-daemonsets --delete-emptydir-dataWarning
kubectl drain manual di atas hanya untuk simulasi. Pada Karpenter, drain dilakukan otomatis oleh disruption controller. Melakukan drain manual bisa mengganggu perhitungan state Karpenter.
Memberi pod toleration tanpa ada NodePool yang bertaint membuat toleration itu tidak berguna. Toleration hanya bermakna saat ada node yang benar-benar memasang taint tersebut.
topologySpreadConstraints dengan whenUnsatisfiable: DoNotSchedule dan maxSkew: 0 bisa membuat pod tidak pernah terjadwal jika jumlah zona tidak mencukupi. Gunakan ScheduleAnyway bila memungkinkan.
Constraint scheduling menentukan batas-batas pilihan yang tersedia untuk Karpenter. Semakin jelas kalian menulis nodeSelector, toleration, spread, dan anti-affinity, semakin tepat pula keputusan provisioning dan consolidation yang dibuat Karpenter.
Inti yang harus dibawa pulang:
Di episode 9, kalian akan belajar bagaimana Karpenter bisa ditekuk untuk menghemat biaya — dari prioritas Spot, pemilihan instance family yang efisien, hingga penanganan otomatis interupsi instance Spot melalui queue processor. Sampai jumpa!