Veröffentlicht am

Kubernetes Adapter Pattern: Legacy-Systeme integrieren

Teilen:
Authors

Kubernetes Adapter Pattern: Legacy-Systeme ohne Code-Aenderungen integrieren

TL;DR

  • Das Adapter Pattern setzt einen Sidecar-Container ein, der Protokolle und Datenformate zwischen inkompatiblen Services uebersetzt
  • Typische Einsatzfelder: REST-zu-gRPC-Konvertierung, XML-zu-JSON-Transformation, SOAP-Anbindung an moderne APIs
  • Implementierung als Sidecar (pro Pod) oder als dedizierter Proxy-Service (zentral fuer mehrere Consumer)
  • Der Hauptcontainer bleibt komplett unveraendert -- kein Refactoring noetig
  • Latenz-Overhead liegt typischerweise unter 2ms pro Hop bei effizienter Implementierung in Go oder Rust

Worum geht es?

In fast jeder gewachsenen IT-Landschaft existieren Systeme, die unterschiedliche Protokolle oder Datenformate sprechen. Ein ERP-System liefert SOAP/XML, die neue Analyseplattform erwartet REST/JSON, und die Produktionsmaschinen senden Modbus-TCP. Diese Systeme komplett neu zu schreiben ist unrealistisch. Punkt-zu-Punkt-Integrationen skalieren nicht.

Das Adapter Pattern loest genau dieses Problem. Ein zwischengeschalteter Container uebernimmt die Uebersetzung, ohne dass der urspruengliche Service veraendert werden muss. In Kubernetes laesst sich das elegant als Sidecar-Container im selben Pod realisieren.

Adapter-Typen im Ueberblick

Adapter-TypAufgabeBeispiel
Protokoll-AdapterKommunikationsprotokoll konvertierenREST zu gRPC, MQTT zu Kafka, SOAP zu REST
Format-AdapterDatenformat transformierenXML zu JSON, CSV zu Protobuf, proprietaer zu OpenAPI
Auth-AdapterAuthentifizierung uebersetzenOAuth2 zu API-Key, mTLS zu Basic Auth
Filter-AdapterDaten selektieren und reduzierenNur relevante Felder weiterleiten, PII entfernen

Implementierung als Sidecar

Die gaengigste Variante: Der Adapter laeuft als zweiter Container im selben Pod. Der Hauptcontainer kommuniziert ueber localhost mit dem Adapter. Der Adapter uebersetzt und leitet an das Zielsystem weiter.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: registry.internal/order-service:2.4.1
        ports:
        - containerPort: 8080
        env:
        - name: ERP_ENDPOINT
          # Service spricht mit dem lokalen Adapter statt direkt mit dem ERP
          value: "http://localhost:9000/api/orders"
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi

      - name: soap-adapter
        image: registry.internal/soap-json-adapter:1.1.0
        ports:
        - containerPort: 9000
        env:
        - name: TARGET_WSDL
          value: "https://erp.internal.corp/OrderService?wsdl"
        - name: LISTEN_PORT
          value: "9000"
        - name: LOG_LEVEL
          value: "info"
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 250m
            memory: 256Mi
        livenessProbe:
          httpGet:
            path: /healthz
            port: 9000
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /readyz
            port: 9000
          initialDelaySeconds: 3
          periodSeconds: 5

Der order-service schickt normale REST-Requests an localhost:9000. Der soap-adapter uebersetzt diese in SOAP-Calls gegen das ERP-System. Fuer den order-service sieht es so aus, als spreche er mit einer REST-API.

Wichtig: Beide Container teilen sich das Netzwerk-Namespace im Pod. Deshalb funktioniert die Kommunikation ueber localhost ohne Service-Discovery.

Implementierung als dedizierter Proxy-Service

Wenn mehrere Services denselben Legacy-Endpunkt ansprechen, lohnt sich ein zentraler Adapter als eigenstaendiges Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: erp-adapter
  namespace: integration
spec:
  replicas: 3
  selector:
    matchLabels:
      app: erp-adapter
  template:
    metadata:
      labels:
        app: erp-adapter
    spec:
      containers:
      - name: erp-adapter
        image: registry.internal/erp-adapter:2.0.0
        ports:
        - containerPort: 8080
        env:
        - name: ERP_HOST
          value: "https://erp.internal.corp"
        - name: CACHE_TTL
          value: "60s"
        - name: MAX_CONCURRENT
          value: "50"
---
apiVersion: v1
kind: Service
metadata:
  name: erp-adapter
  namespace: integration
spec:
  selector:
    app: erp-adapter
  ports:
  - port: 80
    targetPort: 8080

Andere Services im Cluster sprechen dann http://erp-adapter.integration.svc.cluster.local an und erhalten eine saubere REST/JSON-Schnittstelle.

Sidecar vs. Proxy: Wann welchen Ansatz?

KriteriumSidecarDedizierter Proxy
Anzahl Consumer1 ServiceMehrere Services
LatenzMinimal (localhost)Etwas hoeher (Netzwerk-Hop)
Ressourcen-OverheadPro Pod dupliziertEinmalig zentral
Fehler-IsolationGut, auf Pod beschraenktSingle Point of Failure moeglich
SkalierungSkaliert mit dem ServiceUnabhaengig skalierbar
KonfigurationPro Deployment individuellZentral verwaltbar

Fuer die meisten Faelle empfehle ich den Sidecar-Ansatz, wenn nur ein Consumer existiert, und den Proxy-Ansatz, wenn drei oder mehr Services denselben Adapter benoetigen.

Einen minimalen Adapter in Go schreiben

Ein einfacher HTTP-zu-gRPC-Adapter laesst sich in Go mit wenigen hundert Zeilen bauen. Hier die Grundstruktur:

# Projektstruktur initialisieren
mkdir -p erp-adapter/cmd erp-adapter/internal/converter
cd erp-adapter

# Go Modul initialisieren
go mod init github.com/your-org/erp-adapter

# Abhaengigkeiten
go get google.golang.org/grpc
go get github.com/gorilla/mux

# Build fuer Container
CGO_ENABLED=0 GOOS=linux go build -o /bin/adapter ./cmd/main.go

# Multi-Stage Dockerfile
cat <<'DOCKERFILE'
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /bin/adapter ./cmd/main.go

FROM gcr.io/distroless/static:nonroot
COPY --from=builder /bin/adapter /adapter
ENTRYPOINT ["/adapter"]
DOCKERFILE

Die Verwendung von Distroless-Images ist hier bewusst gewaehlt. Weniger Angriffsoberflaeche, kleines Image, kein Shell-Zugriff im Container. Genau das, was man fuer einen Infrastruktur-Adapter will.

Monitoring und Observability

Ein Adapter ohne Monitoring ist ein blindes Stueck Infrastruktur. Drei Metriken sollte jeder Adapter mindestens exponieren:

  1. Request-Latenz (Histogram): Wie lange dauert die Uebersetzung plus der Aufruf zum Zielsystem?
  2. Fehlerrate (Counter): Wie viele Requests scheitern bei der Konvertierung oder am Zielsystem?
  3. Durchsatz (Counter): Wie viele Requests verarbeitet der Adapter pro Sekunde?

Prometheus-Metriken lassen sich in Go mit dem prometheus/client_golang Package in wenigen Zeilen einbauen. Ein ServiceMonitor fuer Prometheus Operator sieht so aus:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: erp-adapter
  namespace: integration
spec:
  selector:
    matchLabels:
      app: erp-adapter
  endpoints:
  - port: metrics
    interval: 15s
    path: /metrics

Wer den Monitoring-Stack noch nicht aufgesetzt hat, findet eine Anleitung in unserem Beitrag zu Kubernetes Monitoring mit Open-Source-Tools.

Fehlerbehandlung und Resilienz

Adapter muessen robust mit Ausfaellen des Zielsystems umgehen. Folgende Patterns sind Pflicht:

Circuit Breaker: Wenn das Zielsystem nicht antwortet, schaltet der Adapter nach einer konfigurierbaren Anzahl von Fehlern in den Open-State und gibt sofort einen Fehler zurueck, statt weiter Last auf das kaputte System zu schicken.

Retry mit Exponential Backoff: Transiente Fehler (Timeouts, 503er) werden automatisch wiederholt, mit steigendem Abstand zwischen den Versuchen. Maximal 3 Retries, danach Fehler an den Client zurueck.

Timeout: Jeder Request an das Zielsystem bekommt ein striktes Timeout. 5 Sekunden ist ein guter Startwert. Besser ein schneller Fehler als ein haengender Request.

Health Checks: Liveness- und Readiness-Probes sind nicht optional. Kubernetes muss wissen, ob der Adapter noch funktioniert. Die Readiness-Probe sollte die Erreichbarkeit des Zielsystems mit einbeziehen.

Typische Stolperfallen

Shared Lifecycle: Sidecar und Hauptcontainer starten und stoppen nicht immer synchron. Seit Kubernetes 1.28 gibt es native Sidecar-Container (initContainers mit restartPolicy: Always), die vor dem Hauptcontainer starten und nach ihm stoppen. Das loest viele Race Conditions.

Resource Limits vergessen: Der Adapter braucht eigene Resource Requests und Limits. Ohne diese wird er bei Ressourcenknappheit als erster vom Scheduler gekillt.

Logging-Konsistenz: Adapter-Logs sollten eine Correlation-ID aus dem eingehenden Request uebernehmen, damit man im Fehlerfall die gesamte Kette nachverfolgen kann.

Wann das Adapter Pattern nicht passt

Das Pattern ist kein Allheilmittel. In folgenden Situationen gibt es bessere Alternativen:

  • Komplexe Geschaeftslogik noetig: Wenn zwischen den Systemen nicht nur uebersetzt, sondern auch transformiert, aggregiert und orchestriert werden muss, ist ein vollstaendiger Integration-Service oder ein Event-driven Ansatz mit Kafka besser geeignet.
  • Hoher Durchsatz bei minimaler Latenz: Bei Sub-Millisekunden-Anforderungen ist jeder zusaetzliche Hop zu viel. Hier lieber die Services direkt kompatibel machen.
  • Einmalige Migration: Wenn das Legacy-System in 6 Monaten abgeloest wird, lohnt der Aufwand fuer einen produktionsreifen Adapter nicht. Ein einfaches Skript fuer die Uebergangszeit reicht.

Fuer eine breitere Uebersicht zu Kubernetes Design Patterns empfehle ich den Beitrag zu Kubernetes Production Cluster Setup.

Zusammenfassung

Das Adapter Pattern ist eines der nuetzlichsten Patterns fuer Kubernetes-Umgebungen mit gewachsener Infrastruktur. Es entkoppelt Legacy-Systeme von modernen Services, ohne dass bestehender Code angefasst werden muss. Die Implementierung als Sidecar oder zentraler Proxy ist mit Kubernetes-Bordmitteln moeglich und laesst sich sauber in bestehende CI/CD-Pipelines einbinden.

Wer seine Kubernetes-Deployments absichern moechte, findet im Beitrag zu Kubernetes Security Hardening weiterfuehrende Informationen. Fuer die Automatisierung von Security Scans in der Pipeline lohnt sich ein Blick auf Security Scanning in Kubernetes.

Wenn Sie Unterstuetzung bei der Integration von Legacy-Systemen in Ihre Kubernetes-Umgebung brauchen, sprechen Sie uns an 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