Back to posts

Kafka on GKE with ZooKeeper — StatefulSet

Read the full guide on docs.beyondyou.my.id
messagingkafkagkekuberneteszookeeperstatefulset

Kafka on GKE with ZooKeeper — StatefulSet Deployment

Table of Contents

SectionTopicDescription
01Why Kafka on GKEManaged Kubernetes for stateful messaging workloads.
02Architecture3-broker Kafka + 3-node ZooKeeper on GKE.
03Namespace & PrerequisitesCluster setup.
04ZooKeeper StatefulSetZooKeeper cluster with Headless Service.
05Kafka StatefulSetKafka brokers with ConfigMap and PVC.
06Kafka UIKafbat UI via Deployment.
07Topic OperationsCLI commands inside the cluster.
08Production ChecklistHardening for production workloads.

1. Why Kafka on GKE

Running Kafka on GKE gives you managed Kubernetes with persistent storage, autoscaling, and networking — without managing the underlying infrastructure.

OptionProsCons
VM + bare metalFull controlManual patching, scaling
Docker ComposeQuick dev setupNo HA, no auto-restart
GKE + StatefulSetHA, rolling updates, PVCMore YAML to manage
Managed Kafka (Confluent)Zero opsExpensive, vendor lock-in

Why StatefulSet

AspectDeploymentStatefulSet
Pod namesRandomStable (kafka-0, kafka-1, kafka-2)
NetworkEphemeralStable DNS via Headless Service
StorageShared or nonePer-pod PVC
ScalingAny orderOrdered, one at a time
IdentityNoneOrdinal index

2. Architecture

graph TB
    subgraph GKE["GKE Cluster"]
        subgraph ZK_NS["Namespace: kafka"]
            subgraph ZK["ZooKeeper Cluster"]
                zk0["zookeeper-0\n:2181"]
                zk1["zookeeper-1\n:2181"]
                zk2["zookeeper-2\n:2181"]
            end
            subgraph KAFKA["Kafka Cluster"]
                kafka0["kafka-0\n:9092"]
                kafka1["kafka-1\n:9092"]
                kafka2["kafka-2\n:9092"]
            end
            ui["Kafbat UI\n:8080"]
        end
    end

    producer["Producer"] --> kafka0
    producer --> kafka1
    producer --> kafka2
    consumer["Consumer"] --> kafka0
    consumer --> kafka1
    consumer --> kafka2
    kafka0 --> zk0
    kafka0 --> zk1
    kafka0 --> zk2
    kafka1 --> zk0
    kafka1 --> zk1
    kafka1 --> zk2
    kafka2 --> zk0
    kafka2 --> zk1
    kafka2 --> zk2
    ui --> kafka0

Resource Allocation

ComponentReplicasCPUMemoryStorage
ZooKeeper3250m512Mi5Gi
Kafka3500m1Gi10Gi
Kafbat UI1100m256Mi-

3. Namespace & Prerequisites

kubectl create namespace kafka

Private Registry Secret

kubectl create secret docker-registry regcred \
  --docker-server=asia-southeast2-docker.pkg.dev \
  --docker-username=_json_key \
  --docker-password="$(cat key.json)" \
  --docker-email=your@email.com \
  -n kafka

4. ZooKeeper StatefulSet

Headless Service

apiVersion: v1
kind: Service
metadata:
  name: zookeeper
  namespace: kafka
  labels:
    app: zookeeper
    app.kubernetes.io/name: zookeeper
    app.kubernetes.io/component: coordination
    app.kubernetes.io/part-of: kafka
spec:
  clusterIP: None
  selector:
    app: zookeeper
  ports:
  - name: client
    port: 2181
  - name: election
    port: 3888
  - name: leader
    port: 2888

StatefulSet

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zookeeper
  namespace: kafka
  labels:
    app: zookeeper
    app.kubernetes.io/name: zookeeper
    app.kubernetes.io/component: coordination
    app.kubernetes.io/part-of: kafka
spec:
  serviceName: zookeeper
  replicas: 3
  selector:
    matchLabels:
      app: zookeeper
  template:
    metadata:
      labels:
        app: zookeeper
    spec:
      securityContext:
        fsGroup: 1000
        runAsUser: 1000
        runAsGroup: 1000
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: zookeeper
              topologyKey: kubernetes.io/hostname
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: zookeeper
      containers:
      - name: zookeeper
        image: asia-southeast2-docker.pkg.dev/example-prd/devops/tools/cp-zookeeper:7.6.0
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 3888
          name: election
        - containerPort: 2888
          name: leader
        env:
        - name: ZOOKEEPER_CLIENT_PORT
          value: "2181"
        - name: ZOOKEEPER_TICK_TIME
          value: "2000"
        - name: ZOOKEEPER_INIT_LIMIT
          value: "10"
        - name: ZOOKEEPER_SYNC_LIMIT
          value: "5"
        - name: ZOOKEEPER_SERVERS
          value: "zookeeper-0.zookeeper.kafka.svc.cluster.local:2888:3888,zookeeper-1.zookeeper.kafka.svc.cluster.local:2888:3888,zookeeper-2.zookeeper.kafka.svc.cluster.local:2888:3888"
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
          limits:
            memory: 512Mi
        livenessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - echo ruok | nc localhost 2181 | grep imok
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - echo ruok | nc localhost 2181 | grep imok
          initialDelaySeconds: 10
          periodSeconds: 10
        volumeMounts:
        - name: zookeeper-data
          mountPath: /var/lib/zookeeper/data
  volumeClaimTemplates:
  - metadata:
      name: zookeeper-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

ZooKeeper Configuration Reference

ParameterValuePurpose
ZOOKEEPER_CLIENT_PORT2181Client connections
ZOOKEEPER_TICK_TIME2000Heartbeat interval (ms)
ZOOKEEPER_INIT_LIMIT10Leader election timeout
ZOOKEEPER_SYNC_LIMIT5Sync timeout
ZOOKEEPER_SERVERSDNS addressesPeer discovery

5. Kafka StatefulSet

Headless Service

apiVersion: v1
kind: Service
metadata:
  name: kafka-headless
  namespace: kafka
  labels:
    app: kafka
    app.kubernetes.io/name: kafka
    app.kubernetes.io/component: broker
    app.kubernetes.io/part-of: kafka
spec:
  clusterIP: None
  selector:
    app: kafka
  ports:
  - name: broker
    port: 9092

ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: kafka-config
  namespace: kafka
  labels:
    app: kafka
    app.kubernetes.io/name: kafka
    app.kubernetes.io/component: broker
    app.kubernetes.io/part-of: kafka
data:
  KAFKA_ZOOKEEPER_CONNECT: "zookeeper.kafka.svc.cluster.local:2181"
  KAFKA_DEFAULT_REPLICATION_FACTOR: "3"
  KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "3"
  KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "3"
  KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "2"
  KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
  KAFKA_DELETE_TOPIC_ENABLE: "true"
  KAFKA_LOG_RETENTION_HOURS: "168"

StatefulSet

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
  namespace: kafka
  labels:
    app: kafka
    app.kubernetes.io/name: kafka
    app.kubernetes.io/component: broker
    app.kubernetes.io/part-of: kafka
spec:
  serviceName: kafka-headless
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      securityContext:
        fsGroup: 1000
        runAsUser: 1000
        runAsGroup: 1000
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: kafka
              topologyKey: kubernetes.io/hostname
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: pool-type
                operator: In
                values:
                - messaging
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: kafka
      containers:
      - name: kafka
        image: asia-southeast2-docker.pkg.dev/example-prd/devops/tools/cp-kafka:7.6.0
        ports:
        - containerPort: 9092
          name: broker
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: KAFKA_BROKER_ID
          value: "$(POD_NAME)"
        - name: KAFKA_ADVERTISED_LISTENERS
          value: "PLAINTEXT://$(POD_NAME).kafka-headless.kafka.svc.cluster.local:9092"
        - name: KAFKA_LISTENERS
          value: "PLAINTEXT://0.0.0.0:9092"
        - name: KAFKA_LOG_DIRS
          value: "/var/lib/kafka/data"
        envFrom:
        - configMapRef:
            name: kafka-config
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
          limits:
            memory: 1Gi
        livenessProbe:
          tcpSocket:
            port: 9092
          initialDelaySeconds: 60
          periodSeconds: 30
        readinessProbe:
          tcpSocket:
            port: 9092
          initialDelaySeconds: 30
          periodSeconds: 10
        volumeMounts:
        - name: kafka-data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: kafka-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

