Kafka on Docker & Kubernetes: Orkestrasi Kluster di Lingkungan Kontainer #
Dalam ekosistem pengembangan perangkat lunak modern, adopsi teknologi kontainerisasi dan orkestrasi menggunakan Docker dan Kubernetes telah menjadi standar de facto untuk menjalankan aplikasi microservices. Namun, memindahkan Apache Kafka—sebuah sistem penyimpanan berstatus (stateful system) dengan throughput tinggi dan kebutuhan I/O disk yang intensif—ke dalam lingkungan kontainer yang secara natural bersifat sementara (ephemeral) menghadirkan serangkaian tantangan arsitektur yang sangat berbeda dengan aplikasi stateless.
Banyak tim DevOps pemula yang salah langkah dengan memperlakukan pod broker Kafka seperti kontainer aplikasi web biasa. Mereka menggunakan objek Deployment standar, membiarkan alokasi penyimpanan menggunakan disk sementara kontainer (emptyDir), atau mengabaikan penataan rute jaringan eksternal. Akibatnya, saat pod mengalami kegagalan, data mereka hilang secara permanen, atau kluster mengalami kegagalan koordinasi akibat IP broker yang terus berubah-ubah.
Dalam panduan ini, kita akan membedah strategi orkestrasi Apache Kafka di Kubernetes secara aman dan berkinerja tinggi. Kita akan mempelajari alasan mengapa StatefulSet wajib digunakan, merancang arsitektur penyimpanan persisten menggunakan Storage Class yang tepat, memecahkan masalah konektivitas jaringan internal dan eksternal, menerapkan aturan keandalan kluster (High Availability), serta memanfaatkan kekuatan Strimzi Operator untuk mengotomatiskan pengelolaan operasional Kafka.
Mengapa Pilih StatefulSet Alih-alih Deployment? #
Di Kubernetes, objek Deployment dirancang untuk aplikasi stateless (tidak menyimpan status). Pod di dalam Deployment bersifat anonim dan dapat digantikan kapan saja dengan nama acak baru (misalnya web-app-8c4d21). Hal ini bertolak belakang dengan kebutuhan Kafka, di mana setiap broker harus memiliki identitas unik, status penyimpanan yang melekat, dan nama host DNS yang konsisten.
Oleh karena itu, kita wajib menggunakan StatefulSet untuk mengorkestrasi pod broker Kafka. StatefulSet memberikan tiga jaminan mutlak berikut yang dibutuhkan oleh Kafka:
- Identitas Pod yang Stabil dan Deterministik:
Pod yang dibuat oleh StatefulSet akan memiliki penomoran indeks yang tetap dimulai dari nol (misalnya
kafka-0,kafka-1,kafka-2). Identitas ini dipertahankan saat pod dijadwalkan ulang (rescheduled) ke node fisik Kubernetes lainnya. - Pemetaan Volume Persisten 1-ke-1:
Menggunakan fitur
volumeClaimTemplatespada StatefulSet menjamin bahwa setiap pod (misalnyakafka-0) selalu terhubung ke berkas penyimpanan fisik (Persistent Volume - PV) yang sama secara eksklusif. Jika podkafka-0mati dan hidup kembali di node lain, ia akan otomatis memasang kembali PV lamanya tanpa kehilangan data. - Nama Host DNS yang Stabil:
StatefulSet dipasangkan dengan sebuah Headless Service untuk membuat entri DNS internal yang stabil bagi setiap pod (misalnya
kafka-0.kafka-headless.default.svc.cluster.local). Nama host inilah yang didaftarkan oleh broker ke controller metadata sehingga sesama broker dapat saling berkomunikasi secara andal meskipun alamat IP internal pod mereka berubah.
Alokasi Resource CPU & Memori yang Tepat #
Sebelum melangkah ke penyimpanan, kita harus memahami bagaimana Kubernetes mengelola alokasi memori untuk kontainer StatefulSet Kafka. Salah satu penyebab utama crash pod Kafka di Kubernetes adalah kegagalan memperhitungkan porsi RAM untuk page cache sistem operasi.
1. Bahaya OOMKilled Akibat Batas Memori Kontainer #
Di Kubernetes, memori page cache yang dialokasikan oleh sistem operasi untuk file I/O di dalam kontainer dihitung sebagai bagian dari penggunaan memori kontainer tersebut.
- Masalah: Jika kita menyetel JVM heap Kafka sebesar 4 GB, lalu menyetel batas memori kontainer (
limits.memory) sebesar 6 GB, kontainer akan berjalan dengan normal di awal. Namun, seiring dengan meningkatnya laju transaksi baca-tulis, sistem operasi akan menimbun data segmen log ke dalam page cache RAM. Penggunaan RAM kontainer akan dengan cepat melampaui 6 GB. - Akibat: Mesin Kubernetes (
kubelet) akan mendeteksi kontainer melebihi batas memori dan langsung membunuh pod Kafka tersebut dengan statusOOMKilled. - Solusi: Kita harus menyetel
limits.memorysecara longgar (minimal 2 kali lipat dari JVM heap) atau lebih baik lagi, hindari menyetel batas memori keras (hard memory limits) pada Kafka pod dan gunakan hanyarequests.memoryuntuk menjamin alokasi RAM dasar di node fisik.
2. Sizing Resource di Kubernetes YAML #
Berikut adalah contoh blok konfigurasi resource CPU dan RAM yang aman untuk produksi:
resources:
requests:
memory: "8Gi" # 4 GB untuk JVM Heap, 4 GB untuk Page Cache minimal
cpu: "4000m" # Alokasi terdedikasi 4 CPU Core
limits:
memory: "16Gi" # Memberikan ruang aman untuk page cache berkembang
cpu: "8000m"
Manajemen Storage di Kubernetes: Storage Class dan PVC #
Performa I/O disk adalah jantung dari kecepatan pemrosesan data Kafka. Di Kubernetes, kita harus memilih tipe media penyimpanan fisik (Persistent Volume) dan mengonfigurasinya melalui objek Storage Class.
1. Memilih Tipe Storage #
- Local Persistent Volumes (LPV) - Sangat Direkomendasikan: Untuk kluster berkinerja tinggi, kita disarankan menggunakan volume lokal yang terpasang langsung pada host fisik Kubernetes (SSD/NVMe lokal). LPV memintas overhead jaringan penyimpanan (SAN/NAS) sehingga menawarkan latensi tulis-baca terendah. Namun, karena LPV terikat pada host fisik tertentu, kita harus mengombinasikannya dengan aturan pod affinity yang ketat.
- Cloud Block Storage (EBS GP3/io2 di AWS, Persistent Disk di GCP): Pilihan yang populer karena mendukung kemudahan scaling ukuran disk secara dinamis dan replikasi cloud. Pastikan kita memilih tipe volume dengan throughput terdedikasi (seperti GP3 dengan alokasi IOPS minimum 3000) untuk mencegah penumpukan lag akibat kelambatan disk.
2. Mengonfigurasi Kebijakan Reklamasi (Reclaim Policy) #
Dalam berkas YAML Storage Class produksi kita, pastikan parameter reclaimPolicy disetel ke Retain (bukan Delete):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: kafka-storage-class
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iops: "3000"
throughput: "125"
reclaimPolicy: Retain # BENAR: Jangan hapus data secara otomatis jika PVC dihapus
volumeBindingMode: WaitForFirstConsumer # Memastikan volume dipasang di node fisik tempat pod dijadwalkan
Dengan menyetel reclaimPolicy: Retain, kita menjamin bahwa jika secara tidak sengaja StatefulSet Kafka kita terhapus, volume penyimpanan fisik di awan tidak akan ikut dihancurkan, memberikan tim SRE kesempatan untuk menyelamatkan data segmen log.
Jaringan di Kubernetes: Komunikasi Internal vs Akses Eksternal #
Menata jaringan untuk Kafka di Kubernetes sering kali menjadi bagian paling membingungkan karena Kafka mengharuskan klien (produser/konsumen) untuk dapat terhubung langsung ke alamat host spesifik dari broker leader partisi.
flowchart TD
subgraph Internal ["AKSES INTERNAL (Headless Service)"]
ClientInt["Pod Klien K8s"] --> Headless["kafka-0.kafka-headless.svc:9092"] --> Pod0["Pod 0"]
end
subgraph External ["AKSES EKSTERNAL (Advertised Listeners & LoadBalancer)"]
ClientExt["Klien Luar"] --> DNS["DNS: broker-0.mycorp.com:9094"] --> LB["LoadBalancer"] --> Pod0Ext["Pod 0"]
end
- Maka, Pod 0 harus memetakan Advertised Listeners eksternal: advertised.listeners=INTERNAL://kafka-0…,EXTERNAL://broker-0.mycorp…
1. Konektivitas Internal (Inter-Broker Quorum) #
Untuk komunikasi antar broker Kafka di dalam kluster Kubernetes yang sama, kita menggunakan Headless Service. Headless service tidak memiliki alamat ClusterIP tunggal. Sebaliknya, ia langsung mengembalikan daftar alamat IP dari seluruh pod anggota StatefulSet melalui kueri DNS internal:
apiVersion: v1
kind: Service
metadata:
name: kafka-headless
spec:
clusterIP: None # Membaca sebagai Headless Service
selector:
app: kafka
ports:
- name: broker
port: 9092
2. Konektivitas Eksternal (Klien di Luar Kubernetes) #
Jika aplikasi produser atau konsumen berjalan di luar kluster Kubernetes, mereka tidak dapat menjangkau DNS internal kafka-headless.svc. Kita harus membuka akses menggunakan salah satu dari tiga metode berikut:
- NodePort: Membuka port tinggi (30000-32767) pada setiap node Kubernetes fisik. Klien terhubung ke alamat IP Node ditambah port unik yang dipetakan ke setiap broker.
- LoadBalancer (Rekomendasi Cloud): Membuat satu unit Load Balancer eksternal (seperti AWS NLB) untuk setiap pod broker Kafka secara terpisah.
- Ingress Controller: Menggunakan Ingress berbasis TCP (seperti NGINX Ingress Controller dengan konfigurasi SSL Passthrough) untuk mengarahkan trafik eksternal berdasarkan SNI (Server Name Indication) ke broker target.
Apapun metode eksternal yang kita gunakan, kita wajib mengonfigurasi parameter advertised.listeners pada setiap broker secara dinamis agar broker mengiklankan nama domain atau alamat IP eksternal yang dapat dijangkau oleh klien luar.
Strategi Ketersediaan Tinggi (High Availability) #
Untuk menjamin bahwa kluster Kafka kita tetap beroperasi meskipun terjadi kegagalan hardware pada host fisik Kubernetes, kita harus menerapkan aturan isolasi pod secara disiplin.
1. Menerapkan Pod Anti-Affinity #
Kita tidak boleh membiarkan dua pod broker dari kluster Kafka yang sama berjalan di satu server fisik Kubernetes (worker node) yang sama. Jika host fisik tersebut mati, kita akan kehilangan dua broker sekaligus, yang dapat memicu kegagalan quorum dan status partisi offline.
Berikut adalah konfigurasi podAntiAffinity yang wajib kita tambahkan pada spec template StatefulSet:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- kafka
topologyKey: kubernetes.io/hostname # Mencegah penjadwalan di host fisik yang sama
2. Menggunakan Topology Spread Constraints #
Jika kluster Kubernetes kita tersebar di beberapa zona ketersediaan (Availability Zones - AZ), kita harus memastikan bahwa pod broker Kafka terdistribusi secara merata di seluruh zona tersebut menggunakan topologySpreadConstraints dengan target topologyKey: topology.kubernetes.io/zone. Hal ini menjamin ketahanan kluster jika salah satu pusat data mengalami mati lampu total.
Orkestrasi Menggunakan Strimzi Operator #
Menulis dan memelihara ratusan baris berkas YAML manifest secara manual untuk StatefulSet, PVC, Service, dan rute jaringan Kafka sangat rawan kesalahan. Di ekosistem Kubernetes, pendekatan terbaik adalah menggunakan Strimzi Operator.
Strimzi adalah proyek sandbox CNCF yang menyediakan Kubernetes Operator khusus untuk menyederhanakan siklus hidup Apache Kafka menggunakan konsep Custom Resource Definitions (CRDs). Dengan Strimzi, kita mendefinisikan kluster Kafka sebagai objek Kubernetes kustom (Kafka). Operator Strimzi kemudian bertugas menerjemahkan objek kustom tersebut menjadi StatefulSet, Service, dan config map, serta memantau kesehatannya secara terus-menerus.
Berikut adalah contoh manifest YAML kustom untuk membuat kluster Kafka 3-node menggunakan Strimzi Operator:
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: my-production-cluster
namespace: kafka
spec:
kafka:
version: 3.7.0
replicas: 3
listeners:
- name: plain
port: 9092
type: internal
tls: false
- name: external
port: 9094
type: loadbalancer # Mengotomatiskan pembuatan AWS NLB per broker
tls: true
configuration:
bootstrap:
alternativeNames:
- kafka-bootstrap.mycorp.com
config:
offsets.topic.num.partitions: 50
offsets.topic.replication.factor: 3
transaction.state.log.replication.factor: 3
transaction.state.log.min.isr: 2
default.replication.factor: 3
min.insync.replicas: 2
inter.broker.protocol.version: "3.7"
storage:
type: persistent-claim
size: 500Gi
class: kafka-storage-class
zookeeper: # Opsional jika menggunakan KRaft mode kustom Strimzi
replicas: 3
storage:
type: persistent-claim
size: 100Gi
class: kafka-storage-class
Keunggulan Operasional Strimzi Operator: #
- Automated Rolling Updates: Ketika kita mengubah konfigurasi di YAML
Kafka, Strimzi akan melakukan restart bergilir (rolling restart) pada pod broker secara aman. Ia memverifikasi status ISR dan memastikan satu pod telah pulih sepenuhnya sebelum merestart pod berikutnya. - Rebalance Otomatis: Strimzi mengintegrasikan alat Cruise Control secara bawaan (
KafkaRebalance). Kita dapat memicu penyeimbangan ulang partisi kluster hanya dengan membuat manifest CRDKafkaRebalancesederhana. - Manajemen Pengguna dan Topik: Strimzi menyediakan operator terpisah (
KafkaUserdanKafkaTopic) sehingga kita dapat membuat topik baru dan hak akses ACL langsung dari pipa CI/CD menggunakan manifest GitOps Kubernetes.
Arsitektur Pod StatefulSet Kafka di Kubernetes #
Berikut adalah diagram yang memvisualisasikan bagaimana Strimzi Operator mengoordinasikan pod StatefulSet, PVC, Headless Service internal, dan Load Balancer eksternal untuk kluster Kafka kita:
flowchart TD
subgraph K8sCluster ["Kluster Kubernetes (Kubernetes Cluster)"]
subgraph Strimzi ["Strimzi Operator Control Plane"]
Operator["Strimzi Operator Pod"]
end
subgraph StatefulSet ["Kafka StatefulSet"]
Pod0["Pod: kafka-0"]
Pod1["Pod: kafka-1"]
Pod2["Pod: kafka-2"]
end
subgraph Services ["Kubernetes Services"]
HeadlessSvc["Headless Service (kafka-headless)"]
end
subgraph StorageClaims ["Persistent Volume Claims (PVC)"]
PVC0["PVC: data-kafka-0"]
PVC1["PVC: data-kafka-1"]
PVC2["PVC: data-kafka-2"]
end
subgraph ExternalLoadBalancers ["Kubernetes Load Balancer Services"]
LB0["LoadBalancer-0 (AWS NLB)"]
LB1["LoadBalancer-1 (AWS NLB)"]
LB2["LoadBalancer-2 (AWS NLB)"]
end
end
subgraph ExternalClients ["Klien di Luar Kubernetes (External Clients)"]
AppProducer["Aplikasi Produser (External)"]
AppConsumer["Aplikasi Konsumen (External)"]
end
Operator -.->|"Mengelola Siklus Hidup"| StatefulSet
Pod0 --- PVC0
Pod1 --- PVC1
Pod2 --- PVC2
StatefulSet --->|Mendaftarkan Host DNS| HeadlessSvc
LB0 --> Pod0
LB1 --> Pod1
LB2 --> Pod2
AppProducer --> LB0
AppConsumer --> LB1
Checklist Audit Kesiapan Kubernetes StatefulSet Produksi #
Lakukan langkah audit berikut sebelum merilis deployment Kubernetes Kafka kita ke tahap produksi:
| No | Item Audit Kepatuhan Kubernetes Kafka | Metode Verifikasi | Status |
|---|---|---|---|
| 1 | Objek StatefulSet Aktif | Pastikan tipe daya tampung broker menggunakan StatefulSet (bukan Deployment atau ReplicaSet). |
[ ] |
| 2 | Reclaim Policy Retain | Jalankan perintah kubectl get sc. Pastikan Storage Class data Kafka memiliki reclaim policy Retain. |
[ ] |
| 3 | Pod Anti-Affinity Berjalan | Jalankan perintah kubectl get pods -o wide. Pastikan tidak ada dua pod broker Kafka berada di node Kubernetes yang sama. |
[ ] |
| 4 | Tuning Memori Container | Pastikan batasan limits.memory kontainer disetel minimal 2 kali lipat dari ukuran JVM heap Kafka untuk menyisakan RAM page cache. |
[ ] |
| 5 | Advertised Listeners Benar | Jalankan perintah kubectl logs kafka-0 and periksa startup metadata log. Pastikan IP/domain eksternal yang diiklankan sudah benar. |
[ ] |
| 6 | Probes Konfigurasi Tepat | Verifikasi bahwa livenessProbe dan readinessProbe kontainer tidak disetel terlalu sensitif guna mencegah restarts beruntun saat rebalance. |
[ ] |
Ringkasan #
- Wajib Gunakan StatefulSet — Gunakan objek
StatefulSetdipadukan denganvolumeClaimTemplatesuntuk mengamankan identitas pod yang deterministik dan mempertahankan pemetaan volume disk 1-ke-1.- Amankan Data dengan Retain SC — Konfigurasikan kebijakan pemulihan Storage Class menggunakan
reclaimPolicy: Retainuntuk melindungi data log segment dari penghancuran tidak sengaja.- Tala Pemetaan Listener Eksternal — Konfigurasikan parameter
advertised.listenerssecara dinamis saat menggunakan LoadBalancer atau NodePort agar klien luar dapat menjangkau IP publik broker.- Gunakan Operator Strimzi — Manfaatkan Strimzi Operator untuk menyederhanakan manajemen operasional harian, termasuk rolling updates otomatis, rebalancing Cruise Control, dan GitOps Kafka.
← Sebelumnya: Kafka on Virtual Machines Berikutnya: Production Failure Scenarios →