Request Routing & Ingress Contract
Envy supports Istio VirtualServices and Gateway API producer routes with Cilium and Linkerd. Linkerd uses Envoy Gateway for preview ingress and immutable producer-route identities because its controller omits route generations from status. See Choose Your Mesh for setup and acceptance status. The VirtualService details below describe the Istio adapter.
Network Topology
Section titled “Network Topology”Both URLs reuse gateway and service-a. The selected mesh selects service-b v2 only when the request carries the matching composition ID. Both service-b versions connect to the same shared data.
Scroll sideways to follow wider branches, or read the connections below.
Read the connections
- Baseline URL → gateway · v1: baseline request.
- Preview URL → gateway · v1: preview request.
- gateway · v1 → service-a · v1: forward baggage.
- service-a · v1 → service-b · v1: no matching context.
- service-a · v1 → service-b · v2: composition=cmp-123.
- service-b · v1 → Database & cache: read / write.
- service-b · v2 → Database & cache: read / write.
Ingress Contract
Section titled “Ingress Contract”-
Baseline URL:
http://baseline.envy.localhost:8080- The exact-host route unconditionally removes all inbound baggage to prevent callers from spoofing composition identifiers.
-
Composition Preview URL:
http://cmp-<id>.envy.localhost:8080- Ingress matches the exact preview hostname and sets request header:
baggage: composition=<id> - Ingress sets the response header:
x-envy-route: <id>
- Ingress matches the exact preview hostname and sets request header:
-
Teardown Verification:
- Cleanup requires observing an HTTP 404 response without the
x-envy-routeheader before pod workloads are drained. This checks that new requests through the preview ingress no longer select the composition; it does not prove all in-flight or internal requests have completed.
- Cleanup requires observing an HTTP 404 response without the
Mesh Destination Routing
Section titled “Mesh Destination Routing”Microservices in the cluster continue calling ordinary Kubernetes Service FQDNs (e.g. http://service-b.envy-baseline.svc.cluster.local:8080).
Envy configures one aggregate mesh VirtualService for each overridden baseline host:
apiVersion: networking.istio.io/v1kind: VirtualServicemetadata: name: envy-mesh-service-b namespace: envy-baselinespec: hosts: - service-b.envy-baseline.svc.cluster.local http: # 1. Match composition cmp-7f39a baggage - match: - headers: baggage: regex: '^(.*,)?[ \t]*composition[ \t]*=[ \t]*cmp-7f39a[ \t]*(;[^,]*)?(,.*)?$' route: - destination: host: service-b.envy-cmp-7f39a.svc.cluster.local port: number: 8080
# 2. Default route: fall through to shared baseline - route: - destination: host: service-b.envy-baseline.svc.cluster.local port: number: 8080Context Propagation
Section titled “Context Propagation”Applications must propagate the incoming W3C baggage header on downstream HTTP calls. Configuring a global propagator alone is insufficient: instrument the incoming handler and outgoing transport, and pass the incoming request context to the downstream request.
This example follows the shop application:
package main
import ( "io" "log" "net/http" "time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/propagation")
func main() { prop := propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) client := &http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport, otelhttp.WithPropagators(prop)), Timeout: 5 * time.Second, } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, "http://service-b.envy-baseline.svc.cluster.local:8080/", nil) if err != nil { http.Error(w, "request setup failed", http.StatusInternalServerError) return } resp, err := client.Do(req) if err != nil { http.Error(w, "downstream unavailable", http.StatusBadGateway) return } defer resp.Body.Close() w.WriteHeader(resp.StatusCode) _, _ = io.Copy(w, resp.Body) }) server := &http.Server{ Addr: ":8080", Handler: otelhttp.NewHandler(handler, "gateway", otelhttp.WithPropagators(prop)), ReadHeaderTimeout: 3 * time.Second, } log.Fatal(server.ListenAndServe())}Apply equivalent instrumentation at every HTTP hop that can call an overridden service. Without propagation, downstream requests fall back to baseline services. Baggage is request-routing context, not an authorization credential.