Veröffentlicht am

Ambassador Pattern in Kubernetes: Envoy als Sidecar-Proxy

Teilen:
Authors

Kubernetes Ambassador Pattern: Sidecar-Proxy fuer sichere Service-Kommunikation

TL;DR

  • Das Ambassador Pattern platziert einen Proxy-Container (typischerweise Envoy) als Sidecar im selben Pod wie der Anwendungscontainer.
  • Der Sidecar uebernimmt TLS-Termination, Retries, Circuit Breaking und Routing -- der Anwendungscode bleibt davon frei.
  • Der Unterschied zum Service Mesh: Das Ambassador Pattern wird manuell pro Service konfiguriert, ein Service Mesh injiziert Sidecars automatisch clustersweit.
  • Der Ressourcen-Overhead liegt bei ca. 50-100 MB RAM und 0.1 CPU pro Sidecar -- bei 10 Pods sind das 1 GB RAM extra.
  • Sinnvoll fuer Teams, die gezielte Proxy-Funktionalitaet fuer einzelne Services brauchen, ohne ein volles Service Mesh einzufuehren.

Was das Ambassador Pattern ist (und was nicht)

Das Ambassador Pattern ist eines der drei klassischen Sidecar-Muster in Kubernetes (neben Adapter und Ambassador). Die Idee: Ein zweiter Container im Pod fungiert als Proxy fuer alle Netzwerkinteraktionen des Hauptcontainers.

Der Hauptcontainer spricht nur mit localhost. Der Ambassador-Sidecar uebernimmt alles, was mit dem Netzwerk zu tun hat: TLS handshake, Connection Pooling, Retries bei Fehlern, Load Balancing zu Upstream-Services.

Was es nicht ist: Ein Ersatz fuer einen Ingress Controller oder ein API Gateway. Der Ambassador sitzt innerhalb des Pods und steuert die Kommunikation eines einzelnen Services. Ein Ingress Controller sitzt am Clusterrand und routet externen Traffic.

Ambassador Pattern vs. Service Mesh vs. API Gateway

KriteriumAmbassador PatternService Mesh (Istio/Linkerd)API Gateway (Kong/APISIX)
ScopeEinzelner PodGesamter ClusterCluster-Eingang
Sidecar-InjectionManuell (im Deployment-YAML)Automatisch (Namespace-Label)Kein Sidecar
KonfigurationPer ConfigMap/Volume pro PodZentral via CRDsZentral via CRDs
TLS/mTLSJa, manuell konfiguriertJa, automatischTLS-Termination am Edge
Retry/Circuit BreakingJaJaBegrenzt
ObservabilityManuell integrierenBuilt-inBuilt-in
OverheadGering (nur wo noetig)Hoch (jeder Pod bekommt Sidecar)Gering (zentraler Proxy)
Wann sinnvoll1-5 Services mit Proxy-BedarfAb 10+ ServicesExterne API-Absicherung

Architektur: So sieht das im Cluster aus

Ein typischer Aufbau:

                   Ingress Controller
                         |
                    Kubernetes Service
                         |
              +----------+-----------+
              |        Pod           |
              |                      |
              |  +----------------+  |
              |  | Envoy Sidecar  |  |
              |  | (Port 8000)    |  |
              |  +-------+--------+  |
              |          |           |
              |  +-------v--------+  |
              |  | App Container  |  |
              |  | (Port 8080)    |  |
              |  +----------------+  |
              +----------------------+

Der Ingress Controller leitet Traffic an den Kubernetes Service. Der Service verweist auf Port 8000 des Pods -- das ist der Envoy-Sidecar. Envoy leitet nach Pruefung und ggf. Transformation an localhost:8080 weiter, wo der eigentliche Anwendungscontainer laeuft.

Praktische Implementierung mit Envoy

Hier ein vollstaendiges Beispiel: Ein Deployment mit einem Python-API-Container und einem Envoy-Sidecar, der TLS-Termination und Basic Routing uebernimmt.

Deployment mit Sidecar

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  labels:
    app: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        # Anwendungscontainer -- spricht nur mit localhost
        - name: order-api
          image: registry.example.com/order-service:1.4.2
          ports:
            - containerPort: 8080
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: order-db-credentials
                  key: url
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi

        # Ambassador-Sidecar: Envoy Proxy
        - name: envoy-ambassador
          image: envoyproxy/envoy:v1.31-latest
          args: ["-c", "/etc/envoy/envoy.yaml", "--log-level", "info"]
          ports:
            - containerPort: 8000
              name: proxy
            - containerPort: 9901
              name: admin
          volumeMounts:
            - name: envoy-config
              mountPath: /etc/envoy
              readOnly: true
            - name: tls-certs
              mountPath: /etc/tls
              readOnly: true
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi

      volumes:
        - name: envoy-config
          configMap:
            name: order-service-envoy-config
        - name: tls-certs
          secret:
            secretName: order-service-tls

