Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka on Kubernetes

Production deployment guide for Apache Kafka on Kubernetes clusters.


Kubernetes ClusterNamespace: kafkaStatefulSet: kafkaNamespace: appsPVC-0PVC-1PVC-2Service: kafka-headlessService: kafka-bootstrapkafka-0kafka-1kafka-2ProducerConsumer

namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: kafka
---
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kafka-config
namespace: kafka
data:
server.properties: |
# KRaft mode
process.roles=broker,controller
node.id=${KAFKA_NODE_ID}
controller.quorum.voters=0@kafka-0.kafka-headless:9093,1@kafka-1.kafka-headless:9093,2@kafka-2.kafka-headless:9093
# Listeners
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
advertised.listeners=PLAINTEXT://${HOSTNAME}.kafka-headless.kafka.svc.cluster.local:9092
controller.listener.names=CONTROLLER
inter.broker.listener.name=PLAINTEXT
# Log configuration
log.dirs=/var/kafka/data
num.partitions=3
default.replication.factor=3
min.insync.replicas=2
# Performance
num.network.threads=8
num.io.threads=16
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
service-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: kafka-headless
namespace: kafka
labels:
app: kafka
spec:
type: ClusterIP
clusterIP: None
publishNotReadyAddresses: true
ports:
- name: client
port: 9092
targetPort: 9092
- name: controller
port: 9093
targetPort: 9093
selector:
app: kafka
service-bootstrap.yaml
apiVersion: v1
kind: Service
metadata:
name: kafka-bootstrap
namespace: kafka
labels:
app: kafka
spec:
type: ClusterIP
ports:
- name: client
port: 9092
targetPort: 9092
selector:
app: kafka
statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka
namespace: kafka
spec:
serviceName: kafka-headless
replicas: 3
podManagementPolicy: Parallel
selector:
matchLabels:
app: kafka
template:
metadata:
labels:
app: kafka
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: kafka
topologyKey: kubernetes.io/hostname
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-central1-a
- us-central1-b
- us-central1-c
terminationGracePeriodSeconds: 300
securityContext:
fsGroup: 1000
runAsUser: 1000
runAsNonRoot: true
containers:
- name: kafka
image: apache/kafka:4.3.0
ports:
- name: client
containerPort: 9092
- name: controller
containerPort: 9093
env:
- name: KAFKA_NODE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: KAFKA_HEAP_OPTS
value: "-Xms4g -Xmx4g"
command:
- bash
- -c
- |
export KAFKA_NODE_ID=${HOSTNAME##*-}
/opt/kafka/bin/kafka-storage.sh format -t $(cat /var/kafka/data/cluster-id 2>/dev/null || kafka-storage.sh random-uuid | tee /var/kafka/data/cluster-id) -c /etc/kafka/server.properties --ignore-formatted
exec /opt/kafka/bin/kafka-server-start.sh /etc/kafka/server.properties
resources:
requests:
memory: "6Gi"
cpu: "2"
limits:
memory: "8Gi"
cpu: "4"
volumeMounts:
- name: data
mountPath: /var/kafka/data
- name: config
mountPath: /etc/kafka
readinessProbe:
tcpSocket:
port: 9092
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 9092
initialDelaySeconds: 60
periodSeconds: 20
volumes:
- name: config
configMap:
name: kafka-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: ssd
resources:
requests:
storage: 100Gi

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "6000"
throughput: "500"
encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd
provisioner: disk.csi.azure.com
parameters:
skuName: PremiumV2_LRS
diskIOPSReadWrite: "6000"
diskMBpsReadWrite: "500"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: kafka-pdb
namespace: kafka
spec:
minAvailable: 2
selector:
matchLabels:
app: kafka

ComponentCPU RequestCPU LimitMemory RequestMemory Limit
Development500m12Gi4Gi
Production246Gi8Gi
High Throughput4812Gi16Gi
Memory LimitHeap SizeRecommended
4Gi2gDevelopment
8Gi4gProduction
16Gi8gHigh throughput

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: kafka-network-policy
namespace: kafka
spec:
podSelector:
matchLabels:
app: kafka
policyTypes:
- Ingress
- Egress
ingress:
# Inter-broker communication
- from:
- podSelector:
matchLabels:
app: kafka
ports:
- port: 9092
- port: 9093
# Client access from application namespaces
- from:
- namespaceSelector:
matchLabels:
kafka-client: "true"
ports:
- port: 9092
egress:
# Inter-broker communication
- to:
- podSelector:
matchLabels:
app: kafka
ports:
- port: 9092
- port: 9093
# DNS resolution
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP

apiVersion: v1
kind: Service
metadata:
name: kafka-external
namespace: kafka
spec:
type: NodePort
ports:
- name: client
port: 9092
nodePort: 30092
selector:
app: kafka
apiVersion: v1
kind: Service
metadata:
name: kafka-lb
namespace: kafka
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
spec:
type: LoadBalancer
ports:
- name: client
port: 9092
targetPort: 9092
selector:
app: kafka
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kafka-ingress
namespace: kafka
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
spec:
ingressClassName: nginx
rules:
- host: kafka.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kafka-bootstrap
port:
number: 9092

values.yaml
replicaCount: 3
image:
repository: apache/kafka
tag: "3.7.0"
pullPolicy: IfNotPresent
resources:
requests:
memory: "6Gi"
cpu: "2"
limits:
memory: "8Gi"
cpu: "4"
jvmOptions:
heapSize: "4g"
persistence:
enabled: true
storageClass: ssd
size: 100Gi
config:
numPartitions: 3
defaultReplicationFactor: 3
minInsyncReplicas: 2
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: kafka
topologyKey: kubernetes.io/hostname
podDisruptionBudget:
minAvailable: 2
metrics:
enabled: true
port: 9404

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kafka
namespace: kafka
spec:
selector:
matchLabels:
app: kafka
endpoints:
- port: metrics
interval: 30s
path: /metrics
# Add to StatefulSet containers
- name: jmx-exporter
image: bitnami/jmx-exporter:latest
ports:
- name: metrics
containerPort: 9404
args:
- "9404"
- "/etc/jmx-exporter/config.yaml"
volumeMounts:
- name: jmx-config
mountPath: /etc/jmx-exporter

spec:
updateStrategy:
type: RollingUpdate
podManagementPolicy: OrderedReady
Terminal window
# Update image
kubectl set image statefulset/kafka kafka=apache/kafka:3.8.0 -n kafka
# Monitor rollout
kubectl rollout status statefulset/kafka -n kafka
# Rollback if needed
kubectl rollout undo statefulset/kafka -n kafka

Rolling Update Considerations

Rolling updates should be performed during low-traffic periods. Each broker restart causes partition leadership changes and potential consumer rebalancing.