Amazon MSK: Running Apache Kafka in Production on AWS

Kafka is one of the most capable event streaming platforms available, and one of the most operationally demanding to run yourself. Broker configuration tuning, ZooKeeper replacement with KRaft, partition leadership rebalancing after broker failures, TLS certificate management, and version upgrades that require rolling restarts across the cluster. Amazon MSK takes on all of that and gives you a fully compatible Kafka cluster you manage through standard Kafka tooling.

In this article I will walk through provisioning a production MSK cluster with Terraform, configuring producers and consumers with the settings that matter for reliability, and using MSK Serverless for teams that want Kafka without cluster management.

Provisioning MSK with Terraform

resource "aws_msk_configuration" "main" {
  name              = "production-msk-config"
  kafka_versions    = ["3.6.0"]
  server_properties = <<-EOF
    auto.create.topics.enable=false
    default.replication.factor=3
    min.insync.replicas=2
    num.partitions=6
    log.retention.hours=168
    log.retention.bytes=107374182400
    log.segment.bytes=1073741824
    socket.send.buffer.bytes=102400
    socket.receive.buffer.bytes=102400
    socket.request.max.bytes=104857600
    replica.fetch.max.bytes=10485760
    message.max.bytes=10485760
  EOF
}

resource "aws_msk_cluster" "main" {
  cluster_name           = "production-kafka"
  kafka_version          = "3.6.0"
  number_of_broker_nodes = 6

  broker_node_group_info {
    instance_type   = "kafka.m5.xlarge"
    client_subnets  = var.private_subnet_ids
    security_groups = [aws_security_group.msk.id]

    storage_info {
      ebs_storage_info {
        volume_size            = 1000
        provisioned_throughput {
          enabled           = true
          volume_throughput = 250
        }
      }
    }
  }

  configuration_info {
    arn      = aws_msk_configuration.main.arn
    revision = aws_msk_configuration.main.latest_revision
  }

  encryption_info {
    encryption_in_transit {
      client_broker = "TLS"
      in_cluster    = true
    }
    encryption_at_rest_kms_key_arn = aws_kms_key.msk.arn
  }

  client_authentication {
    sasl {
      iam = true
    }
  }

  open_monitoring {
    prometheus {
      jmx_exporter  { enabled_in_broker = true }
      node_exporter { enabled_in_broker = true }
    }
  }

  logging {
    broker_logs {
      cloudwatch_logs {
        enabled   = true
        log_group = aws_cloudwatch_log_group.msk.name
      }
      s3 {
        enabled = true
        bucket  = aws_s3_bucket.msk_logs.bucket
        prefix  = "msk-logs/"
      }
    }
  }

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Six broker nodes across three AZs gives you two brokers per AZ. With default.replication.factor=3 and min.insync.replicas=2, each partition has three replicas spread across AZs, and producers require acknowledgment from at least two replicas before a write is confirmed. This configuration survives the loss of an entire AZ without data loss or write failures.

auto.create.topics.enable=false is a production requirement. With auto-creation enabled, a misconfigured producer can create topics with wrong partition counts or replication factors that are expensive to fix later. Manage topic creation explicitly through your infrastructure code.

IAM authentication removes the need for username/password or certificate-based client authentication. Producers and consumers use their IAM role and AWS Signature V4 signing. No credentials to rotate, no certificate expiry to track.

Creating Topics

resource "aws_msk_cluster_policy" "main" {
  cluster_arn = aws_msk_cluster.main.arn
  policy      = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { AWS = aws_iam_role.msk_admin.arn }
      Action    = ["kafka-cluster:*"]
      Resource  = "${aws_msk_cluster.main.arn}/*"
    }]
  })
}

resource "kafka_topic" "orders" {
  name               = "orders"
  replication_factor = 3
  partitions         = 18

  config = {
    "cleanup.policy"       = "delete"
    "retention.ms"         = "604800000"
    "min.insync.replicas"  = "2"
    "compression.type"     = "lz4"
  }
}

resource "kafka_topic" "order_events_dlq" {
  name               = "orders.DLQ"
  replication_factor = 3
  partitions         = 6

  config = {
    "cleanup.policy" = "delete"
    "retention.ms"   = "2592000000"
  }
}

Producer Configuration

from kafka import KafkaProducer
import boto3
from aws_msk_iam_sasl_signer import MSKAuthTokenProvider

def create_msk_producer(bootstrap_servers: list) -> KafkaProducer:
    def oauth_cb(oauth_config):
        auth_token, expiry_ms = MSKAuthTokenProvider.generate_auth_token("us-east-1")
        return auth_token, expiry_ms / 1000

    return KafkaProducer(
        bootstrap_servers    = bootstrap_servers,
        security_protocol    = "SASL_SSL",
        sasl_mechanism       = "OAUTHBEARER",
        sasl_oauth_token_provider = oauth_cb,
        acks                 = "all",
        retries              = 10,
        retry_backoff_ms     = 200,
        max_in_flight_requests_per_connection = 1,
        enable_idempotence   = True,
        compression_type     = "lz4",
        batch_size           = 65536,
        linger_ms            = 10,
        buffer_memory        = 67108864
    )

acks=all combined with enable_idempotence=True is the configuration that prevents data loss and duplicates. acks=all waits for all in-sync replicas to acknowledge the write. Idempotent producers assign sequence numbers to messages, allowing brokers to deduplicate retries. max_in_flight_requests_per_connection=1 ensures messages within a partition arrive in order even during retries. These three settings together provide exactly-once semantics at the producer level.

MSK Serverless

resource "aws_msk_serverless_cluster" "main" {
  cluster_name = "dev-kafka-serverless"

  vpc_config {
    subnet_ids         = var.private_subnet_ids
    security_group_ids = [aws_security_group.msk.id]
  }

  client_authentication {
    sasl {
      iam { enabled = true }
    }
  }

  tags = { Environment = "development" }
}

MSK Serverless is the right choice for development environments, staging clusters with variable load, and teams that want Kafka compatibility without managing cluster sizing. You pay per GB ingested and egressed with no broker nodes to provision. The tradeoff is that it does not support all Kafka configurations and has throughput limits that matter at scale. For production with predictable high throughput, provisioned MSK remains the better option.

Closing Thoughts

MSK gives you a production-ready Kafka cluster without the operational burden of managing brokers, ZooKeeper or KRaft, certificate authorities, and rolling upgrades. The IAM authentication model integrates cleanly with your existing AWS identity infrastructure. The provisioned throughput on EBS volumes handles high-throughput workloads without the performance unpredictability of burst storage.

Disable auto topic creation. Use acks=all with idempotent producers. Set min.insync.replicas=2 with replication factor 3. Monitor consumer lag as your primary operational health metric. These four decisions cover the reliability surface that matters most in production Kafka deployments.

Enjoy the cloud.

Osama


#AWS #MSK #ApacheKafka #EventStreaming #CloudArchitecture #Terraform #InfrastructureAsCode #AmazonWebServices #SolutionsArchitect #CloudComputing #DataEngineering #RealTimeData #StreamProcessing #TechBlog #CloudInfrastructure #Microservices #EventDriven #BackendEngineering #SRE #DevOps

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.