Hub Gateway

The hub gateway pattern provides centralized HTTP ingress on the hub cluster with behaviors similar to an F5 BIG-IP ADC: VIP-style routing, TLS termination at the edge, and weighted traffic splits across backend services or spoke-derived routes.

Gateway API theory

Kubernetes Gateway API separates concerns:

  • Gateway — listens on addresses and ports; attaches TLS and listener policies.
  • HTTPRoute — attaches to a Gateway and selects backend Service resources with matches, filters, and weighted backends.

Weighted rules enable canary or active-active distributions between service versions or regions when paired with mesh or multi-cluster DNS strategies.

Cross-cluster routing architecture

The hub gateway routes traffic to spoke cluster OpenShift Routes via ExternalName services:

flowchart LR
  Browser["Browser"] --> Router["Hub OpenShift<br/>Router (HTTPS)"]
  Router --> IGW["Istio Gateway<br/>(port 8080)"]
  IGW --> WP["Waypoint Proxy<br/>(circuit breaking)"]
  WP --> EXT_E["ExternalName<br/>Service (east)"]
  WP --> EXT_W["ExternalName<br/>Service (west)"]
  EXT_E --> SPOKE_E["East OpenShift<br/>Router (HTTP:80)"]
  EXT_W --> SPOKE_W["West OpenShift<br/>Router (HTTP:80)"]
  SPOKE_E --> POD_E["Backend Pod"]
  SPOKE_W --> POD_W["Backend Pod"]

Front / API split

Traffic is split into separate Service objects per cluster and traffic type to give Kiali and Grafana finer-grained visibility:

Service Purpose
industrial-edge-east-front Static frontend assets for east spoke
industrial-edge-east-api Socket.IO / API backend for east spoke
industrial-edge-west-front Static frontend assets for west spoke
industrial-edge-west-api Socket.IO / API backend for west spoke

All four services use ExternalName pointing to the same spoke Route hostname, but Istio tracks them as distinct destinations. In Kiali’s traffic graph, each appears as a separate node.

The HTTPRoute uses two rules:

flowchart TB
  REQ["Incoming Request"] --> MATCH{Path?}
  MATCH -->|"/api/*"| API["API rule<br/>(session affinity)"]
  MATCH -->|"/*"| FRONT["Frontend rule<br/>(weighted split)"]
  API --> API_E["east-api<br/>weight: 100"]
  API --> API_W["west-api<br/>weight: 0"]
  FRONT --> FRONT_E["east-front<br/>weight: 50"]
  FRONT --> FRONT_W["west-front<br/>weight: 50"]
  1. /api prefix → routed to *-api services (defaults to east 100%, west 0% for Socket.IO session affinity).
  2. Catch-all → routed to *-front services (split by gateway.weights.east / west).

Override API weights with gateway.apiWeights in values when your application supports cross-cluster API load balancing.

Key requirements

  1. HTTP port 80, not HTTPS — Istio ambient mode gateways do not apply DestinationRule TLS settings. Using HTTPS causes CERTIFICATE_VERIFY_FAILED errors. Spoke Routes must set insecureEdgeTerminationPolicy: Allow.

  2. ServiceEntry for each external host — without a ServiceEntry, Envoy has no cluster definition for the external hostname and returns HTTP 500.

  3. Per-backend Host header rewrite — the spoke OpenShift router routes by Host header. Use RequestHeaderModifier filters on each backendRef in the HTTPRoute.

  4. Session affinity for Socket.IO — when load-balancing across multiple backends, Socket.IO polling and WebSocket upgrade must reach the same backend. The /api rule pins to a single spoke by default.

Circuit breaking

Each ExternalName service gets a DestinationRule with outlierDetection and connectionPool settings, enforced by the hub-gateway-system-waypoint proxy (Istio ambient L7).

Default configuration (aggressive / demo)

Parameter Default Purpose
connectionPool.tcp.maxConnections 100 Max concurrent TCP connections
connectionPool.http.h2UpgradePolicy DO_NOT_UPGRADE Spokes expect HTTP/1.1
connectionPool.http.maxRequestsPerConnection 10 Force connection recycling
outlierDetection.consecutive5xxErrors 3 Eject after 3 consecutive 5xx
outlierDetection.interval 10s Health check interval
outlierDetection.baseEjectionTime 30s Minimum ejection duration
outlierDetection.maxEjectionPercent 100 Allow ejecting the only endpoint

maxEjectionPercent: 100 is required because each ExternalName service resolves to a single endpoint; without it, Istio refuses to eject the last remaining host.

Overriding circuit breaker values

Set gateway.circuitBreaking.* in the hub-gateway values:

gateway:
  circuitBreaking:
    enabled: true
    outlierDetection:
      consecutive5xxErrors: 5
      baseEjectionTime: 60s

Set enabled: false to disable circuit breaking entirely.

Connectivity Link (Kuadrant) layers DNS automation, TLS policies, and advanced controls atop Gateway API. Start with plain HTTPRoutes if you need incremental adoption; enable Kuadrant policies when teams are ready.

IoT Dashboard integration

The Industrial Edge line-dashboard (iot-frontend) requires an iot-consumer sidecar to display sensor data:

  • iot-consumer bridges MQTT to WebSocket via Socket.IO
  • Mount a ConfigMap config.json with websocketHost: "" (empty = same origin)
  • Path-based Route /api to port 3000 (iot-consumer)
  • The hub gateway proxies /api requests to the correct spoke backend where iot-consumer handles the Socket.IO connection

Operational notes

  • Keep hostnames aligned with deployer.domain and corporate DNS wildcard records.
  • Combine with Service Mesh ambient when east-west encryption between gateway hops and workloads matters.
  • Monitor the gateway Envoy proxy metrics at port 15020 /stats/prometheus.

Next: attach HTTPRoutes per Connectivity Link policies when Kuadrant enforcement tightens, then verify backends inside Service Mesh ambient namespaces.

References

Implementation chart: charts/all/hub-gateway.


Next → Observability for Grafana dashboards, Kiali traffic graphs, and Kafka Console across clusters.