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:

  1. 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.
  2. Pemetaan Volume Persisten 1-ke-1: Menggunakan fitur volumeClaimTemplates pada StatefulSet menjamin bahwa setiap pod (misalnya kafka-0) selalu terhubung ke berkas penyimpanan fisik (Persistent Volume - PV) yang sama secara eksklusif. Jika pod kafka-0 mati dan hidup kembali di node lain, ia akan otomatis memasang kembali PV lamanya tanpa kehilangan data.
  3. 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 status OOMKilled.
  • Solusi: Kita harus menyetel limits.memory secara 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 hanya requests.memory untuk 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: #

  1. 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.
  2. Rebalance Otomatis: Strimzi mengintegrasikan alat Cruise Control secara bawaan (KafkaRebalance). Kita dapat memicu penyeimbangan ulang partisi kluster hanya dengan membuat manifest CRD KafkaRebalance sederhana.
  3. Manajemen Pengguna dan Topik: Strimzi menyediakan operator terpisah (KafkaUser dan KafkaTopic) 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 StatefulSet dipadukan dengan volumeClaimTemplates untuk 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: Retain untuk melindungi data log segment dari penghancuran tidak sengaja.
  • Tala Pemetaan Listener Eksternal — Konfigurasikan parameter advertised.listeners secara 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 →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact