syntax = "proto3";
package io.linkerd.proxy.outbound;
option go_package = "github.com/linkerd/linkerd2-proxy-api/go/outbound";
import "net.proto";
import "destination.proto";
import "meta.proto";
import "grpc_route.proto";
import "opaque_route.proto";
import "tls_route.proto";
import "http_route.proto";
import "google/protobuf/duration.proto";
service OutboundPolicies {
rpc Get(TrafficSpec) returns (OutboundPolicy) {}
rpc Watch(TrafficSpec) returns (stream OutboundPolicy) {}
}
message TrafficSpec {
// Uniquely identifies the source proxy workload (e.g., pod name) to the
// control plane.
string source_workload = 1;
// Describes a target address, as observed by the proxy.
oneof target {
// Indicates the proxy is connecting to a specific IP:port.
io.linkerd.proxy.net.TcpAddress addr = 2;
// Indicates the proxy is connecting to a named address (like an HTTP
// authority).
string authority = 3;
}
}
// Outbound policy for a given traffic spec.
message OutboundPolicy {
// Indicates the protocol to use for this target. This will be set to Opaque
// if the target has been marked as opaque and will be Discover otherwise.
ProxyProtocol protocol = 1;
// Describes the resource for which outbound policy has been discovered.
io.linkerd.proxy.meta.Metadata metadata = 2;
}
message ProxyProtocol {
oneof kind {
Detect detect = 1;
Opaque opaque = 2;
// HTTP/1 policy configuration.
Http1 http1 = 3;
// HTTP/2 policy configuration.
Http2 http2 = 4;
// gRPC policy configuration.
Grpc grpc = 5;
// TLS policy configuration.
Tls tls = 6;
}
message Detect {
// Protocol detection timeout.
google.protobuf.Duration timeout = 1;
Opaque opaque = 2;
// HTTP/1 policy configuration.
Http1 http1 = 3;
// HTTP/2 policy configuration.
Http2 http2 = 4;
}
message Opaque {
repeated OpaqueRoute routes = 1;
}
message Http1 {
repeated HttpRoute routes = 1;
// If empty, circuit breaking is not performed.
FailureAccrual failure_accrual = 2;
// If set, configures load biasing for 429-aware load balancing.
LoadBiasConfig load_bias = 3;
// If set, configures handling of Retry-After headers (HTTP 429/503).
RetryAfterConfig retry_after = 4;
}
message Http2 {
repeated HttpRoute routes = 1;
// If empty, circuit breaking is not performed.
FailureAccrual failure_accrual = 2;
// If set, configures load biasing for 429-aware load balancing.
LoadBiasConfig load_bias = 3;
// If set, configures handling of Retry-After headers (HTTP 429/503).
RetryAfterConfig retry_after = 4;
}
message Grpc {
repeated GrpcRoute routes = 1;
// If empty, circuit breaking is not performed.
FailureAccrual failure_accrual = 2;
// If set, configures load biasing for 429-aware load balancing.
LoadBiasConfig load_bias = 3;
// If set, configures handling of Retry-After headers (HTTP 429/503)
// and grpc-retry-pushback-ms trailers (gRPC RESOURCE_EXHAUSTED).
RetryAfterConfig retry_after = 4;
}
message Tls {
repeated TlsRoute routes = 1;
}
}
// Outbound-specific HTTP route configuration (based on the
// [Gateway API](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute)).
message HttpRoute {
io.linkerd.proxy.meta.Metadata metadata = 1;
// If empty, the host value is ignored.
repeated io.linkerd.proxy.http_route.HostMatch hosts = 2;
// Must have at least one rule.
repeated Rule rules = 3;
message Rule {
repeated io.linkerd.proxy.http_route.HttpRouteMatch matches = 1;
repeated Filter filters = 2;
Distribution backends = 3;
// DEPRECATED: use `timeouts` instead. Servers should continue to set this
// value to the same value as `timeouts.response`.
google.protobuf.Duration requestTimeout = 4 [deprecated = true];
io.linkerd.proxy.http_route.Timeouts timeouts = 5;
Retry retry = 6;
// If true, the proxy will allow headers to control retry and timeout
// behavior.
bool allow_l5d_request_headers = 7;
}
message Filter {
oneof kind {
io.linkerd.proxy.http_route.HttpFailureInjector failure_injector = 1;
io.linkerd.proxy.http_route.RequestHeaderModifier request_header_modifier = 2;
io.linkerd.proxy.http_route.RequestRedirect redirect = 3;
io.linkerd.proxy.http_route.ResponseHeaderModifier response_header_modifier = 4;
}
}
message Distribution {
oneof kind {
Empty empty = 1;
// Use the first available backend in the list.
FirstAvailable first_available = 2;
RandomAvailable random_available = 3;
}
message Empty {}
message FirstAvailable {
repeated RouteBackend backends = 1;
}
message RandomAvailable {
repeated WeightedRouteBackend backends = 1;
}
}
message Retry {
uint32 max_retries = 1;
uint32 max_request_bytes = 2;
// Must be set, even if there are no internal conditions.
Conditions conditions = 3;
google.protobuf.Duration timeout = 4;
ExponentialBackoff backoff = 5;
// Retryable conditions.
message Conditions {
// Specifies the status code ranges that should trigger a retry.
repeated StatusRange status_ranges = 1;
message StatusRange {
uint32 start = 1;
uint32 end = 2;
}
}
}
message RouteBackend {
Backend backend = 1;
repeated Filter filters = 3;
// DEPRECATED: proxies ignore this. Use Retry timeouts instead.
google.protobuf.Duration requestTimeout = 4 [deprecated = true];
}
message WeightedRouteBackend {
RouteBackend backend = 1;
uint32 weight = 2;
}
}
message GrpcRoute {
io.linkerd.proxy.meta.Metadata metadata = 1;
// If empty, the host value is ignored.
repeated io.linkerd.proxy.http_route.HostMatch hosts = 2;
// Must have at least one rule.
repeated Rule rules = 3;
message Rule {
repeated io.linkerd.proxy.grpc_route.GrpcRouteMatch matches = 1;
repeated Filter filters = 2;
Distribution backends = 3;
// DEPRECATED: use `timeouts` instead. Servers should continue to set this
// value to the same value as `timeouts.response`.
google.protobuf.Duration requestTimeout = 4 [deprecated = true];
io.linkerd.proxy.http_route.Timeouts timeouts = 5;
Retry retry = 6;
// If true, the proxy will allow headers to control retry and timeout
// behavior.
bool allow_l5d_request_headers = 7;
}
message Filter {
oneof kind {
io.linkerd.proxy.grpc_route.GrpcFailureInjector failure_injector = 1;
io.linkerd.proxy.http_route.RequestHeaderModifier request_header_modifier = 2;
}
}
message Distribution {
oneof kind {
Empty empty = 1;
// Use the first available backend in the list.
FirstAvailable first_available = 2;
RandomAvailable random_available = 3;
}
message Empty {}
message FirstAvailable {
repeated RouteBackend backends = 1;
}
message RandomAvailable {
repeated WeightedRouteBackend backends = 1;
}
}
message Retry {
uint32 max_retries = 1;
uint32 max_request_bytes = 2;
// Must be set, even if there are no internal conditions.
Conditions conditions = 3;
google.protobuf.Duration timeout = 4;
ExponentialBackoff backoff = 5;
// Retryable gRPC status codes.
message Conditions {
bool cancelled = 1;
bool deadine_exceeded = 4;
bool resource_exhausted = 8;
bool internal = 13;
bool unavailable = 14;
}
}
message RouteBackend {
Backend backend = 1;
repeated Filter filters = 3;
// DEPRECATED: proxies ignore this. Retry timeouts are used instead of this.
google.protobuf.Duration requestTimeout = 4 [deprecated = true];
}
message WeightedRouteBackend {
RouteBackend backend = 1;
uint32 weight = 2;
}
}
message OpaqueRoute {
io.linkerd.proxy.meta.Metadata metadata = 1;
// Must have at least one rule.
repeated Rule rules = 3;
reserved 4;
message Rule {
Distribution backends = 1;
repeated Filter filters = 2;
}
message Filter {
oneof kind {
io.linkerd.proxy.opaque_route.Invalid invalid = 1;
io.linkerd.proxy.opaque_route.Forbidden forbidden = 2;
}
}
message Distribution {
oneof kind {
Empty empty = 1;
// Use the first available backend in the list.
FirstAvailable first_available = 2;
RandomAvailable random_available = 3;
}
message Empty {}
message FirstAvailable {
repeated RouteBackend backends = 1;
}
message RandomAvailable {
repeated WeightedRouteBackend backends = 1;
}
}
message RouteBackend {
Backend backend = 1;
reserved 2;
repeated Filter filters = 3;
}
message WeightedRouteBackend {
RouteBackend backend = 1;
uint32 weight = 2;
}
}
message TlsRoute {
io.linkerd.proxy.meta.Metadata metadata = 1;
// If empty, the SNI value is ignored.
repeated io.linkerd.proxy.tls_route.SniMatch snis = 2;
// Must have at least one rule.
repeated Rule rules = 3;
reserved 4;
message Rule {
Distribution backends = 1;
repeated Filter filters = 2;
}
message Filter {
oneof kind {
io.linkerd.proxy.opaque_route.Invalid invalid = 1;
io.linkerd.proxy.opaque_route.Forbidden forbidden = 2;
}
}
message Distribution {
oneof kind {
Empty empty = 1;
// Use the first available backend in the list.
FirstAvailable first_available = 2;
RandomAvailable random_available = 3;
}
message Empty {}
message FirstAvailable {
repeated RouteBackend backends = 1;
}
message RandomAvailable {
repeated WeightedRouteBackend backends = 1;
}
}
message RouteBackend {
Backend backend = 1;
reserved 2;
repeated Filter filters = 3;
}
message WeightedRouteBackend {
RouteBackend backend = 1;
uint32 weight = 2;
}
}
message Backend {
io.linkerd.proxy.meta.Metadata metadata = 1;
oneof kind {
// A backend that consists of a single endpoint.
io.linkerd.proxy.destination.WeightedAddr forward = 2;
// A backend that comprises a load balanced service.
BalanceP2c balancer = 3;
}
// Describes queue configuration for a backend.
Queue queue = 4;
// A strategy for discovering endpoints for a service.
message EndpointDiscovery {
oneof kind {
// Use the `Destination` service to discover endpoints for this service.
DestinationGet dst = 1;
}
message DestinationGet {
string path = 1;
}
}
// Describes a power-of-two-choices (P2C) load balancer configuration for a
// backend.
message BalanceP2c {
EndpointDiscovery discovery = 1;
// The load estimation strategy used by this load balancer.
oneof load {
// This load balancer uses peak EWMA (exponentially weighted moving
// average) load estimates.
PeakEwma peak_ewma = 2;
}
// Ejection protection for the pool. When set, prevents circuit
// breakers from ejecting endpoints below the configured floor.
EjectionConfig ejection = 3;
// Parameters configuring peak EWMA load estimation.
message PeakEwma {
// Initial latency value used when no latencies have been
// recorded for an endpoint.
google.protobuf.Duration default_rtt = 1;
// The duration of the moving window over which latency is observed.
google.protobuf.Duration decay = 2;
}
}
}
message Queue {
// The number of requests that may be held in a queue before backpressure is
// exerted.
uint32 capacity = 1;
// A timeout that limits how long a backend may remain unready before any
// requests in its queue are failed.
google.protobuf.Duration failfast_timeout = 2;
}
// Configures failure accrual policies for circuit breaking.
// Setting a numeric policy field to zero disables that policy.
message FailureAccrual {
message ConsecutiveFailures {
// Maximum consecutive failures before the circuit trips.
// Set to 0 to disable (an unset field has the same effect).
uint32 max_failures = 1;
// Must be set. Controls the ejection duration before probe requests
// are allowed after any policy (not just CF) trips the circuit.
ExponentialBackoff backoff = 2;
}
message SuccessRate {
// Success rate threshold in [0.0, 1.0]. The circuit trips when the
// EWMA success rate drops below this value. Set to 0.0 to disable.
// Defined as `double` (not `float`) because this value is compared
// against the proxy's f64 EWMA on every request.
double threshold = 1;
// EWMA decay window for success rate tracking.
google.protobuf.Duration decay = 2;
// Minimum requests before the success rate policy can trip (cold-start guard).
uint32 min_requests = 3;
}
// Must be set. Set `max_failures` to 0 to disable the consecutive
// failure policy while retaining the shared backoff.
ConsecutiveFailures consecutive_failures = 1;
// Success rate policy. If set, the circuit trips when the EWMA success
// rate drops below the threshold.
SuccessRate success_rate = 2;
}
// Configures load biasing for 429-aware load balancing.
// When enabled, the load balancer injects artificial penalties on
// rate-limited endpoints, causing P2C to prefer other endpoints.
message LoadBiasConfig {
// Whether load biasing is enabled. When the message is present
// but `enabled` is false, biasing is inactive.
bool enabled = 1;
// The penalty duration to inject when a 429 response is received.
google.protobuf.Duration penalty = 2;
// The EWMA decay window for the penalty.
google.protobuf.Duration penalty_decay = 3;
}
// Configures handling of rate-limiting hints: Retry-After headers
// (HTTP 429/503) and grpc-retry-pushback-ms trailers (gRPC
// RESOURCE_EXHAUSTED). The proxy uses the parsed duration to extend
// circuit breaker backoff and amplify load biaser penalties.
message RetryAfterConfig {
// Maximum duration the proxy will honor from either hint source.
// Values exceeding this cap are clamped. When absent, the proxy
// uses DEFAULT_RETRY_AFTER_MAX_DURATION as the cap.
google.protobuf.Duration max_duration = 1;
}
// Pool-level ejection protection. Prevents circuit breakers from
// ejecting all endpoints in a load-balancing pool.
//
// Additional ejection parameters may follow in future fields.
message EjectionConfig {
// Minimum number of endpoints that must remain unejected.
// When set to 0 (or absent), ejection protection is disabled.
uint32 min_ready_endpoints = 1;
}
message ExponentialBackoff {
// The minimum amount of time to wait before resuming an operation.
google.protobuf.Duration min_backoff = 1;
// The maximum amount of time to wait before resuming an operation.
// Must be greater than or equal to min_backoff.
google.protobuf.Duration max_backoff = 2;
// The ratio of the base timeout that may be randomly added to a backoff.
// Must be greater than or equal to 0.0.
float jitter_ratio = 3;
}