Veröffentlicht am

Kubernetes Circuit Breaker mit Istio: Resilienz-Patterns

Teilen:
Authors

Kubernetes Circuit Breaker: Resilienz-Patterns fuer Microservices

TL;DR

  • Circuit Breaker verhindern kaskadierende Ausfaelle, indem sie Anfragen an ungesunde Services fruehzeitig abbrechen -- statt den gesamten Cluster in Mitleidenschaft zu ziehen.
  • Istio/Envoy implementiert Circuit Breaking ueber DestinationRules mit Outlier Detection und Connection Pool Limits.
  • Retry-Policies gehoeren auf die Infrastrukturebene (Service Mesh), nicht in den Anwendungscode -- dadurch wird die Konfiguration zentral steuerbar.
  • Das Bulkhead-Pattern isoliert Failure Domains, sodass ein ueberlasteter Service nicht alle anderen blockiert.
  • Die Kombination aus Circuit Breaker, Timeout, Retry und Bulkhead bildet eine vollstaendige Resilienz-Strategie.

Warum Microservices ohne Circuit Breaker gefaehrlich leben

In einer Microservice-Architektur haengt die Verfuegbarkeit des Gesamtsystems von jedem einzelnen Service ab. Wenn Service B langsam antwortet, stauen sich Requests in Service A. Service A verbraucht alle Threads und kann auch Anfragen an Service C nicht mehr bearbeiten. Innerhalb von Minuten ist der gesamte Cluster betroffen.

Dieses Muster heisst kaskadierender Ausfall und ist die haeufigste Ursache fuer grossflaechige Ausfaelle in verteilten Systemen.

Ohne Circuit Breaker:
  Client */} Service A */} Service B (langsam/tot)
                |
                +*/} Thread-Pool voll
                |
                +*/} Service A wird unresponsive
                |
                +*/} Client-Timeout */} Gesamtausfall

Mit Circuit Breaker:
  Client */} Service A */} Circuit Breaker */} OPEN
                |
                +*/} Sofortige Fehlerantwort (503)
                |
                +*/} Service A bleibt funktionsfaehig
                |
                +*/} Client erhaelt schnelle Antwort

Die vier Resilienz-Patterns

1. Circuit Breaker

Der Circuit Breaker hat drei Zustaende:

ZustandVerhaltenUebergang
ClosedAlle Anfragen werden durchgelassenOeffnet bei Fehlerquote ueber Schwellwert
OpenAlle Anfragen werden sofort abgelehntWechselt nach Timeout zu Half-Open
Half-OpenBegrenzte Anzahl Probe-RequestsSchliesst bei Erfolg, oeffnet bei Fehler

2. Timeout

Jeder ausgehende Request braucht ein explizites Timeout. Ohne Timeout wartet ein Service potenziell unendlich auf eine Antwort.

3. Retry

Fehlgeschlagene Requests werden automatisch wiederholt -- aber nur bei transienten Fehlern (5xx, Connection Reset), nie bei Client-Fehlern (4xx).

4. Bulkhead

Ressourcen werden pro Abhaengigkeit isoliert. Ein separater Thread-Pool oder Connection-Pool pro Service verhindert, dass eine langsame Abhaengigkeit alle Ressourcen aufbraucht.

Circuit Breaker mit Istio/Envoy

Istio implementiert Circuit Breaking ueber zwei Mechanismen in der DestinationRule:

  1. Connection Pool Settings: Begrenzen die maximale Anzahl gleichzeitiger Verbindungen
  2. Outlier Detection: Erkennt ungesunde Endpoints und entfernt sie temporaer

Grundlegende DestinationRule

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: order-service-resilience
  namespace: production
spec:
  host: order-service.production.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
        connectTimeout: 5s
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 50
        http2MaxRequests: 200
        maxRequestsPerConnection: 10
        maxRetries: 3
    outlierDetection:
      consecutive5xxErrors: 3
      interval: 10s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
      minHealthPercent: 30

connectionPool erklaert:

  • maxConnections: 100 -- Maximal 100 gleichzeitige TCP-Verbindungen zu diesem Service
  • http1MaxPendingRequests: 50 -- Maximal 50 Requests in der Warteschlange
  • http2MaxRequests: 200 -- Maximal 200 gleichzeitige HTTP/2-Requests
  • maxRequestsPerConnection: 10 -- Nach 10 Requests wird die Verbindung geschlossen und neu aufgebaut

outlierDetection erklaert:

  • consecutive5xxErrors: 3 -- Nach 3 aufeinanderfolgenden 5xx-Fehlern wird der Endpoint entfernt
  • interval: 10s -- Pruefintervall
  • baseEjectionTime: 30s -- Minimale Dauer der Entfernung (wird bei wiederholtem Ejection multipliziert)
  • maxEjectionPercent: 50 -- Maximal 50% der Endpoints duerfen gleichzeitig entfernt werden
  • minHealthPercent: 30 -- Outlier Detection deaktiviert sich, wenn weniger als 30% der Endpoints gesund sind

