Zone Aware Routing¶
Zone aware routing directs traffic preferentially to service endpoints in the same availability zone as the skipper instance handling the request. This helps in reducing cross-zone network latency and data transfer costs in multi-AZ Kubernetes clusters.
This page covers both operator setup (enabling and configuring zone aware routing) and application-level usage (opting out for individual resources).
Prerequisites¶
Zone aware routing requires:
-
Kubernetes EndpointSlices: Skipper reads the
zonefield from EndpointSlice endpoints, which Kubernetes populates from the node’stopology.kubernetes.io/zonelabel. -
Node topology labels: nodes must have the
topology.kubernetes.io/zonelabel set. -
Skipper configured with
-kubernetes-topology-zone: tells skipper which zone it is running in. See Enabling Zone Aware Routing below.
Enabling Zone Aware Routing¶
How zone aware routing is enabled depends on the deployment mode.
Dataclient mode (standalone skipper)¶
Set the -kubernetes-topology-zone flag to the availability zone of
the skipper instance:
-kubernetes-topology-zone string
sets the topology zone to be used for zone aware routing
(default: empty, zone aware routing disabled)
When this flag is empty (the default), zone aware routing is disabled and all endpoints are used regardless of zone.
RouteSRV mode¶
Configure skipper instances to fetch routes from RouteSRV using the
zone-specific endpoint by setting -routes-urls to include the zone
path:
-routes-urls http://<routesrv-host>/routes/<zone>
Injecting the zone via Kubernetes downward API¶
In a typical deployment, each skipper pod runs on a different node and should use that node’s zone. Use the Kubernetes downward API to inject the node’s topology zone into the pod:
apiVersion: apps/v1
kind: Deployment
metadata:
name: skipper
spec:
template:
spec:
containers:
- name: skipper
args:
- skipper
- -kubernetes
- -kubernetes-topology-zone=$(TOPOLOGY_ZONE)
env:
- name: TOPOLOGY_ZONE
valueFrom:
fieldRef:
fieldPath: metadata.labels['topology.kubernetes.io/zone']
How It Works¶
Skipper reads zone information from the Kubernetes EndpointSlice API.
Each endpoint in an EndpointSlice has a zone field (for example
eu-central-1a) that Kubernetes sets based on the node where the
pod runs.
When zone aware routing is enabled, skipper filters endpoints to prefer those in the same zone as the skipper instance. A minimum threshold of 3 endpoints in the local zone is required for zone filtering to activate. If fewer than 3 local-zone endpoints exist, all endpoints across all zones are used as a fallback to maintain availability.
flowchart LR
A[K8s EndpointSlice API] -->|zone per endpoint| B[Skipper Dataclient]
B -->|filter by zone<br/>threshold >= 3| C{Enough local<br/>endpoints?}
C -->|Yes| D[Use local-zone<br/>endpoints only]
C -->|No| E[Use all endpoints<br/>across zones]
D --> F[eskip Route with<br/>LBEndpoints]
E --> F
Architecture: Two Modes of Zone Filtering¶
Zone aware routing can operate in two distinct modes, depending on the deployment model. Both modes apply the same threshold of 3 endpoints per zone.
Mode 1: Dataclient filtering¶
When skipper runs standalone (without RouteSRV), the Kubernetes dataclient filters endpoints at route build time. When it resolves backends for an Ingress or RouteGroup, it returns only same-zone endpoints if at least 3 exist. Otherwise, all endpoints are returned.
This mode is active whenever -kubernetes-topology-zone is set.
Mode 2: RouteSRV filtering¶
When using a RouteSRV deployment, RouteSRV performs zone filtering when serving routes. RouteSRV polls for routes every 3 seconds, then creates per-zone route variants by filtering each route’s endpoints to the requesting zone.
Skipper instances fetch zone-filtered routes from RouteSRV by
requesting /routes/{zone} instead of /routes.
flowchart TB
subgraph routesrv ["RouteSRV"]
A[Poll routes from<br/>K8s dataclient] --> B[Build full<br/>route table]
B --> C[filterRoutesByZone]
C --> D["/routes — all endpoints"]
C --> E["/routes/{zone} — zone-filtered endpoints"]
end
subgraph skipper ["Skipper Instance (zone: eu-central-1a)"]
F["GET /routes/eu-central-1a"] --> E
E --> G[Route table with<br/>local-zone endpoints]
end
RouteSRV Integration¶
RouteSRV exposes zone-filtered routes at /routes/{zone}.
When a skipper instance requests /routes/eu-central-1a, RouteSRV
returns routes with LBEndpoints filtered to only those in
eu-central-1a (when the threshold is met).
Each zone gets independent HTTP caching metadata:
- ETag: zone-specific hash, different from the full-routes ETag
- X-Count: number of routes in the zone-filtered set
- Last-Modified: timestamp of the last update for that zone
If a requested zone has no data or fewer than 3 endpoints in a route, RouteSRV falls back to serving the full unfiltered route set.
Opting Out¶
Individual Ingress or RouteGroup resources can opt out of zone aware
routing by setting the zalando.org/traffic-zone-aware annotation to
"false". When opted out, traffic is routed to all endpoints
regardless of zone, even if there are at least three local-zone endpoints are available.
How opt-out works¶
- When the annotation is set while using the DataClient mode, the DataClient skips zone filtering for that resource’s endpoints (all endpoints are included in the route).
- When the annotation is set while using the RouteSRV mode, the filter
annotate("zalando.org/traffic-zone-aware", "false")is added to the route. This filter signals to RouteSRV that the route should not be zone-filtered when serving/routes/{zone}.
Behavior Details¶
-
Threshold: zone filtering activates only when there are at least 3 endpoints in the local zone. This prevents routing all traffic to a very small number of local pods, which could cause overload.
-
Fallback: when the threshold is not met, all endpoints across all zones are used. This ensures availability is maintained even when a zone has few or no endpoints.