StatusTypeRelevance

ach infrastructure concept

Ausgangssituation

Die SDK nutzt Azure Application Gateway (Standard v2) in Kombination mit dem Azure Application Gateway Ingress Controller (AGIC), um HTTP- und WebSocket-Traffic in ein Kubernetes-Cluster zu routen.📲

Aktuell besteht das Problem, dass Unstabilitäten in nicht direkt betroffenen Pods/Services (z. B. CrashLoops, fehlerhafte Health-Probes) dazu führen können, dass WebSocket-Verbindungen abbrechen, obwohl der WebSocket-Pod selbst gesund ist. Die Ursache liegt in der Kopplung von:

  • AGIC, der eine konsolidierte Konfiguration für das Application Gateway erzeugt, und
  • App Gateway, das Health-Zustand und Konfiguration über Backend-Pools hinweg verarbeitet.
@startuml
title Vorher: Ein gemeinsamer AGIC für alle Ingresses
 
skinparam componentStyle rectangle
 
cloud "Internet" as Internet
 
node "Azure Subscription" {
  node "Azure Resource Group" {
    node "Azure Application Gateway\n(Standard v2)" as AppGW
  }
}
 
node "Kubernetes-Cluster" as K8s {
  component "AGIC\n(ingressClass: default)" as AGIC
 
  package "Namespace ns-ws" as NS_WS {
    [Ingress WS\n(host: ws.sdk-cloud.de,\nclass: default)] as IngressWS
    [Service hub-service] as HubService
    [WebSocket-Pods\n(Quarkus Hub)] as HubPods
  }
 
  package "Namespace ns-core / ns-apps-*" as NS_OTHER {
    [Ingress API/Apps\n(class: default)] as IngressOther
    [Diverse Services] as OtherServices
    [Diverse Pods] as OtherPods
  }
}
 
Internet --> AppGW : HTTPS / WSS
AGIC --> AppGW : ARM API\n(AppGW-Konfiguration)
AGIC --> IngressWS
AGIC --> IngressOther
 
IngressWS --> HubService
HubService --> HubPods
 
IngressOther --> OtherServices
OtherServices --> OtherPods
 
note bottom of AGIC
  AGIC scannt alle Namespaces
  und alle Ingress-Ressourcen
  mit (impliziter) Standard-Ingress-Class.
  Fehler oder Flapping in anderen Services
  können die AppGW-Konfiguration
  und damit auch WebSocket-Verbindungen
  beeinflussen.
end note
 
@enduml
 

Zielsetzung

Ziel der Architektur ist:

  • Die Verfügbarkeit und Stabilität der WebSocket-Verbindungen zu erhöhen,
  • den Blast Radius von Fehlern in anderen Diensten zu reduzieren,
  • und das ohne grundsätzlichen Technologiewechsel (AppGW + AGIC bleiben gesetzt).

Leitprinzipien

  1. Trennung kritischer und unkritischer Pfade
    WebSocket- und direkt damit verbundene Frontend-Endpunkte werden von restlichen Workloads logisch getrennt.
  2. Scope-Reduktion für Konfigurationsänderungen
    Änderungen in Ingress-Ressourcen anderer Services sollen die AppGW-Konfiguration für den WebSocket-Pfad nicht beeinflussen.
  3. Explizite Steuerung über Ingress-Klassen
    Die Zuordnung von Ingress-Ressourcen zu AGIC-Instanzen erfolgt primär über unterschiedliche ingressClass.

Zielarchitektur (High-Level)

Komponenten

  • Azure Application Gateway (ein Gateway)
    • Weiterhin zentrale Entry-Komponente für HTTP und WebSocket.
    • Bekommt Konfiguration aus zwei AGIC-Instanzen.
  • AGIC-WS (Ingress Controller für WebSocket-Pfad)
    • Verantwortlich für alle Ingress-Ressourcen, die WebSocket- und eng gekoppelte Frontend-Endpunkte bereitstellen.
    • Arbeitet mit einer dedizierten ingressClass.
  • AGIC-DEFAULT (Ingress Controller für übrige Workloads)
    • Verantwortlich für alle anderen Ingress-Ressourcen.
    • Nutzt eine separate ingressClass.
  • WebSocket-Service
    • Spezielles Deployment (z. B. ACH/Data-Aggregation-Service),
    • optional plus dazugehörige REST-Endpunkte.
  • Weitere Services / Pods
    • Normale APIs, Tools, interne Anwendungen, die nicht direkt in WebSocket-Kommunikation involviert sind.
