
Amazon Data Firehose, formerly known as Amazon Kinesis Data Firehose, is a managed service that loads streaming data into destinations such as Amazon S3, Amazon Redshift, Amazon OpenSearch Service, and Splunk with built-in transformations and auto-scaling. It removes the operational burden of running streaming infrastructure, but it ties delivery pipelines to AWS-specific IAM authentication, per-GB ingestion and delivery pricing, and a fixed set of supported destinations and transform mechanisms.
Apache Kafka is a distributed event streaming platform that handles high-throughput, fault-tolerant messaging with persistent storage and replay capabilities. Combined with Kafka Connect, it provides a connector framework for reliably moving data between Kafka and external systems. Strimzi is a Kubernetes operator that automates deploying, managing, and scaling Kafka clusters through Custom Resource Definitions (CRDs), providing a self-hosted alternative to Amazon Data Firehose with infrastructure-based costs and support for custom destinations and transforms.
This article explains how to deploy Apache Kafka with Strimzi on Kubernetes as an alternative to Amazon Data Firehose. It covers installing the Strimzi operator, deploying a Kafka cluster in KRaft mode, and configuring Kafka Connect with sink connectors for Object Storage. It also covers stream transformations with Single Message Transforms (SMTs), authentication and encryption, monitoring with Prometheus and Grafana, and migration from Amazon Data Firehose.
Apache Kafka and Kafka Connect map directly to Amazon Data Firehose components, providing equivalent functionality through a self-hosted Kafka and Kafka Connect deployment.
The Kafka with Strimzi architecture consists of:
Before you begin, you need to:
The Strimzi operator watches for Kafka-related Custom Resources and manages the underlying Kubernetes resources (StrimziPodSets, Services, ConfigMaps) automatically.
Add the Strimzi Helm repository.
Update the Helm repository cache.
Create a dedicated namespace for Kafka resources.
Install the Strimzi Kafka operator. The --version flag pins the operator to a release whose CRDs still serve the kafka.strimzi.io/v1beta2 API used throughout this article. Starting with Strimzi 1.0.0, Strimzi supports only the v1 CRD API, so manifests that use v1beta2 are not compatible with Strimzi 1.0.0 or newer.
Verify that the operator pod is running.
The output displays a pod named strimzi-cluster-operator-* with status Running.
Verify that the CRDs are installed.
The output lists CRDs including kafkas.kafka.strimzi.io, kafkanodepools.kafka.strimzi.io, kafkatopics.kafka.strimzi.io, kafkaconnects.kafka.strimzi.io, and kafkausers.kafka.strimzi.io.
In KRaft mode (Kafka Raft), Kafka manages its own metadata quorum and no longer depends on ZooKeeper. Strimzi models this with KafkaNodePool resources that define groups of nodes acting as brokers, controllers, or both.
Create the KafkaNodePool manifest for the controller and broker nodes.
Add the following configuration.
Save and close the file.
In this configuration:
controller pool: Three nodes that participate in the KRaft quorum for metadata management.broker pool: Three nodes that store topic partitions and serve client traffic.roles: Defines whether each node acts as a controller, broker, or both. Separating roles is recommended for production.storage.type: persistent-claim: Uses Persistent Volume Claims for data durability across pod restarts. The sizes above (40 GB for controllers, 50 GB for brokers) meet common block storage minimum volume sizes. Increase the broker volumes to match your topic retention and partition footprint.Apply the KafkaNodePool manifest.
Create the Kafka cluster manifest.
Add the following configuration.
Save and close the file.
In this configuration:
strimzi.io/node-pools: enabled: Enables the KafkaNodePool resources defined in the previous step.strimzi.io/kraft: enabled: Activates KRaft mode, removing the ZooKeeper dependency.metadataVersion: Sets the KRaft metadata version compatible with the configured Kafka version.listeners: Configures both plaintext (port 9092) and Transport Layer Security (TLS) encrypted (port 9093) internal listeners.min.insync.replicas: 2: Requires at least two replicas to acknowledge writes for durability.entityOperator: Deploys the Topic and User operators for managing KafkaTopic and KafkaUser resources.Apply the Kafka cluster manifest.
Monitor the cluster deployment progress.
Wait until the READY column shows True. This may take several minutes as Strimzi provisions the StrimziPodSets and waits for all pods to become ready.
Verify that all Kafka pods are running.
The output displays three controller pods, three broker pods, and entity operator pods, all with status Running.
Kafka topics are the primary mechanism for organizing and storing messages. Sink connectors consume from these topics, so the topics must exist before the connectors are configured.
Create the KafkaTopic manifest for the main data ingestion topic.
Add the following configuration.
Save and close the file.
In this configuration:
partitions: 12: Creates 12 partitions for parallel processing. Choose a partition count based on expected throughput and consumer parallelism.replicas: 3: Replicates each partition across all three brokers for fault tolerance.retention.ms: 604800000: Retains messages for 7 days (604800000 milliseconds).cleanup.policy: delete: Removes messages after the retention period expires.segment.bytes: 1073741824: Sets segment file size to 1 GB for efficient disk I/O.Apply the topic manifest.
Create additional topics for different data streams.
Add the following configuration.
Save and close the file.
Apply the additional topics.
Verify that all topics are created.
The output lists the events, logs, and metrics topics with their partition and replica counts.
Kafka Connect provides a framework for running connectors that stream data between Kafka and external systems. Strimzi builds a custom Connect image with the S3 sink connector plugin and runs it as a KafkaConnect resource.
Create a Kubernetes Secret that holds your container registry credentials. Strimzi uses this Secret to push the custom Connect image, and the cluster's worker nodes use it to pull the image. Replace REGISTRY-HOSTNAME, REGISTRY-USERNAME, and REGISTRY-PASSWORD with values from your container registry (for example, the hostname, username, and API key from the Overview tab). Set REGISTRY-HOSTNAME to the registry host only, such as ams.vultrcr.com, without the https:// scheme and without the registry name path. The registry name belongs in the image path in a later step, not in the server value.
Create the KafkaConnect manifest with the S3 sink connector plugin.
Add the following configuration, replacing REGISTRY-HOSTNAME with your registry hostname and REGISTRY-NAMESPACE with your registry namespace or project path.
Save and close the file.
In this configuration:
annotations.strimzi.io/use-connector-resources: Enables managing connectors via KafkaConnector CRDs instead of the REST API.replicas: 2: Deploys two Connect workers for high availability and load distribution.bootstrapServers: Points to the Kafka cluster's internal bootstrap service.offset.storage.topic, config.storage.topic, status.storage.topic: Internal topics for storing connector state.template.pod.imagePullSecrets: Lets the worker pods pull the built image from the private container registry.build: Builds a custom Connect image with the S3 sink connector plugin using Strimzi's build capability and pushes it to the configured container registry.Apply the KafkaConnect manifest.
Monitor the build and deployment progress.
Wait until the READY column shows True. The initial build may take several minutes.
Verify that the Connect worker pods are running.
The output displays two Connect worker pods with status Running.
Check the available connector plugins.
The output includes io.confluent.connect.s3.S3SinkConnector confirming the S3 plugin is installed.
A sink connector delivers Kafka messages to S3-compatible object storage. Storing credentials in a Kubernetes Secret and non-sensitive settings (bucket name, region, endpoint) in a ConfigMap keeps the connector manifests free of environment-specific values.
Create a Kubernetes Secret with object storage credentials. Replace OBJECT-STORAGE-ACCESS-KEY and OBJECT-STORAGE-SECRET-KEY with your S3-compatible access credentials.
Create a Kubernetes ConfigMap with the bucket and endpoint settings. Replace OBJECT-STORAGE-BUCKET, OBJECT-STORAGE-REGION, and OBJECT-STORAGE-ENDPOINT with values from your object storage provider. Set OBJECT-STORAGE-ENDPOINT to the hostname only (without https://).
Update the KafkaConnect resource to expose the Secret and ConfigMap values as environment variables on the Connect worker pods.
Add the following connectContainer block under the existing spec.template block, at the same indentation as the pod block already present there.
Save and close the file.
The Strimzi operator injects each value as an environment variable into the Connect worker container. Connector configurations reference these variables using the ${strimzienv:VAR_NAME} substitution syntax, which Strimzi auto-registers via the EnvVarConfigProvider on every Connect worker.
Apply the updated KafkaConnect configuration.
Wait for the Connect cluster to roll the worker pods.
Create the S3 sink connector manifest.
Add the following configuration.
Save and close the file.
In this configuration:
tasksMax: 3: Creates three connector tasks for parallel processing across topic partitions.s3.bucket.name, s3.region, store.url: Resolved at runtime from the S3_BUCKET, S3_REGION, and S3_ENDPOINT environment variables sourced from the s3-config ConfigMap.aws.access.key.id, aws.secret.access.key: Resolved at runtime from the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables sourced from the s3-credentials Secret.format.class: Writes records as JSON files. Alternatives include AvroFormat and ParquetFormat.partitioner.class: TimeBasedPartitioner: Organizes files by time-based directory structure.path.format: Creates directories like year=2024/month=06/day=15/hour=14.flush.size: 1000: Writes a file after receiving 1000 records.rotate.interval.ms: 600000: Forces file rotation every 10 minutes regardless of record count.Apply the S3 sink connector.
Verify that the connector is running.
The output shows the s3-sink-events connector with READY status True.
Check the connector status for any errors.
Configure additional connectors to deliver data to multiple destinations from the same Kafka topics, similar to Amazon Data Firehose's multi-destination capability. Each collapsible below is optional and requires a backing service deployed in the cluster before you configure the connector.
Single Message Transforms (SMTs) modify records as they flow through Kafka Connect, providing functionality similar to Amazon Data Firehose Lambda transforms.
Create a connector with SMTs for data enrichment.
Add the following configuration with transformation chains.
Save and close the file.
The configuration applies a chain of four transforms to every record:
addTimestamp: Inserts a processed_at field with the time the connector handled the record.addSource: Inserts a static source field set to kafka-connect for downstream lineage.filterNull: Drops records that match the isNullValue predicate, which uses RecordIsTombstone to identify Kafka tombstone records.maskSensitive: Replaces the values of the email and ssn fields with [REDACTED] to prevent personally identifiable information from reaching the sink.Apply the transformed sink connector.
Tune connector flush intervals and batch sizes to balance latency versus throughput, similar to Amazon Data Firehose buffering configuration.
Create a connector with optimized batching for high-throughput scenarios.
Add the following configuration.
Save and close the file.
In this configuration:
flush.size: 10000: Writes a file after receiving 10,000 records.rotate.interval.ms: 900000: Forces file rotation every 15 minutes.rotate.schedule.interval.ms: 3600000: Schedules rotation at hourly boundaries.s3.part.size: 26214400: Uses 25 MB parts for multipart uploads.errors.deadletterqueue.topic.name: Routes failed records to a dead-letter queue topic for later analysis.Apply the batched sink connector.
Create the dead-letter queue topic.
Add the following configuration.
Save and close the file.
Apply the dead-letter queue topic.
Configure Simple Authentication and Security Layer (SASL) authentication with the Salted Challenge Response Authentication Mechanism (SCRAM) and TLS encryption to secure Kafka communication, replacing Amazon Data Firehose's IAM-based security model. Enable the authorizer on the Kafka cluster first so that KafkaUser Access Control List (ACL) rules are accepted by the operator.
Update the Kafka cluster to add a TLS-protected SASL listener and enable simple authorization.
Add an authorization block under spec.kafka, at the same indentation as the existing listeners and config keys.
Add the following sasl listener entry to the existing spec.kafka.listeners list, below the tls entry.
Save and close the file.
In this configuration:
authorization.type: simple: Enables Kafka's built-in ACL authorizer. Without this block, the User Operator rejects any KafkaUser that declares ACL rules.sasl listener: Adds a TLS-encrypted listener on port 9094 that authenticates clients with SCRAM-SHA-512 credentials.Apply the updated Kafka cluster configuration and wait for the rolling update to finish.
Create the KafkaUser manifest.
Add the following configuration.
Save and close the file.
The connect- prefix ACLs on topics and consumer groups let each sink connector create its internal offset topic and join a dedicated consumer group named after the connector (for example, connect-s3-sink-events).
Apply the KafkaUser.
Verify that the user is Ready and that a credentials Secret was generated.
The output shows True under the READY column after the User Operator has reconciled.
To connect a client that runs outside the cluster, retrieve the plaintext SCRAM password from the generated Secret with kubectl get secret connect-user -n kafka -o jsonpath='{.data.password}' | base64 -d. In-cluster components such as KafkaConnect read the password directly from the Secret, so this value is not needed elsewhere in this guide.
Update KafkaConnect to use SASL authentication.
Change the existing spec.bootstrapServers value from port 9092 to the SASL listener on port 9094.
Add the following tls and authentication blocks under spec, at the same indentation as bootstrapServers.
Save and close the file.
Apply the updated KafkaConnect configuration and wait for the worker pods to roll onto the new SASL/TLS configuration.
The first worker pod to restart under the new configuration can briefly crash loop with NoSuchFileException: /tmp/kafka/cluster.truststore.p12, because the mounted CA certificate Secret volume is not always fully populated before the container's truststore-generation step runs on the first attempt. The pod recovers automatically on a subsequent restart within about a minute. If the kubectl wait command above times out, check kubectl get pods -n kafka -l strimzi.io/cluster=kafka-connect before troubleshooting further. The pods are often already Running by then.
Prometheus scrapes the Kafka metrics that Strimzi's JMX exporters expose, and Grafana visualizes them, replacing CloudWatch monitoring.
Enable Strimzi metrics exporters in the Kafka cluster.
Add the metricsConfig section under spec.kafka:
Save and close the file.
Create the metrics configuration ConfigMap.
Add the following configuration.
Save and close the file.
Apply the metrics ConfigMap.
Apply the updated Kafka cluster configuration.
Strimzi now exposes Prometheus metrics on port 9404 of each broker and controller pod. The next steps deploy the Prometheus Operator and create a PodMonitor that scrapes those endpoints directly, because the pods expose this port but the Kafka Services do not. The PodMonitor resource depends on the monitoring.coreos.com/v1 Custom Resource Definition that the Prometheus Operator installs.
Add the Prometheus Community Helm repository.
Update the Helm repository cache.
Install the kube-prometheus-stack.
The podMonitorSelectorNilUsesHelmValues=false setting allows Prometheus to discover PodMonitor resources outside the Helm release, including the Kafka PodMonitor you create in the next steps.
Wait for the Prometheus and Grafana pods to become ready.
If Grafana remains in Error or CrashLoopBackOff, inspect the pod with kubectl describe pod -l app.kubernetes.io/name=grafana -n monitoring and increase memory limits or add a node with more capacity.
Create the PodMonitor manifest that tells Prometheus to scrape Kafka metrics. A PodMonitor is required instead of a ServiceMonitor because Strimzi exposes the tcp-prometheus metrics port directly on the broker and controller pods. The headless *-kafka-brokers Service it generates does not include that port.
Add the following configuration.
Save and close the file.
The matchExpressions selector matches pods from any Kafka, KafkaConnect, or KafkaMirrorMaker2 resource in the namespace. This PodMonitor can also scrape KafkaConnect worker pods after you enable metricsConfig on the KafkaConnect resource, following the same pattern shown above for the Kafka cluster.
Apply the PodMonitor.
Verify that Prometheus discovered the Kafka scrape targets.
Port-forward the Prometheus UI and open Status, followed by Targets, to confirm kafka-resources-metrics endpoints appear as UP.
Open http://localhost:9090/targets in a web browser.
Access Grafana using port-forwarding.
Open http://localhost:3000 in a web browser and log in with username admin and password admin.
Open Connections, then Data sources in the left-side menu, and confirm that a Prometheus data source is listed. The kube-prometheus-stack chart provisions it automatically through a Grafana sidecar, but this step can fail silently. If a Prometheus data source already appears, continue to the next step.
If no Prometheus data source is present, click Add new data source and select Prometheus. Set the Prometheus server URL to the in-cluster Prometheus service, then scroll to the bottom and click Save & test. Verify that Grafana reports the data source is working.
Download a Strimzi dashboard definition. This example downloads the Kafka broker dashboard. The grafana-dashboards folder also includes strimzi-kafka-connect.json for Kafka Connect metrics.
In Grafana, click the + icon in the top-right corner and select Import dashboard. Under Upload dashboard JSON file, drag in or browse to the downloaded strimzi-kafka.json, or paste its contents into the Import via dashboard JSON model field. Click Load.
In the import screen, set the Prometheus data source selector to the Prometheus data source you verified earlier, then click Import.
The dashboard populates after Prometheus has scraped the kafka-resources-metrics targets.
Validate the complete deployment by testing end-to-end data flow from a producer through Kafka to the S3 sink. The Kafka cluster now requires SASL/SCRAM authentication over TLS, so the test client uses the connect-user credentials and the cluster CA certificate.
Verify that the connector and all its tasks are running.
The READY column shows True. If any task reports FAILED, inspect the trace with kubectl exec -n kafka kafka-connect-connect-0 -- curl -s localhost:8083/connectors/s3-sink-events/status.
Create a client pod manifest that mounts the cluster CA certificate and exposes the SCRAM password as an environment variable.
Add the following configuration.
Save and close the file.
Apply the client pod manifest and wait for it to become ready.
Generate the client properties file inside the pod with the SASL credentials and the cluster truststore path. Use /bin/bash -lc so the pod's PASSWORD environment variable expands inside the container rather than in your local shell.
Produce a batch of 1500 test messages with the same key so they land on one topic partition and cross the connector's flush.size threshold. Files materialize in the bucket within seconds rather than waiting on rotate.interval.ms.
Wait about 30 seconds for the connector to commit the batch.
List the objects in the bucket. Replace OBJECT-STORAGE-ACCESS-KEY, OBJECT-STORAGE-SECRET-KEY, OBJECT-STORAGE-REGION, OBJECT-STORAGE-ENDPOINT, and OBJECT-STORAGE-BUCKET with the values from your s3-credentials Secret and s3-config ConfigMap.
This command runs the AWS CLI in a temporary pod, so no local installation is required. The output displays JSON files organized by the time-based partitioning structure, for example kafka-events/events/year=2026/month=06/day=25/hour=17/events+0+0000000000.json.
This command passes the Object Storage secret key on the command line, so it is recorded in the pod's container logs. The --rm flag deletes the pod immediately after the command exits, but rotate the key afterward if the cluster is not disposable, or browse the bucket in the Vultr Console instead to avoid exposing the credentials.
Verify the data format by downloading and inspecting one of the listed files. Replace the placeholders as before, and replace the object key with one from the previous listing. This command carries the same credential-exposure risk described in the previous step.
Each line is a JSON record matching the messages produced earlier.
Verify topic delivery by consuming directly from Kafka. Pass --group verify-group so the consumer joins the group authorized in the connect-user ACLs.
The output displays the messages produced earlier.
Delete the client pod after verification completes.
Migrating from Amazon Data Firehose to Kafka Connect involves mapping Firehose streams to topic and connector pairs, updating producer applications, and converting Lambda-based transforms to Kafka Connect SMTs or Kafka Streams. The service was formerly known as Amazon Kinesis Data Firehose. API resources still use names such as DeliveryStreamName. The guidance below assumes you completed Deploy Kafka Cluster, Deploy Kafka Connect, and Set Up Authentication and Encryption earlier in this article.
An Amazon Data Firehose stream bundles a source, a destination, and buffering rules into a single managed resource. Kafka Connect splits these responsibilities across two Strimzi resources: a KafkaTopic that holds the records and a KafkaConnector that delivers them to the destination.
KafkaConnector name for each Firehose stream. In the Firehose API, this is DeliveryStreamName.PutRecord API.COPY command. It does not write directly to Redshift. Model this with an S3 sink connector and your existing warehouse load tooling rather than the JDBC sink alone.BufferingHints uses IntervalInSeconds (0–900, default 300) and SizeInMBs (1–128 MiB, default 5). If you set one, you must set the other. Map IntervalInSeconds to the connector's rotate.interval.ms (multiply seconds by 1000). SizeInMBs is a byte-size threshold with no direct Kafka Connect equivalent. Approximate it by tuning flush.size based on your average record size. Use s3.part.size for multipart upload chunk sizing, not as a direct Firehose buffer equivalent.Producer changes depend on how data enters Firehose.
If producers use Firehose Direct PUT (the most common pattern), replace the Firehose API with Apache Kafka client libraries:
PutRecord / PutRecordBatch: producer.send() (Amazon Data Firehose uses PutRecordBatch, not Amazon Kinesis Data Streams PutRecords)DeliveryStreamName: Kafka topic nameIf producers write to Amazon Kinesis Data Streams that Amazon Data Firehose reads as a source, replace the Kinesis Data Streams API and optionally the Kinesis Producer Library (KPL):
PutRecord / PutRecords: producer.send()StreamName: Kafka topic namePartitionKey: Kafka message key, which Kafka hashes to assign records to partitionsbatch.size, linger.ms, and compression.typeIn both cases, update producers to use SASL/SCRAM credentials and the cluster TLS certificate configured in Set Up Authentication and Encryption. Refer to the Kafka producer configuration documentation for tuning options.
Firehose invokes Lambda transforms synchronously on each buffered batch before delivery. Lambda buffering uses separate hints (BufferSizeInMBs up to 3 MB and IntervalInSeconds up to 900 in the processing configuration), distinct from destination BufferingHints.
Kafka Connect offers two equivalent mechanisms with different operational characteristics:
Unlike Lambda transforms, which scale to zero between invocations, Kafka Streams applications run continuously and consume dedicated compute resources. Account for this cost-model difference when planning capacity.
Firehose destination settings such as S3 prefix patterns, file formats, and compression map to Kafka Connect sink connector configuration keys.
Prefix expressions such as !{timestamp:yyyy/MM/dd/HH} map to the connector's topics.dir, partitioner.class, and path.format settings. Refer to the Firehose custom prefix documentation and the S3 Sink Connector partitioning documentation.ErrorOutputPrefix (for example errors/!{firehose:error-output-type}/!{timestamp:yyyy/MM/dd}) maps to the connector's dead-letter queue topic configured with errors.deadletterqueue.topic.name. Route that topic to Object Storage with a second sink connector if you require failed records on disk, similar to Firehose writing failed records under ErrorOutputPrefix.CompressionFormat values (GZIP, ZIP, Snappy, HADOOP_SNAPPY, or UNCOMPRESSED) map to the connector's format class and s3.compression.type setting.format.class setting combined with a schema-aware converter such as Avro or Protobuf and an AWS Glue schema equivalent in your Kafka ecosystem.BufferingHints (IntervalInSeconds, SizeInMBs) map to rotate.interval.ms, flush.size, and s3.part.size as described under Configure Buffering and Batching.For destinations with existing data that needs to be preserved:
format.class.path.format to the existing Firehose Prefix scheme so that downstream queries and data catalogs continue to discover new partitions automatically.Review the following operational differences before cutting production traffic over to the new deployment.
replicas and tasksMax on the KafkaConnect resource, or by configuring a Horizontal Pod Autoscaler driven by consumer lag metrics.IncomingRecords (ingestion volume), DeliveryToS3.Success (successful S3 deliveries), and BackupToS3.Records (records written to the S3 backup bucket when backup is enabled).KafkaUser ACLs. Map each Firehose IAM role to a KafkaUser with the minimum set of topic and group ACLs required for its workload.RecordsPerSecondLimit and BytesPerSecondLimit. Kafka throughput is bounded by your cluster's broker count, partition count, and disk and network capacity.You have deployed Apache Kafka with Strimzi on Kubernetes as a self-hosted alternative to Amazon Data Firehose. The deployment includes a Kafka cluster, Kafka Connect with S3-compatible sink connectors, stream transformations using SMTs, SASL/SCRAM authentication with TLS encryption, and Prometheus monitoring. For additional configuration options, connector plugins, and advanced features, visit the official Strimzi documentation and Kafka Connect documentation.
0 Comments
Be the first to comment and share your perspective with the community.