Fortgeschrittene Konfiguration pro Subset

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: payment-service-resilience
  namespace: production
spec:
  host: payment-service.production.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 50
      http:
        http1MaxPendingRequests: 25
        maxRetries: 2
    outlierDetection:
      consecutive5xxErrors: 2
      interval: 5s
      baseEjectionTime: 60s
      maxEjectionPercent: 30
  subsets:
    - name: v1
      labels:
        version: v1
      trafficPolicy:
        connectionPool:
          http:
            http1MaxPendingRequests: 10
    - name: v2
      labels:
        version: v2
      trafficPolicy:
        connectionPool:
          http:
            http1MaxPendingRequests: 50

Das erlaubt unterschiedliche Circuit Breaker-Einstellungen fuer verschiedene Versionen eines Service -- nuetzlich bei Canary Deployments. Details zu Deployment-Strategien finden Sie unter Kubernetes Deployment Strategien.

Retry und Timeout mit Istio VirtualService

Retries und Timeouts werden in Istio ueber VirtualServices konfiguriert:

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: order-service-routing
  namespace: production
spec:
  hosts:
    - order-service
  http:
    - route:
        - destination:
            host: order-service
            port:
              number: 8080
      timeout: 8s
      retries:
        attempts: 3
        perTryTimeout: 3s
        retryOn: "5xx,reset,connect-failure,retriable-4xx"
        retryRemoteLocalities: true

Wichtig: Das Gesamt-Timeout muss groesser sein als attempts * perTryTimeout. In diesem Beispiel: 3 Versuche * 3 Sekunden = 9 Sekunden -- das Gesamt-Timeout von 8 Sekunden greift vorher. Passen Sie die Werte entsprechend an:

Empfehlung:
  timeout >= attempts * perTryTimeout + Puffer
  Beispiel: timeout: 12s, attempts: 3, perTryTimeout: 3s

Retry-Bedingungen im Detail

BedingungBedeutung
5xxRetry bei allen 5xx-Antworten
resetRetry bei Connection Reset
connect-failureRetry wenn Verbindungsaufbau fehlschlaegt
retriable-4xxRetry bei 409 Conflict
gateway-errorRetry bei 502, 503, 504
refused-streamRetry bei HTTP/2 REFUSED_STREAM

Bulkhead-Pattern mit Envoy

Das Bulkhead-Pattern in Envoy wird ueber separate Connection Pools pro Upstream-Cluster realisiert. Jeder Service in der DestinationRule hat seinen eigenen Pool:

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: api-gateway-bulkhead
  namespace: production
spec:
  host: user-service.production.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 50
      http:
        http1MaxPendingRequests: 25
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: payment-bulkhead
  namespace: production
spec:
  host: payment-service.production.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 30
      http:
        http1MaxPendingRequests: 10

Wenn der payment-service seinen Connection Pool ausschoepft, ist der user-service davon nicht betroffen. Requests an den user-service funktionieren weiterhin normal.

Anwendungsseitige Circuit Breaker

Nicht jedes Team hat ein Service Mesh im Einsatz. Circuit Breaker lassen sich auch auf Anwendungsebene implementieren. Hier ein Beispiel-Pattern:

Go: sony/gobreaker

settings := gobreaker.Settings{
    Name:        "payment-service",
    MaxRequests: 3,
    Interval:    10 * time.Second,
    Timeout:     30 * time.Second,
    ReadyToTrip: func(counts gobreaker.Counts) bool {
        failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
        return counts.Requests >= 10 && failureRatio >= 0.5
    },
}

cb := gobreaker.NewCircuitBreaker(settings)

result, err := cb.Execute(func() (interface{}, error) {
    resp, err := httpClient.Get("http://payment-service/api/charge")
    if err != nil {
        return nil, err
    }
    if resp.StatusCode >= 500 {
        return nil, fmt.Errorf("server error: %d", resp.StatusCode)
    }
    return resp, nil
})

Java: Resilience4j

CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)
    .waitDurationInOpenState(Duration.ofSeconds(30))
    .slidingWindowSize(10)
    .minimumNumberOfCalls(5)
    .permittedNumberOfCallsInHalfOpenState(3)
    .build();

CircuitBreaker cb = CircuitBreaker.of("paymentService", config);

Try.ofSupplier(CircuitBreaker.decorateSupplier(cb, () -> paymentClient.charge(amount)))
    .recover(CallNotPermittedException.class, e -> "Service unavailable");

Health Checks als Grundlage fuer Circuit Breaker

Circuit Breaker funktionieren nur zuverlaessig, wenn Health Checks korrekt konfiguriert sind. Ohne Readiness Probes sendet Kubernetes Traffic an Pods, die noch nicht bereit sind -- und der Circuit Breaker interpretiert die resultierenden Fehler als Service-Ausfall.