@startuml
title Nachher: Getrennte AGIC-Instanzen und Ingress-Klassen
 
skinparam componentStyle rectangle
 
cloud "Internet" as Internet
 
node "Azure Subscription" {
  node "Azure Resource Group" {
    node "Azure Application Gateway\n(Standard v2)" as AppGW
  }
}
 
node "Kubernetes-Cluster" as K8s {
 
  component "AGIC-WS\n(ingressClass: agic-ws\nwatchNamespace: ns-ws optional)" as AGIC_WS
  component "AGIC-DEFAULT\n(ingressClass: agic-default\nwatchNamespace: ns-core, ns-apps-* optional)" as AGIC_DEF
 
  package "Namespace ns-ws\n(WebSocket & direktes Frontend)" as NS_WS {
    [Ingress WS\n(host: ws.sdk-cloud.de,\nclass: agic-ws)] as IngressWS
    [Service hub-service] as HubService
    [WebSocket-Pods\n(Quarkus Hub)] as HubPods
 
    [Health-Endpoint\n/healthz-hub] as HealthWS
  }
 
  package "Namespace ns-core / ns-apps-*" as NS_OTHER {
    [Ingress API/Apps\n(class: agic-default)] as IngressOther
    [Diverse Services] as OtherServices
    [Diverse Pods] as OtherPods
 
    [Health-Endpoints\n/healthz-api, ...] as HealthOther
  }
}
 
Internet --> AppGW : HTTPS / WSS
 
' AGIC-zu-AppGW
AGIC_WS --> AppGW : ARM API\n(Konfiguration nur für agic-ws Ingresses)
AGIC_DEF --> AppGW : ARM API\n(Konfiguration nur für agic-default Ingresses)
 
' AGIC-WS Pfad
AGIC_WS --> IngressWS
IngressWS --> HubService
HubService --> HubPods
HubService --> HealthWS
 
' AGIC-DEFAULT Pfad
AGIC_DEF --> IngressOther
IngressOther --> OtherServices
OtherServices --> OtherPods
OtherServices --> HealthOther
 
note bottom of NS_WS
  Ingress-Class: agic-ws
  Eigene Health-Probe (z.B. /healthz-hub)
  Eigene HTTP-Settings (request-timeout, Probe)
  Konfigurations- und Health-Probleme
  in anderen Namespaces/Ingresses
  wirken nicht mehr direkt auf diese
  WebSocket-Backends.
end note
 
note bottom of NS_OTHER
  Ingress-Class: agic-default
  Andere Services können unhealthy sein,
  ohne dass AGIC-WS sie sieht.
end note
 
@enduml
 

Kernmechanismus: Trennung über Ingress-Klassen

Die Trennung erfolgt über unterschiedliche Ingress-Klassen.

Ingress-Klassen

  • Für WebSocket-relevante Ingress-Ressourcen:
    spec:
      ingressClassName: agic-ws
  • Für alle anderen Ingress-Ressourcen:
    spec:
      ingressClassName: agic-default

Damit gilt:

  • AGIC-WS verarbeitet ausschließlich Ingress-Ressourcen mit ingressClassName: agic-ws.
  • AGIC-DEFAULT verarbeitet ausschließlich Ingress-Ressourcen mit ingressClassName: agic-default.

Diese Trennung ist bereits ausreichend, um sicherzustellen, dass fehlerhafte Ingress-Ressourcen anderer Services die AppGW-Konfiguration für den WebSocket-Einstiegspunkt nicht mehr beeinflussen, sofern diese korrekt klassifiziert sind.


AGIC-Konfiguration

AGIC-WS (WebSocket-Controller)

Zweck: Verwaltung nur der WebSocket-bezogenen Hosts/Ingresses.

