Routing and Proxying
The X-Skipper-Function header
Every request to the router must include an X-Skipper-Function header containing a JSON-encoded Function protobuf:
{
"namespace": "production",
"deployment": "my-app",
"tenant": "tenant-123",
"metadata": "v2",
"scale": {
"min_instances": 1,
"max_instances": 10,
"target_cpu_usage_milli": 500
},
"oneshot": false
}
The metadata field is an opaque string delivered to the pod during assignment. Use it for arbitrary data your application needs at runtime (configuration values, version tags, etc.).
The router returns 400 if the header is missing. The only exception is GET /healthz, which returns 200 without requiring the header.
Request flow
- Router extracts the function from the
X-Skipper-Functionheader. - Creates or updates heartbeat state for the function.
- Marks the function as active. A shared background loop sends heartbeats for all active functions every 5 seconds (configurable via
--heartbeat-interval). - Queries the controller via gRPC
GetInstanceRPC for an instance address. - Sets the request URL to
http://<instance-addr>. - Proxies the request to the backend pod.
- For oneshot functions: calls
ReleaseInstanceafter the response completes.
Retry logic
Failed requests are retried with up to 6 total attempts (configurable via --max-round-trip-attempts) with exponential backoff and jitter:
- Minimum backoff: 100ms
- Maximum backoff: 5s
- Only dial errors trigger retries (connection refused, host unreachable, dial timeout)
- TLS errors, response timeouts, and other non-dial failures are not retried
On each retry, the failed instance is excluded so the controller returns a different pod. The request body is preserved across retries.
Header rewriting
When proxying to backend pods, the router rewrites headers:
| Header | Value |
|---|---|
X-Skipper-Function |
Removed |
Host |
Preserved from original request |
X-Forwarded-For |
From incoming header, or RemoteAddr if absent |
X-Forwarded-Host |
From incoming header, or request Host if absent |
X-Forwarded-Proto |
From incoming header, or https/http based on TLS presence |
Forwarded |
RFC 7239 format with proper IPv6 quoting |
WebSocket support
WebSocket connections are proxied transparently. The standard HTTP upgrade mechanism handles the protocol switch -- no special configuration is needed.
The function remains marked as active for the duration of the WebSocket session, so heartbeats continue keeping the pods alive in the controller.
HTTP transport configuration
The router's HTTP transport is tuned for proxying workloads:
| Setting | Value |
|---|---|
| Dial timeout | 2s |
| Keep-alive | 30s |
| Max idle connections | 100 |
| Idle connection timeout | 1m30s |
| TLS handshake timeout | 10s |
| Protocol | HTTP/2 attempted (falls back to HTTP/1.1 if unsupported) |
| Compression | Disabled (no Accept-Encoding sent) |
The transport is wrapped with OpenTelemetry instrumentation for distributed tracing.