Stellen Sie sicher, dass jeder Service eine Readiness Probe definiert, die den tatsaechlichen Zustand der Anwendung widerspiegelt (nicht nur den HTTP-Server-Status). Die korrekte Konfiguration wird in Kubernetes Health Checks und Probes ausfuehrlich behandelt.

Monitoring und Alerting fuer Circuit Breaker

Envoy/Istio Metriken

# Circuit Breaker Overflow-Metriken
kubectl exec -n production deploy/order-service -c istio-proxy -- \
  curl -s localhost:15000/stats | grep circuit_breaker

# Wichtige Metriken:
# upstream_rq_pending_overflow  -- Requests abgelehnt wegen vollem Pending-Pool
# upstream_cx_pool_overflow     -- Connection Pool Overflow
# upstream_rq_retry             -- Anzahl der Retries
# upstream_rq_retry_success     -- Erfolgreiche Retries

# Outlier Detection Metriken
kubectl exec -n production deploy/order-service -c istio-proxy -- \
  curl -s localhost:15000/stats | grep outlier_detection

Prometheus Alerting Rules

Drei Alerts, die in jeder Istio-Installation aktiv sein sollten:

AlertPromQL (vereinfacht)Schwellwert
CircuitBreakerTrippedrate(envoy_cluster_upstream_rq_pending_overflow[5m])Ueber 0.1 fuer 2 Minuten
HighOutlierEjectionsenvoy_cluster_outlier_detection_ejections_activeMehr als 2 Endpoints ejected fuer 5 Minuten
HighRetryRaterate(upstream_rq_retry[5m]) / rate(upstream_rq_total[5m])Ueber 20% fuer 5 Minuten

Resilienz-Strategie: Alle Patterns kombinieren

Eine vollstaendige Resilienz-Konfiguration kombiniert alle vier Patterns:

# DestinationRule: Circuit Breaker + Bulkhead
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: complete-resilience
  namespace: production
spec:
  host: critical-service.production.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100        # Bulkhead: TCP-Limit
        connectTimeout: 3s          # Timeout: Verbindungsaufbau
      http:
        http1MaxPendingRequests: 50 # Bulkhead: Request-Queue
        http2MaxRequests: 200       # Bulkhead: Parallele Requests
        maxRetries: 3               # Retry: Max Versuche
    outlierDetection:
      consecutive5xxErrors: 3       # Circuit Breaker: Fehler-Schwelle
      interval: 10s
      baseEjectionTime: 30s
      maxEjectionPercent: 40
---
# VirtualService: Timeout + Retry
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: critical-service-routing
  namespace: production
spec:
  hosts:
    - critical-service
  http:
    - route:
        - destination:
            host: critical-service
            port:
              number: 8080
      timeout: 12s                  # Timeout: Gesamt
      retries:
        attempts: 3
        perTryTimeout: 3s           # Timeout: Pro Versuch
        retryOn: "5xx,reset,connect-failure"

Haeufige Fehler und Anti-Patterns

Retries ohne Idempotenz: Retry-Policies funktionieren nur sicher mit idempotenten Operationen. Ein POST-Request, der eine Bestellung erstellt, darf nicht ohne Weiteres wiederholt werden. Verwenden Sie Idempotency-Keys oder beschraenken Sie Retries auf GET-Requests.

Circuit Breaker zu aggressiv: Ein consecutive5xxErrors: 1 fuehrt dazu, dass einzelne sporadische Fehler sofort den Circuit oeffnen. Beginnen Sie mit 3-5 Fehlern und passen Sie basierend auf Beobachtung an.

maxEjectionPercent zu hoch: Bei maxEjectionPercent: 100 kann Outlier Detection alle Endpoints entfernen und den Service komplett unerreichbar machen. Setzen Sie den Wert auf maximal 50%.

Timeout-Kette nicht aufeinander abgestimmt: Wenn der Client ein Timeout von 5s hat, der Gateway ein Timeout von 30s und der Backend-Service ein Timeout von 60s, greift immer das Client-Timeout. Stellen Sie sicher, dass Timeouts von aussen nach innen abnehmen.

Fuer die Absicherung der Kommunikation zwischen Services in einem Service Mesh eignet sich Kubernetes Istio Ambient Mesh als sidecar-freie Alternative.

Fazit

Resilienz in Microservice-Architekturen ist kein optionales Feature -- es ist eine Voraussetzung fuer produktionsfaehige Systeme. Circuit Breaker, Timeout, Retry und Bulkhead bilden zusammen eine Verteidigungslinie gegen kaskadierende Ausfaelle. Istio/Envoy bietet die beste Infrastrukturebene dafuer, aber auch anwendungsseitige Implementierungen mit Resilience4j oder gobreaker sind valide Optionen. Starten Sie mit Timeouts und einfachem Retry, fuegen Sie Outlier Detection hinzu und ueberwachen Sie die Metriken, bevor Sie die Schwellwerte verfeinern.

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