Routing by Region and Compliance Zone
Set a region (and where residency is regulated, a compliance-zone) entry in OpenTelemetry baggage at ingress, derive an X-Region header from it, and match that header in the service mesh so every downstream hop keeps the request — and its data — inside the approved geography.
Context & When It Matters
Data-residency rules such as GDPR, Schrems II, and sector-specific mandates require that a request originating from an EU subject be processed and stored on EU infrastructure. In a microservice system, honouring that is not a single gateway decision — it is an invariant that must hold across every hop of the distributed trace. If the order service runs in eu-west but the fraud-scoring service it calls resolves to a us-east pool, EU personal data has just crossed the Atlantic, and no amount of gateway geo-fencing at the edge caught it. Carrying the region in baggage makes the residency constraint travel with the request so the mesh can enforce it at every service boundary, not just the first.
Minimal Working Setup
The shape mirrors any baggage-driven routing: set the constraint at ingress, derive a matchable header, and let the mesh route on it. What changes for residency is the failure policy — the fallback must never cross the boundary.
# ingress: pin region + compliance zone from the request's origin
from opentelemetry import baggage, context
async def region_ingress(request, call_next):
# Resolved from the ingress point / subject's residency, not the client's claim.
zone = request.state.residency_zone # e.g. "eu"
region = request.state.ingress_region # e.g. "eu-west"
ctx = baggage.set_baggage("compliance-zone", zone)
ctx = baggage.set_baggage("region", region, context=ctx)
token = context.attach(ctx)
try:
return await call_next(request)
finally:
context.detach(token)
# istio: keep eu-zoned traffic on eu subsets; fail closed otherwise
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: fraud-scoring-residency
spec:
hosts:
- fraud-scoring
http:
- match:
- headers:
x-region:
exact: "eu-west"
route:
- destination: { host: fraud-scoring, subset: eu-west }
- match:
- headers:
x-region:
exact: "us-east"
route:
- destination: { host: fraud-scoring, subset: us-east }
# Fail closed: no cross-region catch-all. Unmatched residency traffic
# returns 404 rather than silently crossing a data boundary.
- fault:
abort:
httpStatus: 421
percentage: { value: 100 }
route:
- destination: { host: fraud-scoring, subset: eu-west }
The crucial difference from canary or tier routing: there is no permissive cross-region fallback. An unmatched region returns an error (421 Misdirected Request is a reasonable signal) rather than defaulting to an arbitrary subset. Availability must never be bought with a residency breach.
Implementation
The derivation reads both entries. region drives the mesh route; compliance-zone is the coarser policy label carried alongside for enforcement and audit. Emit X-Region for matching, and validate that the baggage region agrees with the infrastructure the request is actually running on.
# derive_region_header.py — region baggage -> matchable header + guardrail
from opentelemetry import baggage, trace
# The region this process is actually deployed in (from the SDK resource).
LOCAL_REGION = "eu-west"
def apply_region_routing(outbound_headers: dict, ctx=None) -> dict:
region = baggage.get_baggage("region", context=ctx)
zone = baggage.get_baggage("compliance-zone", context=ctx)
# Guardrail: baggage region must match where we are running. A mismatch
# means a routing bug or a spoofed value — record it and fail closed.
if region and region != LOCAL_REGION:
span = trace.get_current_span()
span.set_attribute("residency.mismatch", True)
span.set_attribute("residency.baggage_region", region)
raise ResidencyViolation(f"region {region} reached {LOCAL_REGION} cluster")
# Emit the single-purpose header the mesh matches on.
if region:
outbound_headers["x-region"] = region
# Stamp the zone for audit queries; do NOT pair it with a raw user id.
if zone:
trace.get_current_span().set_attribute("compliance.zone", zone)
return outbound_headers
class ResidencyViolation(Exception):
pass
Because the compliance zone is PII-adjacent, it must be handled with the same care as any sensitive attribute at the security boundaries in distributed tracing layer. Allowlist it at the OpenTelemetry Collector so the zone reaches your backend as a bounded, low-cardinality label and nothing else leaks alongside it.
# otel-collector: allowlist the zone, drop anything PII-adjacent
processors:
attributes/residency:
actions:
- key: compliance.zone # keep the coarse label
action: upsert
- key: user.email # never persist alongside the zone
action: delete
- key: subject.address
action: delete
The X-Region derivation and mesh matching are the same mechanism as the parent guide, dynamic request routing with baggage; the residency case adds the fail-closed policy and the region-vs-infrastructure guardrail.
Decision Criteria
Use these rules to decide how strict your region routing should be:
- If the data is subject to residency law, fail closed. No cross-region fallback, ever. Return an error and alert rather than serving the request from the wrong geography.
- If the routing is a soft preference (latency, cost), a cross-region fallback is acceptable. Only then may an unmatched region degrade to a nearer pool — and even then, log the crossing.
- Validate region baggage against the local cluster at the first internal hop. A mismatch is either a bug or an attack; treat it as fatal for residency-constrained traffic.
- Keep the zone coarse.
eu/us/apac, not a city or a precise coordinate. Coarse labels are lower-cardinality and less identifying.
Common Pitfalls
- Cross-region fallback violating residency. The most damaging mistake: an unconditional catch-all route added “for availability” that sends EU-zoned traffic to a US subset. For residency traffic this is a breach, not resilience. The fallback must stay in-zone or fail — never cross the boundary silently.
- Treating compliance-zone as harmless metadata. A zone label is PII-adjacent: it narrows where a subject is and can contribute to identification when combined with other data. Allowlist it at the Collector, keep it coarse, and never propagate it in the same baggage payload as a raw user identifier.
- Region baggage disagreeing with the infrastructure region. If ingress pins
region=eu-westbut a downstream call lands in aus-eastregion — through a misconfiguredDestinationRule, a stale subset, or a spoofed value — the request is now out of zone. Validate baggage region against the host cluster’s own region label at each boundary and fail closed on mismatch, as the guardrail above does.
FAQ
Is a compliance-zone baggage entry considered PII?
A coarse zone label such as eu or us is not itself personal data, but it is PII-adjacent: it narrows where a subject is and, combined with other attributes, can contribute to identification. Treat it as sensitive metadata, allowlist it at the Collector, and never pair it with a raw user identifier in the same baggage payload.
What should happen when the target region is unavailable?
For residency-constrained traffic, fail closed. A cross-region fallback that sends EU data to a US subset to preserve availability is a compliance violation, not graceful degradation. Route to an in-region degraded path or return an error; never let a fallback route silently cross the residency boundary.
How do I reconcile region baggage with the actual infrastructure region?
Validate the region entry at the first internal service against the host cluster’s own region label. If the baggage says eu-west but the request is running in the us-east region, that is a routing bug or a spoofed value. Reject or re-route rather than trusting the baggage blindly, and record the mismatch as a span attribute for audit.
Related
- Dynamic Request Routing with Baggage — the general middleware-derives-headers pattern this page specialises.
- Canary Routing with OpenTelemetry Baggage — the same mechanism applied to canary cohort pinning.
- Security Boundaries in Distributed Tracing — handling PII-adjacent metadata and trust boundaries.
↑ Back to Dynamic Request Routing with Baggage