KubeBlocks supports three Kafka deployment topologies:
| Topology | Node layout | Coordination | Use Case |
|---|---|---|---|
| combined / combined_monitor | Single Component; every pod runs broker + controller roles | KRaft (no ZooKeeper) | Smaller clusters; simplified operations; fewer pods to manage |
| separated / separated_monitor | Controller and broker Components are independent; scale each separately | KRaft (no ZooKeeper) | Production workloads; large clusters; independent controller and broker scaling |
| kafka2-external-zk | Broker-only Component; coordination delegated to external ZooKeeper | ZooKeeper (external) | Legacy Kafka 2.7 deployments that already have a ZooKeeper ensemble |
The *_monitor variants add a standalone kafka-exporter Component that scrapes Kafka-specific metrics (consumer group lag, partition offsets, topic throughput) and exposes them on port 9308 for Prometheus.
Configuration templates and configs: the Kafka ComponentDefinition treats main config slots (for example kafka-configuration-tpl) as externally managed in current addon charts. When you create a Cluster, you must wire those slots by setting configs on the matching component (or sharding template) to ConfigMaps whose keys match the template file names — typically the ConfigMaps shipped with the addon in kb-system, or your own copies in the application namespace. If provisioning fails with a message about missing templates, compare your manifest to the Kafka examples in kubeblocks-addons for the same chart version.
In the combined topology every pod simultaneously acts as both a broker (stores and serves partition data) and a controller (participates in the KRaft metadata quorum). There is a single KubeBlocks Component (kafka-combine) for all combined nodes, and the entire set of pods forms both the KRaft controller quorum and the broker cluster.
kafka-cluster-kafka-combine-advertised-listener-0:9092,...kafka-{n}.kafka-cluster-kafka-combine-headless:9092kafka container on each node — not a separate metadata deployment · Raft consensus for cluster metadata · one active controller at a timeCluster → Component (kafka-combine) → InstanceSet → Pod × N
→ Component (kafka-exporter) → InstanceSet → Pod × 1 [combined_monitor only]
| Resource | Role |
|---|---|
| Cluster | User-facing declaration — specifies topology, combined node count, storage, and resources |
| Component (kafka-combine) | Generated automatically; references the cmpd-kafka-combine ComponentDefinition; all pods are identical (each runs broker + controller) |
| Component (kafka-exporter) | Optional; present in combined_monitor only; references cmpd-kafka-exporter; scrapes Kafka cluster metrics and exposes them on port 9308 |
| InstanceSet | KubeBlocks custom workload (replaces StatefulSet); manages pods with stable identities |
| Pod | Actual running combined node; each pod gets a unique ordinal and its own PVC |
KubeBlocks provisions kafka-combine first; kafka-exporter (if present) starts after.
Each combined pod runs two containers. A kafkatool init container runs before the main containers start, copying the /sasl directory to /shared-tools/sasl for SASL authentication support:
| Container | Port | Purpose |
|---|---|---|
kafka | 9092 (client), 9093 (controller quorum), 9094 (inter-broker) | Kafka node running in combined broker,controller mode (KAFKA_CFG_PROCESS_ROLES=broker,controller); serves producer/consumer traffic on 9092; participates in KRaft consensus on 9093; replicates partition data between brokers on 9094 |
jmx-exporter | 5556 | JMX-based Prometheus metrics exporter; scrapes the local JVM's JMX registry and exposes all Kafka JMX metrics |
Each pod mounts a single PVC at /bitnami/kafka. Kafka stores the partition log data under /bitnami/kafka/data and the KRaft metadata log under /bitnami/kafka/metadata, both on the same PVC.
Since Kafka 3.3 (KIP-833), the KRaft metadata quorum fully replaces ZooKeeper. In the combined topology every pod is a controller-eligible node, so the whole pod set forms the controller quorum:
| KRaft Concept | Description |
|---|---|
| Controller quorum | All combined pods participate in Raft consensus on port 9093 to manage cluster metadata — topic configurations, partition assignments, and ISR lists |
| Active controller | The Raft leader among controllers; all metadata writes go through it; elected automatically via Raft |
| Metadata log | An internal Kafka topic (__cluster_metadata) replicated across all controller-eligible pods; brokers tail this log to stay current |
| Quorum tolerance | 3 pods tolerate 1 failure; 5 pods tolerate 2 failures |
A combined node that fails loses both its broker and controller roles simultaneously. The remaining quorum elects a new active controller; partition leaders for the affected partitions are re-elected from the ISR.
Kafka clients do not use a single ClusterIP service for all brokers. Instead, KubeBlocks creates one per-pod ClusterIP service (via podService: true) for each combined pod so that every broker can advertise a unique, stable address:
| Service | Type | Port | Notes |
|---|---|---|---|
{cluster}-kafka-combine-advertised-listener-{n} | ClusterIP | 9092 | One service per pod; clients use all per-pod addresses as the bootstrap seed list |
{cluster}-kafka-combine-headless | Headless | — | All pods; always created; used for internal cluster bus (port 9093 and 9094) and operator access |
Clients bootstrap by connecting to the seed list of per-pod ClusterIP addresses on port 9092. After bootstrap, the client fetches the full cluster metadata and connects directly to the partition leader for each partition using the advertised per-pod address.
# Bootstrap seed list (all per-pod ClusterIP addresses)
{cluster}-kafka-combine-advertised-listener-0.{namespace}.svc.cluster.local:9092,
{cluster}-kafka-combine-advertised-listener-1.{namespace}.svc.cluster.local:9092,
{cluster}-kafka-combine-advertised-listener-2.{namespace}.svc.cluster.local:9092
The combined topology provisions only the kafka-combine Component. The combined_monitor topology additionally provisions the kafka-exporter Component, which connects to the Kafka cluster and exposes consumer group lag, topic/partition offsets, and throughput metrics on port 9308. If you use combined_monitor, configure your Prometheus scrape target to point at the kafka-exporter pod on port 9308.
In the separated topology the controller and broker roles are placed in independent KubeBlocks Components, each backed by its own InstanceSet. This allows you to scale and update controllers and brokers independently and provides clear operational isolation between the metadata plane (controllers) and the data plane (brokers).
kafka-cluster-kafka-broker-advertised-listener-0:9092,...kafka-{n}.kafka-cluster-kafka-broker-headless:9092Cluster → Component (kafka-controller) → InstanceSet → Pod × N
→ Component (kafka-broker) → InstanceSet → Pod × N
→ Component (kafka-exporter) → InstanceSet → Pod × 1 [separated_monitor only]
| Resource | Role |
|---|---|
| Cluster | User-facing declaration — specifies topology, controller count, broker count, storage per component type, and resources |
| Component (kafka-controller) | References cmpd-kafka-controller; controller-eligible pods only; forms the KRaft quorum; no client traffic |
| Component (kafka-broker) | References cmpd-kafka-broker; broker-only pods; serves all producer/consumer traffic; fetches metadata from controllers |
| Component (kafka-exporter) | Optional; present in separated_monitor only; scrapes Kafka metrics and exposes them on port 9308 |
| InstanceSet | KubeBlocks custom workload; manages pods within each Component with stable identities |
| Pod | Actual running process; each pod gets a unique ordinal and its own PVC |
KubeBlocks provisions the components in order: kafka-controller first (the quorum must elect an active controller before brokers can register), then kafka-broker, then kafka-exporter (if present). On termination, the order reverses.
Controller pods (no init containers):
| Container | Port | Purpose |
|---|---|---|
kafka | 9093 (controller quorum) | Kafka node running as controller only (KAFKA_CFG_PROCESS_ROLES=controller); participates in KRaft Raft consensus; manages cluster metadata; does not serve client traffic |
jmx-exporter | 5556 | JMX-based Prometheus metrics exporter |
Each controller pod mounts a PVC at /bitnami/kafka for the KRaft metadata log (/bitnami/kafka/metadata).
Broker pods (init container: kafkatool copies /sasl to /shared-tools/sasl before startup):
| Container | Port | Purpose |
|---|---|---|
kafka | 9092 (client), 9094 (inter-broker) | Kafka node running as broker only (KAFKA_CFG_PROCESS_ROLES=broker); serves producer/consumer requests; replicates partition data to other brokers on port 9094; fetches metadata from the active controller |
jmx-exporter | 5556 | JMX-based Prometheus metrics exporter |
Each broker pod mounts a PVC at /bitnami/kafka/data for partition log storage.
Exporter pod (present in separated_monitor only; no init containers):
| Container | Port | Purpose |
|---|---|---|
kafka-exporter | 9308 | Standalone Kafka metrics exporter — connects to the broker cluster and exposes consumer group lag, topic/partition offsets, and throughput in Prometheus format |
The controller Component forms a dedicated KRaft quorum. Brokers do not participate in the quorum — they only consume the __cluster_metadata log and register themselves with the active controller:
| KRaft Concept | Description |
|---|---|
| Controller quorum | All controller pods run Raft consensus on port 9093; replicate the __cluster_metadata log |
| Active controller | The current Raft leader; brokers send all metadata updates (topic creation, ISR changes) through it |
| Broker registration | Each broker pod fetches the controller quorum address on startup and registers itself with the active controller |
| Quorum tolerance | 3 controller pods tolerate 1 failure; 5 tolerate 2. Broker count has no effect on quorum tolerance |
The controller quorum size and the broker count are independently configurable in the separated topology. A common production pattern is 3 controller pods + N broker pods, where N scales with data throughput requirements.
Controllers have no client-facing service. Only broker pods get per-pod ClusterIP services:
| Service | Type | Port | Notes |
|---|---|---|---|
{cluster}-kafka-broker-advertised-listener-{n} | ClusterIP | 9092 | One per broker pod (podService: true); use all as bootstrap seed list |
{cluster}-kafka-broker-headless | Headless | — | All broker pods; internal use (inter-broker replication on port 9094, operator access) |
{cluster}-kafka-controller-headless | Headless | — | All controller pods; used by brokers to reach the controller quorum on port 9093 |
Clients connect to broker pods only. The controller headless service is for internal Kafka use:
# Bootstrap seed list (all broker per-pod ClusterIP addresses)
{cluster}-kafka-broker-advertised-listener-0.{namespace}.svc.cluster.local:9092,
{cluster}-kafka-broker-advertised-listener-1.{namespace}.svc.cluster.local:9092,
...
The separated topology provisions only kafka-controller and kafka-broker. The separated_monitor topology additionally provisions the kafka-exporter Component on port 9308 for consumer group and partition metrics.
The kafka2-external-zk topology deploys Kafka 2.7 in the traditional ZooKeeper-based mode. Instead of the KRaft metadata quorum, an external ZooKeeper ensemble (deployed as a separate KubeBlocks cluster or external service) provides cluster coordination, controller election, and topic metadata storage.
Cluster → Component (kafka-broker) → InstanceSet → Pod × N
→ Component (kafka-exporter) → InstanceSet → Pod × 1
The kafka-broker ComponentDefinition for Kafka 2.x declares a serviceRefDeclaration named kafkaZookeeper (required; matches ZooKeeper 3.5–3.9). The Cluster CR must provide a serviceRef pointing to an available ZooKeeper ensemble before the broker component can start:
spec:
topology: kafka2-external-zk
serviceRefs:
- name: kafkaZookeeper
namespace: <zk-namespace>
cluster: <zk-cluster-name>
Each broker pod runs two containers. The kafkatool init container copies /sasl to /shared-tools/sasl before startup, as in Kafka 3.x:
| Container | Port | Purpose |
|---|---|---|
kafka | 9092 (client), 9094 (inter-broker) | Kafka 2.7 broker node; uses ZooKeeper for metadata (topic configs, controller election, ISR management); serves producer/consumer traffic on 9092; replicates partition data on 9094 |
jmx-exporter | 5556 | JMX-based Prometheus metrics exporter |
Each broker pod mounts a PVC at /bitnami/kafka/data for partition log storage.
In Kafka 2.x, ZooKeeper is responsible for all coordination tasks that KRaft handles in Kafka 3.x:
| ZooKeeper Role | Description |
|---|---|
| Controller election | One broker is elected Kafka controller via a ZooKeeper ephemeral node; it manages partition leader assignments |
| Topic metadata | Topic configurations, partition assignments, and ISR lists are stored as ZooKeeper znodes |
| Broker registration | Each broker registers an ephemeral node in ZooKeeper on startup; the controller detects broker failures via session expiry |
| SASL credentials | SCRAM-SHA-256/512 credentials are provisioned using kafka-configs.sh --zookeeper (via the KubeBlocks accountProvision lifecycle action) |
Kafka 2.x is a legacy topology. It requires an operational ZooKeeper ensemble and does not support the KRaft metadata management or the operational simplifications available in Kafka 3.x. For new deployments, use combined or separated.
Traffic routing is identical to Kafka 3.x brokers — per-pod ClusterIP services via advertised-listener:
| Service | Type | Port | Notes |
|---|---|---|---|
{cluster}-kafka-broker-advertised-listener-{n} | ClusterIP | 9092 | One per broker pod; use all as bootstrap seed list |
{cluster}-kafka-broker-headless | Headless | — | All broker pods; inter-broker replication on port 9094 |
Partition leader election governs data availability and is the core HA mechanism across all Kafka topologies. The active KRaft controller (or ZooKeeper-elected controller in Kafka 2.x) manages all partition leader assignments:
| Concept | Description |
|---|---|
| Partition leader | The single broker responsible for all reads and writes for a given partition; elected by the active controller |
| Follower replicas | Brokers that replicate the leader's partition log via port 9094; form the In-Sync Replica (ISR) set |
| ISR (In-Sync Replicas) | Replicas fully caught up with the leader; only ISR members are eligible to become the new leader |
| Leader election | When a partition leader fails, the active controller selects the next leader from the current ISR set |
| Unclean leader election | Disabled by default (unclean.leader.election.enable=false); enabling it risks data loss but allows recovery when the ISR is empty |
The replication factor for each topic determines how many ISR replicas exist per partition. A replication factor of 3 tolerates 1 broker failure per partition without any data loss or unavailability.
When a Kafka node fails, KubeBlocks and Kafka's internal protocols respond as follows:
NOT_LEADER_OR_FOLLOWER error, refresh metadata from any available broker, and reconnect to the new partition leaders; brief retries are normalKubeBlocks automatically creates the following Kafka SASL accounts for the broker and combined components. Credentials are stored in Secrets named {cluster}-{component}-account-{name}.
| Account | Purpose |
|---|---|
admin | Superuser account for cluster administration — topic management, ACL configuration, quota management; injected as KAFKA_ADMIN_USER / KAFKA_ADMIN_PASSWORD and added to super.users |
client | Default client SASL account for producer and consumer authentication — use the credentials from the {cluster}-{component}-account-client Secret in your Kafka client configuration |
Both accounts use SCRAM-SHA-256 and SCRAM-SHA-512 for authentication. In Kafka 3.x, credentials are stored in the __cluster_metadata log. In Kafka 2.x (kafka2-external-zk), credentials are provisioned via kafka-configs.sh --zookeeper during the KubeBlocks accountProvision lifecycle action.
The kafka-controller Component in the separated topology and the kafka-exporter Component in monitor topologies do not have system accounts — accounts are only needed on the broker-eligible components (kafka-broker and kafka-combine).