Kafka Configuration Reference

ParameterValuePurpose
KAFKA_BROKER_ID$(POD_NAME)Unique broker ID from pod name
KAFKA_ADVERTISED_LISTENERSDNS nameClient-facing address
KAFKA_ZOOKEEPER_CONNECTzookeeper:2181ZooKeeper connection
KAFKA_DEFAULT_REPLICATION_FACTOR3Replicate across all brokers
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR3__consumer_offsets replication
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR2Min in-sync for transaction log
KAFKA_AUTO_CREATE_TOPICS_ENABLEfalsePrevent accidental topic creation
KAFKA_LOG_RETENTION_HOURS1687-day retention

6. Kafka UI

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kafbat-ui
  namespace: kafka
  labels:
    app: kafbat-ui
    app.kubernetes.io/name: kafbat-ui
    app.kubernetes.io/component: management
    app.kubernetes.io/part-of: kafka
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kafbat-ui
  template:
    metadata:
      labels:
        app: kafbat-ui
    spec:
      containers:
      - name: kafka-ui
        image: asia-southeast2-docker.pkg.dev/example-prd/devops/tools/kafka-ui:latest
        ports:
        - containerPort: 8080
          name: http
        env:
        - name: KAFKA_CLUSTERS_0_NAME
          value: "gke-cluster"
        - name: KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS
          value: "kafka-0.kafka-headless.kafka.svc.cluster.local:9092"
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            memory: 256Mi

Service

apiVersion: v1
kind: Service
metadata:
  name: kafbat-ui
  namespace: kafka
  labels:
    app: kafbat-ui
    app.kubernetes.io/name: kafbat-ui
    app.kubernetes.io/component: management
    app.kubernetes.io/part-of: kafka
spec:
  type: ClusterIP
  selector:
    app: kafbat-ui
  ports:
  - name: http
    port: 80
    targetPort: 8080

Access via Port Forward

kubectl port-forward svc/kafbat-ui 8080:80 -n kafka

7. Topic Operations

Create Topic

kubectl -n kafka exec -it kafka-0 -- \
  kafka-topics \
  --bootstrap-server kafka-0.kafka-headless.kafka.svc.cluster.local:9092 \
  --create \
  --topic my-topic \
  --partitions 3 \
  --replication-factor 3

List Topics

kubectl -n kafka exec -it kafka-0 -- \
  kafka-topics \
  --bootstrap-server kafka-0.kafka-headless.kafka.svc.cluster.local:9092 \
  --list

Describe Topic

kubectl -n kafka exec -it kafka-0 -- \
  kafka-topics \
  --bootstrap-server kafka-0.kafka-headless.kafka.svc.cluster.local:9092 \
  --describe \
  --topic my-topic

Delete Topic

kubectl -n kafka exec -it kafka-0 -- \
  kafka-topics \
  --bootstrap-server kafka-0.kafka-headless.kafka.svc.cluster.local:9092 \
  --delete \
  --topic my-topic

Consume Messages

kubectl -n kafka exec -it kafka-0 -- \
  kafka-console-consumer \
  --topic my-topic \
  --from-beginning \
  --bootstrap-server kafka-0.kafka-headless.kafka.svc.cluster.local:9092

8. Production Checklist

CategoryItemStatus
Replicationdefault.replication.factor >= 3Required
Min ISRmin.insync.replicas = 2Required
Topic creationauto.create.topics.enable = falseRequired
PersistencePVC with ReadWriteOnceRequired
Anti-affinitypodAntiAffinity across nodesRequired
Zone spreadtopologySpreadConstraintsRecommended
Security contextrunAsUser, fsGroupRecommended
Resource limitsCPU and memory setRequired
Health checksLiveness + readiness probesRequired
Log retentionlog.retention.hours tunedRequired
Internal LBcloud.google.com/load-balancer-type: InternalIf needed

References