Deploying Kafka Connect on Kubernetes with Strimzi
Apache Kafka Connect is the standard framework for streaming data between Kafka and external systems, but running it reliably in production means managing worker clusters, plugin images, credentials, and rolling restarts. Strimzi handles this declaratively by extending the Kubernetes API with custom resources that the Cluster Operator reconciles. This guide walks through a production deployment of Kafka Connect on Kubernetes with Strimzi: the operator model, the KafkaConnect resource, TLS and SASL authentication, declarative connector management, building plugin images, and monitoring with Prometheus.
Understanding the Strimzi operator model
Strimzi uses the operator pattern. The Cluster Operator watches for custom resources such as KafkaConnect and reconciles the desired state against the running cluster. When you create a KafkaConnect resource, the operator provisions a Deployment, configures the pods with the required environment variables, secrets, and volumes, and performs rolling updates when the spec changes.
This implements Kafka Connect’s distributed deployment mode natively on Kubernetes. Workers connect to the Kafka cluster through its bootstrap address and form a Connect group; the operator exposes the REST API through a Kubernetes Service. Connect integrates with the Schema Registry & Connect Ecosystem by setting converter and schema registry URLs directly in the Connect configuration.
Before deploying Connect, install the Strimzi Cluster Operator. It can watch a single namespace, several namespaces, or the whole cluster, depending on your multi-tenancy needs. Create the namespace first, then apply the install files. The namespace query parameter rewrites the role bindings to match the target namespace, so it must equal the -n value:
kubectl create namespace kafka-connect
kubectl apply -f 'https://strimzi.io/install/latest?namespace=kafka-connect' -n kafka-connect
Quote the URL so the shell does not interpret the ?. Verify the operator is running before proceeding:
kubectl get pods -n kafka-connect -l name=strimzi-cluster-operator
Defining the KafkaConnect custom resource
The KafkaConnect resource defines the desired state of the Connect cluster: worker replica count, the Kafka bootstrap address, internal topic settings, converters, resource limits, and (optionally) a plugin build. The example below connects to a Strimzi-managed Kafka cluster over TLS with mutual TLS authentication:
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnect
metadata:
name: production-connect-cluster
namespace: kafka-connect
annotations:
strimzi.io/use-connector-resources: "true"
spec:
version: 3.9.0
replicas: 3
bootstrapServers: production-kafka-bootstrap.kafka.svc:9093
tls:
trustedCertificates:
- secretName: production-cluster-ca-cert
certificate: ca.crt
authentication:
type: tls
certificateAndKey:
secretName: connect-tls-auth
certificate: user.crt
key: user.key
config:
group.id: production-connect-cluster
offset.storage.topic: connect-cluster-offsets
config.storage.topic: connect-cluster-configs
status.storage.topic: connect-cluster-status
config.storage.replication.factor: 3
offset.storage.replication.factor: 3
status.storage.replication.factor: 3
key.converter: io.confluent.connect.avro.AvroConverter
value.converter: io.confluent.connect.avro.AvroConverter
key.converter.schema.registry.url: http://schema-registry.kafka-connect.svc:8081
value.converter.schema.registry.url: http://schema-registry.kafka-connect.svc:8081
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
jvmOptions:
-Xms: 1g
-Xmx: 2g
A few production notes:
versionpins the Kafka version of the Connect workers. Pinning it keeps reconciliation deterministic across operator upgrades; omit it and the operator uses its default for the installed version.- Internal topics (
*.storage.topic) must be unique per Connect cluster. The operator does not create them for you with custom partition counts — Connect creates them on first start using the replication factors above, so ensure the broker allows topic auto-creation or pre-create them. group.idmust be unique across all Connect clusters that share the same Kafka cluster, or workers from different clusters will join the same group and corrupt each other’s assignments.- Avro converters (
io.confluent.connect.avro.AvroConverter) ship in Confluent’s plugin, not in the base Strimzi image. Add them through a build (below) or a custom image, or the workers will fail to start a connector that uses them. jvmOptionsuses the-Xms/-Xmxkeys exactly. If you omit them but set memoryrequests/limits, the operator derives a default heap from the limit.
The strimzi.io/use-connector-resources: "true" annotation tells the operator to manage connectors through KafkaConnector resources instead of the REST API, which is covered below.
Configuring security and authentication
Strimzi supports TLS encryption, mutual TLS authentication, and SASL mechanisms. The example above uses mutual TLS: the worker presents a client certificate and trusts the cluster CA, both stored in Kubernetes Secrets and mounted automatically. For a Strimzi-managed KafkaUser, the operator generates the user.crt/user.key secret for you.
For SASL/SCRAM-SHA-512 over a TLS-encrypted connection, replace the authentication block:
tls:
trustedCertificates:
- secretName: production-cluster-ca-cert
certificate: ca.crt
authentication:
type: scram-sha-512
username: connect-user
passwordSecret:
secretName: connect-scram-credentials
password: password
passwordSecret.password is the key within the named Secret that holds the password value, not the password itself. Keep tls configured alongside SASL so credentials are not sent in plaintext — SASL authenticates but does not encrypt. The Strimzi configuration guide covers TLS, authentication, and authorization options in full.
Managing connectors declaratively
With strimzi.io/use-connector-resources: "true" set on the KafkaConnect cluster, you manage each connector as a KafkaConnector resource. The operator translates it into Connect REST API calls and reconciles drift, so connectors live in Git alongside the rest of your infrastructure.
Credentials should never be inlined. Kafka’s EnvVarConfigProvider (in kafka-clients since 3.5.0) lets you expose a secret as a worker environment variable via externalConfiguration, then reference it from connector config. First, register the provider under an alias — here env — and mount the secret in the KafkaConnect spec. The alias you choose becomes the prefix in the placeholder (${env:...}):
spec:
config:
config.providers: env
config.providers.env.class: org.apache.kafka.common.config.provider.EnvVarConfigProvider
externalConfiguration:
env:
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-credentials
key: password
Then deploy a Debezium MySQL CDC connector that reads the password from that variable:
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnector
metadata:
name: mysql-inventory-connector
namespace: kafka-connect
labels:
strimzi.io/cluster: production-connect-cluster
spec:
class: io.debezium.connector.mysql.MySqlConnector
tasksMax: 1
autoRestart:
enabled: true
config:
database.hostname: mysql-primary.database.svc
database.port: "3306"
database.user: debezium
database.password: ${env:MYSQL_PASSWORD}
database.server.id: "184054"
topic.prefix: inventory
database.include.list: inventory
schema.history.internal.kafka.bootstrap.servers: production-kafka-bootstrap.kafka.svc:9093
schema.history.internal.kafka.topic: schema-changes.inventory
Two correctness points specific to this connector:
tasksMax: 1— the Debezium MySQL connector reads a single binlog and always runs exactly one task. Setting a higher value does not parallelize it.autoRestart— with it enabled, the Cluster Operator watches the connector and its tasks every reconciliation and restarts any that have failed, using an exponential back-off (immediate, then after 2, 6, 12, 20, 30, 42, 56 minutes, capped at 60). SetautoRestart.maxRestartsto give up after a fixed number of attempts; left unset, the operator retries indefinitely. This is genuine self-healing, not merely status reporting. The resource’sstatusstill reflects connector and task health for external alerting.
For many connectors across environments, template the KafkaConnector resources with Kustomize or Helm to keep staging and production consistent.
Building custom container images with plugins
Strimzi’s build feature lets the operator build a Connect image containing your plugins, so you do not maintain a separate Dockerfile and CI pipeline for images. The operator runs a Kaniko-based builder pod that downloads each artifact, assembles the image, and pushes it to your registry. Add a build section to the KafkaConnect spec:
spec:
build:
output:
type: docker
image: my-registry.io/kafka-connect-plugins:3.9.0-1
pushSecret: registry-credentials
plugins:
- name: debezium-mysql-connector
artifacts:
- type: zip
url: https://repo1.maven.org/maven2/io/debezium/debezium-connector-mysql/2.5.0.Final/debezium-connector-mysql-2.5.0.Final-plugin.zip
sha512sum: <archive-sha512>
Details that matter:
- Artifact
typemust be one ofjar,zip,tgz,maven, orother, and must match the URL. Debezium ships plugin archives as...-plugin.zip, so usetype: zip. Note the file name isdebezium-connector-mysql-2.5.0.Final-plugin.zip(hyphen-joined), not a nested path. sha512sumis optional but recommended in production: Strimzi verifies the downloaded archive against it, so a tampered or corrupted download fails the build instead of shipping silently.output.imageshould be a stable, versioned tag (notlatest) so that a rebuild produces a new tag and the workers actually roll to the new image.pushSecretis akubernetes.io/dockerconfigjsonSecret with push credentials for the registry.
Watch the builder pod and stream its logs while the image is built:
kubectl get pods -n kafka-connect -l strimzi.io/kind=KafkaConnectBuild
kubectl logs -f production-connect-cluster-build -n kafka-connect
When the build completes, the operator updates the Connect Deployment to the new image and performs a rolling restart. Because plugins are declared in the same manifest as the cluster, the whole configuration fits a GitOps workflow.
Monitoring and operating Kafka Connect clusters
Connect workers expose JMX metrics. Strimzi converts them to Prometheus format with the JMX Prometheus Exporter when you set metricsConfig to reference a ConfigMap. Metrics are then served on port 9404. Add this to the KafkaConnect spec:
spec:
metricsConfig:
type: jmxPrometheusExporter
valueFrom:
configMapKeyRef:
name: connect-metrics
key: metrics-config.yml
Create the referenced ConfigMap with the exporter rules. The patterns below match Connect’s worker- and task-level MBeans:
apiVersion: v1
kind: ConfigMap
metadata:
name: connect-metrics
namespace: kafka-connect
data:
metrics-config.yml: |
lowercaseOutputName: true
lowercaseOutputLabelNames: true
rules:
- pattern: "kafka.connect<type=connect-worker-metrics><>([a-z-]+)"
name: kafka_connect_worker_metrics_$1
- pattern: "kafka.connect<type=connect-worker-metrics, connector=([^:]+)><>([a-z-]+)"
name: kafka_connect_worker_connector_metrics_$2
labels:
connector: "$1"
- pattern: "kafka.connect<type=connect-metrics, client-id=([^:]+)><>([a-z-]+)"
name: kafka_connect_metrics_$2
labels:
client_id: "$1"
Key signals to alert on:
connector-failed-task-countandtask-startup-failure-percentagefromconnect-worker-metrics— a task entering theFAILEDstate.connector-countdropping unexpectedly, which indicates connectors disappearing from a worker.- Sink connector offset lag — track consumer group lag on the connector’s group, since Connect reuses the consumer group machinery for sink tasks.
The garbled metric-exporter output from earlier drafts of this topic is replaced above with the structure from Strimzi’s official kafka-connect-metrics.yaml example. The Apache Kafka Connect monitoring reference lists every MBean.
Combine metrics with the resource status. The operator records readiness in the KafkaConnect resource’s status.conditions; inspect it directly:
kubectl describe kafkaconnect production-connect-cluster -n kafka-connect
For logging, override the Connect Log4j configuration through the logging property of the KafkaConnect spec (inline or via a ConfigMap) and forward worker logs to your aggregator — Elasticsearch, Loki, or similar — so connector failures can be correlated with worker restarts.
Handling upgrades and rollbacks
Strimzi performs a controlled rolling restart of the worker pods whenever you change the replicas, config, version, or build section. Connect’s group rebalancing redistributes tasks as workers cycle, so connectors keep running across the roll provided the cluster has enough replicas to absorb one pod at a time.
Before upgrading the operator itself, read the version-specific notes in the Strimzi deployment and upgrade guide — CRD API versions and defaults change between releases. Back up your KafkaConnect and KafkaConnector manifests and rehearse the upgrade in a staging cluster that mirrors production.
To roll back a bad change, revert the resource in Git and reapply; the operator reconciles back to the declared state. For a single misbehaving connector, set spec.state: stopped (or paused) on its KafkaConnector — which supersedes the older spec.pause field — or delete the resource without touching the cluster, then resume once fixed.
Treating Connect clusters this way — provisioned, scaled, and decommissioned through the same Kubernetes-native workflows as your stateless services — is what makes Strimzi worthwhile: the plugin image, credentials, connectors, and cluster topology all live in one reconciled, version-controlled place.