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
- Trennung kritischer und unkritischer Pfade
WebSocket- und direkt damit verbundene Frontend-Endpunkte werden von restlichen Workloads logisch getrennt. - Scope-Reduktion für Konfigurationsänderungen
Änderungen in Ingress-Ressourcen anderer Services sollen die AppGW-Konfiguration für den WebSocket-Pfad nicht beeinflussen. - Explizite Steuerung über Ingress-Klassen
Die Zuordnung von Ingress-Ressourcen zu AGIC-Instanzen erfolgt primär über unterschiedlicheingressClass.
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-defaultkubernetes.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: falsefür beide AGIC-Instanzen ausreichend. shared: truewä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-wsDamit 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-timeoutwird 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.
- RollingUpdate mit
- 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.