Kafka MirrorMaker 2 on Kubernetes: Deployment Patterns That Work
The standard way to run Kafka MirrorMaker on Kubernetes is MirrorMaker 2 managed by the Strimzi operator, declared as a KafkaMirrorMaker2 custom resource. Two alternatives exist — a plain Deployment running the dedicated connect-mirror-maker.sh driver, or MirrorMaker 2 connectors added to a Kafka Connect cluster you already operate — and each earns its place in a specific situation.
This guide compares the three patterns, walks through a current Strimzi manifest field by field, and covers the decisions Kubernetes forces on you: scaling, placement, replication policy, cross-cluster networking, and what to watch once it runs.
Use Strimzi's KafkaMirrorMaker2 resource unless you cannot run an operator. Pick your replication policy before the first record flows, keep the offset-syncs topic in the target cluster, and scale replicas and tasksMax together — one without the other adds pods that do nothing.
Three ways to run Kafka MirrorMaker on Kubernetes
First, a naming cleanup. Apache Kafka 4.0 removed the original MirrorMaker: the kafka-mirror-maker.sh script that ships in every 3.x release is gone from the 4.0 distribution, following its deprecation under KIP-720. Every current deployment of "MirrorMaker on Kubernetes" means MirrorMaker 2, which runs as a set of Kafka Connect connectors. If the connector architecture is new to you, start with the MirrorMaker explainer and come back.
That leaves three realistic patterns on Kubernetes:
| Pattern | What runs | Best for | Tradeoff |
|---|---|---|---|
Strimzi KafkaMirrorMaker2 resource | Operator-managed Connect workers, declared as one custom resource | Most production Kubernetes estates | Requires the Strimzi operator and Kubernetes 1.30 or newer |
Plain Deployment + connect-mirror-maker.sh | The dedicated MM2 driver with a ConfigMap-mounted properties file | Small one-way flows where no operator is welcome | You own upgrades, secret rotation, restarts, and there is no resource status to query |
| Connectors on an existing Kafka Connect cluster | MirrorSourceConnector and MirrorCheckpointConnector on workers you already run | Teams already operating a Connect cluster | Connector JSON managed by hand, and mirroring shares a failure domain with every other connector |
The plain-Deployment option uses the same properties file covered in the MirrorMaker 2 setup tutorial — Kubernetes just supplies the container and the config mount. It works, but you give up rolling updates, declarative TLS wiring, and status reporting that the operator provides for free.
Our recommendation: the Strimzi resource, unless operator installation is genuinely off the table. The rest of this post focuses there.
Deploying MirrorMaker 2 with the Strimzi KafkaMirrorMaker2 resource
Strimzi 1.1.0, the current release at the time of writing, defaults to Kafka 4.3.0 and declares MirrorMaker 2 through the kafka.strimzi.io/v1 API. A production-shaped manifest for one-way replication from cluster-a to cluster-b looks like this, adapted from the example Strimzi ships:
apiVersion: kafka.strimzi.io/v1
kind: KafkaMirrorMaker2
metadata:
name: dr-mirror
spec:
version: 4.3.0
replicas: 2
target:
alias: cluster-b
bootstrapServers: cluster-b-kafka-bootstrap:9092
groupId: dr-mirror-group
configStorageTopic: __dr-mirror-config
offsetStorageTopic: __dr-mirror-offset
statusStorageTopic: __dr-mirror-status
config:
config.storage.replication.factor: -1
offset.storage.replication.factor: -1
status.storage.replication.factor: -1
mirrors:
- source:
alias: cluster-a
bootstrapServers: cluster-a-kafka-bootstrap:9092
sourceConnector:
tasksMax: 4
config:
replication.factor: -1
offset-syncs.topic.replication.factor: -1
sync.topic.acls.enabled: "false"
offset-syncs.topic.location: "target"
checkpointConnector:
tasksMax: 1
config:
checkpoints.topic.replication.factor: -1
sync.group.offsets.enabled: "true"
emit.checkpoints.interval.seconds: 60
sync.group.offsets.interval.seconds: 60
refresh.groups.interval.seconds: 600
offset-syncs.topic.location: "target"
topicsPattern: ".*"
groupsPattern: ".*"
The fields that deserve attention:
targetnames the cluster the Connect workers attach to, plus the three internal topics Connect stores its own state in. The__prefix is deliberate — internal topics prefixed with__are excluded from mirroring by default, so the mirror does not try to mirror its own bookkeeping.-1replication factors tell Kafka to use the broker's default replication factor rather than hardcoding one.offset-syncs.topic.location: "target"stores the offset-sync topic in the target cluster, so MirrorMaker only needs read access to the source. The value must match betweensourceConnectorandcheckpointConnector.sync.group.offsets.enabled: "true"writes translated consumer group offsets into the target cluster so consumers can fail over near where they left off. How that translation works — and where it breaks — is the subject of the offset sync guide.
Apply it and check the resource status:
kubectl apply -f mirror-maker-2.yaml
kubectl get kafkamirrormaker2 dr-mirror
One migration note for anyone with older manifests: Strimzi 1.0.0 removed the v1beta2 API entirely. The old schema declared a connectCluster field plus a clusters array; the v1 schema replaces both with the target block and an inline source per mirror. Manifests written for Strimzi 0.4x need converting before they apply to a 1.x operator.
Scaling: replicas, tasksMax, and where to run it
Two knobs control capacity, and they only work together. spec.replicas sets how many Connect worker pods run — that is your availability and horizontal capacity. tasksMax on each connector sets how many tasks that connector may spread across those workers — that is your parallelism, ceilinged by the source topic's partition count. Raising replicas without raising tasksMax adds pods with nothing to do; raising tasksMax beyond partition count creates tasks with no partitions to read.
Sizing those numbers against throughput, and the lag metrics that tell you whether the sizing worked, are covered in the MirrorMaker best practices guide — the knobs behave the same on Kubernetes as anywhere else.
Placement is the Kubernetes-specific decision. Strimzi's documentation states the expectation plainly: a MirrorMaker 2 cluster is required at each target destination. Run the mirror in the Kubernetes cluster (and region) of the target Kafka cluster, so it consumes over the long-haul link and produces locally. The consume side tolerates a flaky wide-area network far better than the produce side does.
Resource requests and limits go in the pod template like any other Strimzi-managed workload. Since Strimzi 1.0.0, MirrorMaker 2 nodes also support in-place pod resizing, so a memory bump no longer forces a rolling restart on clusters that enable it.
Topic naming: pick the replication policy before the first record
MirrorMaker 2 decides target topic names through its replication policy, and the choice is effectively permanent once data flows.
The default, DefaultReplicationPolicy, prefixes each mirrored topic with the source cluster alias: orders from cluster-a becomes cluster-a.orders. The prefix is how MirrorMaker detects mirroring cycles, which is what makes bidirectional and active-active topologies workable — a topic never gets replicated back to the cluster it came from.
org.apache.kafka.connect.mirror.IdentityReplicationPolicy keeps original names. Strimzi documents it as suitable for unidirectional replication, migration, and failover — the cases where consumers should find orders under the same name after switching clusters. Understand what you give up: with renaming off, nothing marks a topic as replicated, so the policy provides no cycle detection. Keep the topology strictly one-way.
sourceConnector:
config:
replication.policy.class: "org.apache.kafka.connect.mirror.IdentityReplicationPolicy"
checkpointConnector:
config:
replication.policy.class: "org.apache.kafka.connect.mirror.IdentityReplicationPolicy"
Strimzi flags this as an IMPORTANT in its own docs, so it bears repeating: the policy class (and separator, if customized) must be identical on the source connector and the checkpoint connector. A mismatch leaves checkpoints pointing at topic names that do not exist. And switching policies after records have flowed strands everything already written under the old names — decide before you apply.
Crossing Kubernetes cluster boundaries
When both Kafka clusters live in the same Kubernetes cluster, the manifest above just works: cluster-b-kafka-bootstrap:9092 is a normal in-cluster Service DNS name.
Disaster recovery mirrors rarely have that luxury — the whole point is a second region, which usually means a second Kubernetes cluster. Then the source bootstrap address must be reachable from the MirrorMaker pods like any external client: an external listener on the source Kafka (LoadBalancer, NodePort, or Ingress-based, depending on your platform), TLS on the wire, and client credentials for MirrorMaker. In the v1 schema that attaches to the cluster entry:
mirrors:
- source:
alias: cluster-a
bootstrapServers: cluster-a.example.com:9094
tls:
trustedCertificates:
- secretName: cluster-a-ca-cert
pattern: "*.crt"
This is where offset-syncs.topic.location: "target" pays off a second time: with the sync topic in the target, MirrorMaker needs only read permissions against the remote source cluster. Grant a read-only principal on the source and keep every write local.
Watching it run: status, logs, and metrics
The operator gives MirrorMaker 2 an operational surface that a plain Deployment lacks. Three places to look, in order:
- Resource status.
kubectl get kafkamirrormaker2shows READY, and the resource.statusreports each connector's state — a failedMirrorSourceConnectorshows up here before it shows up as missing data. - Pod logs. The workers are ordinary pods;
kubectl logson them surfaces connect-level errors, authentication failures, and rebalance churn. - Metrics. Add a
metricsConfigblock to the resource and the workers expose Prometheus metrics for scraping, same as any Strimzi component.
Which lag metrics matter and what thresholds to alert on is territory the best practices guide already owns; verifying that checkpoints actually translate offsets is covered in the offset sync guide.
MirrorMaker on Kubernetes is still replication, not backup
Running MirrorMaker 2 under an operator changes how it is deployed, not what it is. The mirror copies whatever the source produces — including the accidental topic deletion, the poison-pill deploy, and the corrupted batch — to the target in near real time. A perfectly healthy mirror gives you a second copy of the mistake, in a second region.
Recovery from those failures needs an independent, point-in-time copy that lives outside both clusters. On Kubernetes that layer can be as declarative as the mirror itself: the Kafka backup on Kubernetes solution covers the operator-managed pattern, the Strimzi integration covers wiring it to Strimzi-managed clusters, and the Strimzi backup walkthrough shows the custom-resource workflow end to end.
MirrorMaker 2 copies mistakes as faithfully as it copies good records. Kafka Backup's Kubernetes operator keeps independent, point-in-time restorable copies of topics and consumer offsets alongside your mirrors — declared as custom resources, just like the rest of your platform.
Frequently asked questions
How do I run Kafka MirrorMaker 2 on Kubernetes?
The standard approach is the Strimzi operator: declare a KafkaMirrorMaker2 custom resource naming the target cluster, the source cluster, and the connector configuration, and the operator runs the Connect workers as pods. Alternatives are a plain Deployment running connect-mirror-maker.sh with a mounted properties file, or adding the MirrorMaker connectors to an existing Kafka Connect cluster.
Does Strimzi support MirrorMaker 2?
Yes. Strimzi provides a dedicated KafkaMirrorMaker2 custom resource, and as of Strimzi 1.x it uses the kafka.strimzi.io/v1 API with a target block plus per-mirror source entries. Strimzi 1.1.0 defaults to Kafka 4.3.0. The old MirrorMaker 1 resource is gone, matching the removal of MirrorMaker 1 in Apache Kafka 4.0.
Can MirrorMaker 2 replicate between two different Kubernetes clusters?
Yes. Run MirrorMaker 2 in the Kubernetes cluster hosting the target Kafka, expose the source Kafka through an external listener such as a LoadBalancer with TLS, and point the mirror source bootstrapServers at that address. With offset-syncs.topic.location set to target, MirrorMaker only needs read access to the remote source cluster.
What happened to the original Kafka MirrorMaker on Kubernetes?
Apache Kafka 4.0 removed the original MirrorMaker after its deprecation under KIP-720, and the kafka-mirror-maker.sh script no longer ships in 4.x releases. Strimzi removed its MirrorMaker 1 resource in step. Any current MirrorMaker deployment on Kubernetes means MirrorMaker 2, which runs on Kafka Connect.
Should MirrorMaker 2 run on the source or target cluster?
Run it at the target. Strimzi's documentation expects a MirrorMaker 2 cluster at each target destination: the workers consume from the remote source over the long-haul link and produce locally, which tolerates wide-area network problems far better than remote produces would.
Wrapping up
MirrorMaker 2 on Kubernetes comes down to a handful of decisions made in the right order. Choose the deployment pattern — Strimzi's KafkaMirrorMaker2 resource for almost everyone. Choose the replication policy before the first record, because it names every topic the mirror will ever create. Keep the offset-syncs topic in the target so the source stays read-only. Scale replicas and tasksMax together, run the mirror where the target lives, and watch the resource status rather than guessing. Then give the whole arrangement the layer no mirror provides: an independent backup that can put back what replication faithfully copied away.