Envoy-Konfiguration als ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: order-service-envoy-config
data:
  envoy.yaml: |
    static_resources:
      listeners:
        - address:
            socket_address:
              address: 0.0.0.0
              port_value: 8000
          filter_chains:
            - transport_socket:
                name: envoy.transport_sockets.tls
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
                  common_tls_context:
                    tls_certificates:
                      - certificate_chain:
                          filename: /etc/tls/tls.crt
                        private_key:
                          filename: /etc/tls/tls.key
              filters:
                - name: envoy.filters.network.http_connection_manager
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                    stat_prefix: ingress_http
                    access_log:
                      - name: envoy.access_loggers.stdout
                        typed_config:
                          "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
                    route_config:
                      name: local_route
                      virtual_hosts:
                        - name: local_service
                          domains: ["*"]
                          routes:
                            - match:
                                prefix: "/"
                              route:
                                cluster: local_app
                                timeout: 30s
                          retry_policy:
                            retry_on: "5xx,connect-failure,refused-stream"
                            num_retries: 3
                            per_try_timeout: 10s
                    http_filters:
                      - name: envoy.filters.http.router
                        typed_config:
                          "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

      clusters:
        - name: local_app
          connect_timeout: 0.5s
          type: STATIC
          lb_policy: ROUND_ROBIN
          load_assignment:
            cluster_name: local_app
            endpoints:
              - lb_endpoints:
                  - endpoint:
                      address:
                        socket_address:
                          address: 127.0.0.1
                          port_value: 8080
          circuit_breakers:
            thresholds:
              - max_connections: 100
                max_pending_requests: 50
                max_requests: 200
                max_retries: 3

    admin:
      address:
        socket_address:
          address: 127.0.0.1
          port_value: 9901

Service-Definition

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    # Traffic geht an den Envoy-Sidecar, nicht direkt an die App
    - name: https
      port: 443
      targetPort: 8000

Der wichtige Punkt: Der Kubernetes Service zeigt auf Port 8000 (Envoy), nicht auf 8080 (App). Alle Aufrufer im Cluster sprechen mit Envoy, der dann TLS terminiert, Retries durchfuehrt und an die lokale App weiterleitet.

Was der Sidecar uebernehmen kann

Durch die Envoy-Konfiguration lassen sich verschiedene Funktionen aktivieren, ohne den Anwendungscode zu aendern:

TLS-Termination -- wie im Beispiel oben gezeigt. Der Anwendungscontainer empfaengt unverschluesselten Traffic auf localhost. Envoy kuemmert sich um Zertifikate und TLS Handshake.

Automatic Retries -- bei 5xx-Fehlern oder Connection Failures versucht Envoy es automatisch erneut (konfigurierbar). Das ist besonders nuetzlich bei transienten Fehlern in Upstream-Services.

Circuit Breaking -- wenn ein Upstream-Service nicht antwortet, oeffnet Envoy den Circuit Breaker nach einer konfigurierbaren Anzahl Fehler. Neue Requests werden sofort mit einem Fehler beantwortet, statt den Thread zu blockieren.

Rate Limiting -- Envoy unterstuetzt lokales Rate Limiting ueber den envoy.filters.http.local_ratelimit-Filter. Nuetzlich, um einzelne Services vor Ueberlastung zu schuetzen.

Observability -- Envoy generiert automatisch Metriken (Prometheus-kompatibel), Zugriffslogs und unterstuetzt Distributed Tracing (Jaeger, Zipkin). Ohne eine Zeile Code in der Anwendung.

Wann das Pattern nicht sinnvoll ist

Nicht jeder Service braucht einen Sidecar. Ueberlegt euch, ob das Ambassador Pattern wirklich den Aufwand rechtfertigt:

  • Wenige Services ohne komplexe Netzwerkanforderungen -- wenn ein einfacher ClusterIP Service reicht, ist ein Sidecar Overkill.
  • Bereits ein Service Mesh im Einsatz -- Istio oder Linkerd injizieren bereits Envoy/Linkerd-Proxy als Sidecar. Ein zusaetzlicher Ambassador-Sidecar waere doppelt.
  • Batch Jobs oder CronJobs -- kurzlebige Pods profitieren kaum von Retry-Logik und Circuit Breaking.
  • Ressourcen sind knapp -- jeder Sidecar braucht CPU und RAM. Bei Hunderten von Pods laeppern sich 50-100 MB pro Sidecar.

Fuer die Entscheidung Service Mesh vs. manuelles Sidecar hilft der Vergleich unter Istio vs. Linkerd.

Deployment-Checkliste

Bevor ihr das Ambassador Pattern in Produktion bringt:

SchrittDetails
Resource Limits fuer Sidecar definierenCPU und Memory Limits setzen, sonst frisst Envoy unter Last beliebig Ressourcen
Liveness/Readiness ProbesEnvoy Admin-API (/ready auf Port 9901) als Readiness-Probe nutzen
Log-Rotation konfigurierenAccess Logs koennen gross werden -- stdout + Log-Aggregation (Loki, Fluentd)
TLS-Zertifikate rotierencert-manager oder aehnliches Tool nutzen, nicht manuell
Metriken scrapenEnvoy /stats/prometheus Endpoint in Prometheus ServiceMonitor einbinden
Sidecar-Version pinnenKein latest-Tag verwenden, Envoy-Version explizit festlegen
Graceful ShutdownpreStop-Hook mit Sleep, damit Envoy laufende Connections sauber beenden kann

Fuer Capacity Planning mit Sidecars empfiehlt sich der Beitrag zu Kubernetes Capacity Planning.

Weiter vertiefen

Wenn ihr Hilfe bei der Entscheidung braucht, ob ein Ambassador-Sidecar, ein Service Mesh oder ein API Gateway fuer euren Fall passt, meldet euch unter /kontakt.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen