← Back to list

Advanced Event-Driven Microservices on Amazon EKS with Knative and Kafka

Introduction

Tolgahan Demirbaş · 2025-07-31 15:02 · 5 claps · 5.9 min read
#aws-eks #amazon-eks #kafka #apache-kafka #knative
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud

Advanced Event-Driven Microservices on Amazon EKS with Knative and Kafka

Introduction

Event-driven architectures have become the backbone of modern distributed systems, but most tutorials stop at the “hello world” level. This post dives deep into production patterns, advanced configurations, and real-world challenges you’ll face when running event-driven microservices at scale.

We’ll cover:

  • Advanced Knative Eventing patterns (Brokers, Triggers, Channels)
  • Kafka optimization for high-throughput scenarios
  • Error handling and resilience patterns
  • Security hardening and multi-tenancy
  • Performance tuning and resource optimization
  • Production deployment strategies

Advanced Architecture Overview

Prerequisites

  • Amazon EKS Cluster (v1.28+) with Fargate profiles
  • Amazon MSK with SASL/SCRAM authentication
  • AWS Load Balancer Controller
  • External Secrets Operator
  • Cert-Manager for TLS
  • Container registry (ECR)

Step 1: Knative Installation

Instead of basic Helm installation, let’s use a production-ready configuration:

# knative-serving-values.yaml
config:
  deployment:
    progressDeadlineSeconds: 600
    replicas: 3
  autoscaler:
    enable-scale-to-zero: "false"
    scale-to-zero-grace-period: "30s"
    stable-window: "60s"
    panic-window: "6s"
    max-scale-up-rate: "10"
    max-scale-down-rate: "2"
  network:
    ingress-class: "kourier"
    domain-template: "{{.Name}}.{{.Namespace}}.example.com"
# Install with custom configuration
helm install knative-serving knative/knative-serving \
  --namespace knative-serving \
  --create-namespace \
  --values knative-serving-values.yaml

# Install Kourier as networking layer
kubectl apply -f https://github.com/knative/net-kourier/releases/download/knative-v1.12.0/kourier.yaml

Step 2: Production Kafka Integration

Enhanced KafkaSource with Error Handling

apiVersion: sources.knative.dev/v1beta1
kind: KafkaSource
metadata:
  name: orders-source
  namespace: ecommerce
spec:
  bootstrapServers:
    - "b-1.orders-msk.kafka.us-east-1.amazonaws.com:9098"
  topics:
    - orders
    - order-updates
  consumerGroup: order-processing-group
  net:
    sasl:
      enable: true
      type:
        secretKeyRef:
          name: kafka-secret
          key: sasl-type
      user:
        secretKeyRef:
          name: kafka-secret
          key: username
      password:
        secretKeyRef:
          name: kafka-secret
          key: password
    tls:
      enable: true
  sink:
    ref:
      apiVersion: eventing.knative.dev/v1
      kind: Broker
      name: order-broker
  delivery:
    retry: 3
    backoffPolicy: exponential
    backoffDelay: PT1S
    deadLetterSink:
      ref:
        apiVersion: serving.knative.dev/v1
        kind: Service
        name: dlq-handler

Broker Configuration

apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
  name: order-broker
  namespace: ecommerce
  annotations:
    eventing.knative.dev/broker.class: MTChannelBasedBroker
spec:
  config:
    apiVersion: v1
    kind: ConfigMap
    name: broker-config
  delivery:
    retry: 5
    backoffPolicy: exponential
    backoffDelay: PT2S
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: broker-config
  namespace: ecommerce
data:
  channelTemplateSpec: |
    apiVersion: messaging.knative.dev/v1
    kind: KafkaChannel
    spec:
      numPartitions: 10
      replicationFactor: 3
      retentionDuration: PT168H  # 7 days

Step 3: Service Patterns

High-Performance Order Processor

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: order-processor
  namespace: ecommerce
  annotations:
    autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
    autoscaling.knative.dev/metric: "concurrency"
    autoscaling.knative.dev/target: "100"
    autoscaling.knative.dev/min-scale: "3"
    autoscaling.knative.dev/max-scale: "50"
    autoscaling.knative.dev/scale-down-delay: "30s"
    autoscaling.knative.dev/window: "60s"
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/target-utilization-percentage: "70"
    spec:
      containerConcurrency: 1000
      timeoutSeconds: 300
      containers:
        - name: processor
          image: your-registry/order-processor:v2.1.0
          ports:
            - containerPort: 8080
              protocol: TCP
          env:
            - name: KAFKA_BROKERS
              valueFrom:
                secretKeyRef:
                  name: kafka-config
                  key: brokers
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-config
                  key: url
            - name: MAX_CONCURRENT_ORDERS
              value: "50"
            - name: PROCESSING_TIMEOUT
              value: "30s"
          resources:
            requests:
              memory: "256Mi"
              cpu: "200m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10

Intelligent Trigger Configuration

apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: payment-processing
  namespace: ecommerce
spec:
  broker: order-broker
  filter:
    attributes:
      type: com.ecommerce.order.created
      source: order-service
      status: pending-payment
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: payment-processor
  delivery:
    retry: 3
    backoffPolicy: exponential
    backoffDelay: PT1S
    deadLetterSink:
      ref:
        apiVersion: serving.knative.dev/v1
        kind: Service
        name: payment-dlq-handler
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: inventory-check
  namespace: ecommerce
spec:
  broker: order-broker
  filter:
    attributes:
      type: com.ecommerce.order.created
      source: order-service
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: inventory-service
    uri: /api/v1/check-inventory

Step 4: Producer Patterns

Circuit Breaker Pattern Implementation

import asyncio
import json
from kafka import KafkaProducer
from kafka.errors import KafkaError
import time
from enum import Enum
from dataclasses import dataclass
from typing import Optional
import logging

class CircuitState(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"

@dataclass
class CircuitBreaker:
    failure_threshold: int = 5
    recovery_timeout: int = 60
    expected_exception: tuple = (KafkaError,)

    def __post_init__(self):
        self.failure_count = 0
        self.last_failure_time = None
        self.state = CircuitState.CLOSED

class EventProducer:
    def __init__(self, bootstrap_servers: str, security_config: dict):
        self.producer = KafkaProducer(
            bootstrap_servers=bootstrap_servers,
            value_serializer=lambda v: json.dumps(v).encode('utf-8'),
            key_serializer=lambda k: k.encode('utf-8') if k else None,
            acks='all',  # Wait for all replicas
            retries=3,
            batch_size=16384,
            linger_ms=10,
            buffer_memory=33554432,
            compression_type='snappy',
            max_in_flight_requests_per_connection=1,  # Ensure ordering
            enable_idempotence=True,
            **security_config
        )
        self.circuit_breaker = CircuitBreaker()
        self.logger = logging.getLogger(__name__)

    async def send_event(self, topic: str, event: dict, key: Optional[str] = None):
        if not self._can_execute():
            raise Exception("Circuit breaker is OPEN")

        try:
            # Add CloudEvents headers
            cloud_event = {
                "specversion": "1.0",
                "type": f"com.ecommerce.{event.get('event_type', 'unknown')}",
                "source": "order-service",
                "id": event.get('id'),
                "time": event.get('timestamp'),
                "datacontenttype": "application/json",
                "data": event
            }

            future = self.producer.send(topic, value=cloud_event, key=key)
            record_metadata = future.get(timeout=10)

            self._on_success()
            self.logger.info(f"Event sent to {record_metadata.topic}:{record_metadata.partition}:{record_metadata.offset}")

        except Exception as e:
            self._on_failure()
            self.logger.error(f"Failed to send event: {e}")
            raise

    def _can_execute(self) -> bool:
        if self.circuit_breaker.state == CircuitState.CLOSED:
            return True
        elif self.circuit_breaker.state == CircuitState.OPEN:
            if time.time() - self.circuit_breaker.last_failure_time > self.circuit_breaker.recovery_timeout:
                self.circuit_breaker.state = CircuitState.HALF_OPEN
                return True
            return False
        else:  # HALF_OPEN
            return True

    def _on_success(self):
        self.circuit_breaker.failure_count = 0
        self.circuit_breaker.state = CircuitState.CLOSED

    def _on_failure(self):
        self.circuit_breaker.failure_count += 1
        self.circuit_breaker.last_failure_time = time.time()

        if self.circuit_breaker.failure_count >= self.circuit_breaker.failure_threshold:
            self.circuit_breaker.state = CircuitState.OPEN

Step 5: Security Hardening

RBAC Configuration

apiVersion: v1
kind: ServiceAccount
metadata:
  name: knative-kafka-source
  namespace: ecommerce
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: knative-kafka-source
  namespace: ecommerce
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list"]
- apiGroups: ["sources.knative.dev"]
  resources: ["kafkasources"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: knative-kafka-source
  namespace: ecommerce
subjects:
- kind: ServiceAccount
  name: knative-kafka-source
  namespace: ecommerce
roleRef:
  kind: Role
  name: knative-kafka-source
  apiGroup: rbac.authorization.k8s.io

Network Policies

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: knative-services-policy
  namespace: ecommerce
spec:
  podSelector:
    matchLabels:
      serving.knative.dev/service: order-processor
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: knative-serving
    - namespaceSelector:
        matchLabels:
          name: knative-eventing
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to: []
    ports:
    - protocol: TCP
      port: 443  # HTTPS
    - protocol: TCP
      port: 9098 # Kafka SASL_SSL

Step 6: Performance Optimization

Resource Limits and Requests Tuning

apiVersion: v1
kind: ConfigMap
metadata:
  name: config-deployment
  namespace: knative-serving
data:
  # Global defaults for all services
  requests-cpu: "100m"
  requests-memory: "128Mi"
  limits-cpu: "1000m"
  limits-memory: "1Gi"
  # Progress deadline for deployments
  progress-deadline: "600s"
  # Resource quota enforcement
  queue-sidecar-cpu-request: "25m"
  queue-sidecar-memory-request: "50Mi"

JVM Tuning for Java Services

FROM openjdk:17-jre-slim

# JVM optimizations for containerized environments
ENV JAVA_OPTS="-XX:+UseContainerSupport \
    -XX:InitialRAMPercentage=25.0 \
    -XX:MaxRAMPercentage=75.0 \
    -XX:+UseG1GC \
    -XX:+UseStringDeduplication \
    -XX:+OptimizeStringConcat \
    -Djava.security.egd=file:/dev/./urandom"

COPY target/order-processor.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

Step 7: Advanced Error Handling

Dead Letter Queue Handler

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: dlq-handler
  namespace: ecommerce
spec:
  template:
    spec:
      containers:
        - name: dlq-processor
          image: your-registry/dlq-handler:latest
          env:
            - name: RETRY_ATTEMPTS
              value: "3"
            - name: BACKOFF_MULTIPLIER
              value: "2"
            - name: MAX_BACKOFF_SECONDS
              value: "300"
          ports:
            - containerPort: 8080

Retry Strategy Implementation

package main

import (
    "context"
    "fmt"
    "math"
    "time"
)

type RetryConfig struct {
    MaxAttempts     int
    BaseDelay       time.Duration
    MaxDelay        time.Duration
    BackoffExponent float64
}

func ExponentialBackoffRetry(ctx context.Context, config RetryConfig, operation func() error) error {
    var lastErr error

    for attempt := 0; attempt < config.MaxAttempts; attempt++ {
        if attempt > 0 {
            delay := time.Duration(float64(config.BaseDelay) * math.Pow(config.BackoffExponent, float64(attempt-1)))
            if delay > config.MaxDelay {
                delay = config.MaxDelay
            }

            select {
            case <-ctx.Done():
                return ctx.Err()
            case <-time.After(delay):
            }
        }

        if err := operation(); err != nil {
            lastErr = err
            fmt.Printf("Attempt %d failed: %v\n", attempt+1, err)
            continue
        }

        return nil
    }

    return fmt.Errorf("operation failed after %d attempts, last error: %w", config.MaxAttempts, lastErr)
}

Conclusion

This advanced setup provides a production-ready foundation for event-driven microservices that can handle:

  • High throughput: Optimized Kafka configurations and efficient resource usage
  • Fault tolerance: Circuit breakers, retries, and dead letter queues
  • Security: RBAC, network policies, and encrypted communication
  • Scalability: Advanced autoscaling and resource optimization
  • Maintainability: Clear separation of concerns and standardized patterns

The architecture supports complex event flows while maintaining loose coupling and high availability. Each component is independently scalable and can handle failures gracefully.

Key takeaways for production deployments:

  • Always use structured logging and correlation IDs
  • Implement proper backpressure mechanisms
  • Monitor queue depths and processing latencies
  • Use feature flags for gradual rollouts
  • Implement comprehensive health checks
  • Plan for capacity and cost optimization

This setup forms the backbone for sophisticated event-driven systems that can grow with your business needs while maintaining operational excellence.


메타데이터
post_id
7d4bcf6dfeff
slug
advanced-event-driven-microservices-on-amazon-eks-with-knative-and-kafka-7d4bcf6dfeff
url
https://medium.com/@tolghn/advanced-event-driven-microservices-on-amazon-eks-with-knative-and-kafka-7d4bcf6dfeff
canonical_url
https://medium.com/@tolghn/advanced-event-driven-microservices-on-amazon-eks-with-knative-and-kafka-7d4bcf6dfeff
author_url
https://medium.com/@tolghn
status
ok
fetched_at
2026-06-25 07:00:49