Wichtige Parameter:

  • kubernetes.ingressClass = agic-ws
    AGIC-WS liest und verarbeitet nur Ingress-Ressourcen dieser Klasse.
  • Optional: kubernetes.watchNamespace = ns-ws
    Wenn zusätzlich eine Namespace-Isolation gewünscht ist.

Weitere typische Konfigurationspunkte:

  • appgw.*: Subscription, ResourceGroup, Name des bestehenden Application Gateways.
  • armAuth.*: Identität für ARM-Zugriff.
  • rbac.enabled = true.

AGIC-DEFAULT (Standard-Controller)

Zweck: Verwaltung der restlichen Ingress-Ressourcen.

  • kubernetes.ingressClass = agic-default
  • kubernetes.watchNamespace:
    • kann leer bleiben (AGIC sieht alle Namespaces).

Auch hier gilt: Nur Ingress-Ressourcen mit ingressClassName: agic-default werden berücksichtigt.

shared-Flag

Das appgw.shared-Flag ist für diese Trennung nicht zwingend relevant:

  • Bei einem gemeinsamen Application Gateway und sauber getrennten Hostnamen/Pfaden ist shared: false für beide AGIC-Instanzen ausreichend.
  • shared: true wäre nur notwendig, wenn weitere, manuell gepflegte oder externe Konfigurationen am gleichen Gateway bestehen, die nicht von AGIC verändert werden sollen.

WebSocket-spezifische Ingress- und Probe-Konfiguration

Um die Stabilität der WebSocket-Verbindungen zusätzlich abzusichern, wird für den WebSocket-Service eine dedizierte Health-Probe und eigene HTTP-Settings verwendet. Dies geschieht über AGIC-Annotations am Ingress:

Beispiel:

metadata:
  annotations:
    appgw.ingress.kubernetes.io/request-timeout: "900"  # z. B. 15 Minuten
    appgw.ingress.kubernetes.io/health-probe-path: "/healthz-hub"
    appgw.ingress.kubernetes.io/health-probe-interval: "30"
    appgw.ingress.kubernetes.io/health-probe-timeout: "10"
    appgw.ingress.kubernetes.io/health-probe-unhealthy-threshold: "3"
spec:
  ingressClassName: agic-ws

Damit gilt:

  • Der WebSocket-Service hat eine eigene Probe, die nicht mit anderen Services geteilt wird (also unabhängig von anderen Pods im Cluster).
  • Flapping oder Fehler in anderen Services können den Health-Status des WebSocket-Backends nicht beeinflussen.
  • request-timeout wird explizit auf einen zu den WebSocket-Anforderungen passenden Wert gesetzt.

Deployment-Richtlinien für den WebSocket-Service

Damit die Infrastruktur-Trennung wirkt, sollte der WebSocket-Service zusätzlich stabil betrieben werden:

  • Deployment-Strategie:
    • RollingUpdate mit maxUnavailable: 0, maxSurge: 1, um kontinuierliche Verfügbarkeit sicherzustellen.
  • Readiness-/Liveness-Probes:
    • Keine übermäßig aggressiven Timeouts/Thresholds, um unnötiges Flapping zu vermeiden.

Ergebnis

Mit dieser Architektur erreichen wir:

  • WebSocket-Verbindungen, deren Stabilität primär vom Zustand des entsprechenden Pods/Deployments abhängt und nicht mehr indirekt von fremden, fehlerhaften Pods.
  • Klare Trennung der Ingress-Verantwortlichkeiten über unterschiedliche ingressClass-Werte und optional über Namespaces.
  • Eine kontrollierbare und begrenzte Auswirkung von Fehlern: Probleme in „normalen“ Services erreichen den WebSocket-Einstiegspunkt nicht mehr über AGIC/AppGW-Konfigurationsänderungen.

Die Basisvariante mit zwei Ingress-Klassen und zwei AGIC-Instanzen ist dabei relativ schnell umsetzbar. Namespace-basierte Trennung kann schrittweise ergänzt werden, um die Isolation weiter zu verstärken, ohne dass das Grundkonzept geändert werden muss.