Veröffentlicht am

Kubernetes Retry Pattern mit Istio konfigurieren

Teilen:
Authors

Kubernetes Retry Pattern: Automatische Wiederholung bei Fehlern

TL;DR

  • Transiente Fehler (Netzwerk-Timeouts, 503er, Connection Resets) werden durch Retries automatisch abgefangen, ohne dass Endnutzer etwas bemerken
  • Drei Retry-Strategien: Immediate Retry, Fixed Delay und Exponential Backoff mit Jitter -- je nach Fehlertyp unterschiedlich geeignet
  • Istio VirtualService und Envoy bieten Retry-Konfiguration auf Infrastrukturebene, komplett ohne Aenderung am Anwendungscode
  • Retries ohne Idempotenz sind gefaehrlich und fuehren zu doppelten Bestellungen oder Buchungen
  • Kombination mit Circuit Breaker verhindert, dass Retries ein bereits ueberlastetes System endgueltig zum Absturz bringen

Warum Retries in verteilten Systemen Pflicht sind

In einem Kubernetes-Cluster kommunizieren Dutzende bis Hunderte Services ueber das Netzwerk. Netzwerke sind nicht zuverlaessig. Pods werden neu geplant, Nodes verschwinden, Load Balancer verteilen Traffic auf Pods, die gerade hochfahren. Transiente Fehler sind der Normalfall, nicht die Ausnahme.

Ohne Retry-Logik fuehrt jeder dieser Fehler zu einem sichtbaren Problem fuer den Endnutzer. Mit einer durchdachten Retry-Strategie werden ueber 90 Prozent dieser Fehler unsichtbar abgefangen.

Das Problem: Naive Retries koennen die Situation verschlimmern. Wenn ein Service ueberlastet ist und 100 Clients gleichzeitig ihre fehlgeschlagenen Requests wiederholen, entsteht ein Retry Storm, der den Service endgueltig zum Absturz bringt.

Die drei Retry-Strategien im Vergleich

Immediate Retry

Der Request wird sofort erneut gesendet. Sinnvoll bei Connection Resets, wenn der Fehler durch einen kurzfristigen Zustand verursacht wurde. Risiko: Bei Ueberlast verschlimmert sofortiges Retry die Situation massiv.

Fixed Delay Retry

Zwischen den Versuchen liegt ein fester Zeitabstand, z.B. 500ms. Besser als Immediate Retry, aber alle Clients wiederholen gleichzeitig, wenn sie den ersten Fehler gleichzeitig bekamen.

Exponential Backoff mit Jitter (Best Practice)

Der Abstand zwischen Versuchen verdoppelt sich mit jedem Versuch, plus eine zufaellige Komponente (Jitter). Das verteilt die Retry-Last ueber die Zeit und verhindert Thundering-Herd-Effekte.

StrategieVerzoegerungEinsatzRisiko
Immediate0msConnection ResetRetry Storm bei Ueberlast
Fixed Delay500ms festKurze AusfaelleSynchronisierte Retries
Exponential Backoff100ms, 200ms, 400msAllgemein empfohlenLaengere Gesamtwartezeit
Backoff mit Jitter100ms +/- 50ms, variabelBest PracticeMinimal

Retry auf Infrastrukturebene mit Istio

Der groesste Vorteil eines Service Mesh: Retry-Logik wird aus dem Anwendungscode herausgehalten und zentral konfiguriert. Jeder Envoy Sidecar fuehrt Retries automatisch durch.

Mehr zu Istio-Konfiguration in Kubernetes-Umgebungen findet sich im Beitrag zu Istio Ambient Mesh.

Istio VirtualService mit Retry-Policy

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

Die wichtigen Parameter im Detail:

  • attempts: Maximale Anzahl Wiederholungsversuche (hier 3, also insgesamt 4 Versuche)
  • perTryTimeout: Timeout fuer jeden einzelnen Versuch
  • retryOn: Welche Fehler einen Retry ausloesen
  • timeout: Gesamttimeout fuer alle Versuche zusammen

Envoy Retry-Bedingungen

BedingungWann wird retried
5xxBei allen Server-Fehlern (500-599)
resetBei TCP Connection Reset
connect-failureWenn der Verbindungsaufbau fehlschlaegt
retriable-4xxBei 409 Conflict
gateway-errorBei 502, 503, 504
refused-streamBei HTTP/2 REFUSED_STREAM

Erweiterte Backoff-Konfiguration mit EnvoyFilter

Fuer feinere Kontrolle ueber Backoff-Parameter laesst sich ein EnvoyFilter einsetzen:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: retry-backoff-config
  namespace: production
spec:
  workloadSelector:
    labels:
      app: order-service
  configPatches:
  - applyTo: HTTP_ROUTE
    match:
      context: SIDECAR_OUTBOUND
    patch:
      operation: MERGE
      value:
        route:
          retry_policy:
            retry_back_off:
              base_interval: 100ms
              max_interval: 1s
            num_retries: 3
            retry_on: "5xx,connect-failure,refused-stream"

Der base_interval definiert den Startwert fuer den Exponential Backoff. Envoy verdoppelt diesen Wert mit jedem Versuch bis zum max_interval. Zusaetzlich fuegt Envoy automatisch Jitter hinzu.

Retry auf Anwendungsebene

Infrastruktur-Retries decken HTTP-basierte Kommunikation ab. Fuer Datenbank-Verbindungen, Message-Queue-Operationen oder komplexe Ablaeufe braucht man Retries im Anwendungscode.

Go: Retry mit Exponential Backoff

func retryWithBackoff(ctx context.Context, maxRetries int, fn func() error) error {
    baseDelay := 100 * time.Millisecond
    maxDelay := 10 * time.Second

    for attempt := 0; attempt <= maxRetries; attempt++ {
        err := fn()
        if err == nil {
            return nil
        }
        if !isRetryable(err) {
            return err
        }
        if attempt == maxRetries {
            return fmt.Errorf("max retries exceeded: %w", err)
        }

        delay := time.Duration(math.Pow(2, float64(attempt))) * baseDelay
        if delay > maxDelay {
            delay = maxDelay
        }
        jitter := time.Duration(rand.Int63n(int64(delay / 2)))
        delay += jitter

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

Java: Resilience4j Retry

RetryConfig retryConfig = RetryConfig.custom()
    .maxAttempts(3)
    .waitDuration(Duration.ofMillis(500))
    .intervalFunction(IntervalFunction.ofExponentialBackoff(500, 2.0))
    .retryOnException(e -> e instanceof ServiceUnavailableException)
    .ignoreExceptions(BadRequestException.class)
    .build();

Retry retry = Retry.of("orderService", retryConfig);

Supplier<Order> decoratedSupplier = Retry.decorateSupplier(
    retry, () -> orderClient.createOrder(request)
);

Try.ofSupplier(decoratedSupplier)
    .recover(MaxRetriesExceededException.class, e -> fallbackOrder());

Idempotenz: Die Voraussetzung fuer sichere Retries

Retries bedeuten, dass ein Request mehrfach beim Zielservice ankommen kann. Ohne Idempotenz fuehrt das zu Problemen:

OperationIdempotent?Retry sicher?
GET /orders/123JaJa
PUT /orders/123 (vollstaendig)JaJa
DELETE /orders/123JaJa
POST /orders (neue Bestellung)NeinGefaehrlich
PATCH /orders/123 (Menge +1)NeinGefaehrlich

Fuer nicht-idempotente Operationen gibt es eine bewaehrte Loesung: Idempotency Keys. Der Client sendet einen eindeutigen Key mit jedem Request. Der Server prueft vor der Ausfuehrung, ob dieser Key bereits verarbeitet wurde.

apiVersion: v1
kind: ConfigMap
metadata:
  name: idempotency-config
  namespace: production
data:
  config.yaml: |
    idempotency:
      header_name: "Idempotency-Key"
      store: redis
      ttl: 24h
      redis_host: "redis-master.production.svc.cluster.local:6379"

Circuit Breaker Integration

Retries allein reichen nicht. Wenn ein Service dauerhaft ausfaellt, produzieren Retries nur zusaetzliche Last. Ein Circuit Breaker erkennt, wann ein Service nicht mehr reagiert, und stoppt weitere Aufrufe.

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

Die outlierDetection konfiguriert den Circuit Breaker. Nach 5 aufeinanderfolgenden 5xx-Fehlern wird der betroffene Pod fuer 30 Sekunden aus dem Load Balancing entfernt. Retries werden dann automatisch an gesunde Pods gelenkt.

Mehr dazu im Beitrag zu Resilience Patterns in Kubernetes.

Monitoring von Retries

Retries, die unsichtbar im Hintergrund ablaufen, sind gefaehrlich, wenn man sie nicht ueberwacht. Drei Metriken sind essentiell:

  1. Retry-Rate: Wie viele Requests brauchen mindestens einen Retry? Steigt diese Rate, deutet das auf ein Problem hin
  2. Retry-Erfolgsrate: Wie viele Retries fuehren letztendlich zum Erfolg? Eine niedrige Rate bedeutet, dass Retries sinnlos Last erzeugen
  3. Latenz-Overhead: Wie viel laenger dauern Requests mit Retries im Vergleich?
# Retry-Statistiken eines Pods abfragen
kubectl exec -n production deploy/order-service -c istio-proxy -- \
  curl -s localhost:15000/stats | grep retry

# Wichtige Metriken:
# upstream_rq_retry                -- Gesamtzahl der Retries
# upstream_rq_retry_success        -- Erfolgreiche Retries
# upstream_rq_retry_overflow       -- Retries die das Budget ueberschritten
# upstream_rq_retry_limit_exceeded -- Retries ueber dem Maximum

Fuer die Grundlage zuverlaessiger Health Checks im Zusammenspiel mit Retries empfiehlt sich der Beitrag zu Kubernetes Health Checks und Probes.

Best Practices

Maximal 3 Retries: Mehr als 3 Retries bringen selten Erfolg und erhoehen die Gesamtlatenz erheblich. Wenn nach 4 Versuchen keine Antwort kommt, liegt kein transienter Fehler vor.

Gesamttimeout setzen: Retries mit Exponential Backoff koennen sich aufaddieren. Ein Gesamttimeout von 10-15 Sekunden verhindert endloses Warten.

Nur retriable Fehler wiederholen: 400 Bad Request oder 404 Not Found werden durch Wiederholen nicht besser. Retries nur bei 5xx, Timeouts und Connection-Fehlern.

Jitter immer aktivieren: Ohne Jitter wiederholen alle Clients zur exakt gleichen Zeit und erzeugen periodische Lastspitzen.

Retry-Budget definieren: Maximal 20 Prozent des gesamten Traffics duerfen Retries sein. Wird das Budget ueberschritten, werden Retries temporaer deaktiviert.

Fuer eine umfassende Uebersicht zu Service-Kommunikation in Kubernetes empfiehlt sich der Beitrag zu Kubernetes Service Discovery.

Haeufige Fehler bei der Implementierung

Retry auf beiden Ebenen: Wenn sowohl die Anwendung als auch Istio Retries durchfuehren, multiplizieren sich die Versuche. 3 App-Retries mal 3 Istio-Retries ergibt 9 Versuche. Entscheidet euch fuer eine Ebene.

Kein Backoff: Sofortige Retries ohne Verzoegerung ueberlasten den Zielservice. Exponential Backoff ist kein Nice-to-have, sondern Pflicht.

Retry bei nicht-idempotenten Operationen: POST-Requests ohne Idempotency-Key zu wiederholen fuehrt zu doppelten Daten. Prueft die Idempotenz, bevor ihr Retries aktiviert.

Retries ohne Monitoring: Retries kaschieren Probleme. Ohne Metriken bemerkt man nicht, dass ein Service seit Wochen instabil ist und nur durch Retries am Laufen gehalten wird.

Zusammenfassung

Das Retry Pattern ist eine der grundlegendsten Resilience-Massnahmen in Kubernetes-Umgebungen. Mit Istio und Envoy laesst es sich auf Infrastrukturebene umsetzen, ohne Anwendungscode anzupassen. Fuer Datenbank- und Message-Queue-Operationen braucht man zusaetzlich Retries im Code.

Die drei wichtigsten Regeln: Exponential Backoff mit Jitter verwenden, Idempotenz sicherstellen und Retries mit einem Circuit Breaker kombinieren. Ohne diese Grundlagen richten Retries mehr Schaden an, als sie verhindern.

Wenn Sie Unterstuetzung bei der Implementierung von Retry-Strategien in Ihrer Kubernetes-Umgebung benoetigen, kontaktieren Sie uns 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

kubernetesretry pattern+1 weitere

Kubernetes Deutschland: Retry Pattern für resiliente Systeme

Erfahren Sie, wie das Retry Pattern die Resilienz und Fehlertoleranz von Kubernetes-Anwendungen in Deutschland optimiert. Steigern Sie die Verfügbarkeit Ihrer Systeme und gewährleisten Sie mit intelligenten Wiederholungsstrategien eine robuste Performance auch in anspruchsvollen Umgebungen.

Weiterlesen →