envoyproxy / envoyproxy/gateway
Feature Request: Allow per-HTTPRoute opt-out of gateway-level `rateLimit` via merged `BackendTrafficPolicy`
- Dominant language
- Go
- Stars
- 3k
- Forks
- 864
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 140
Description
## Summary
When a `BackendTrafficPolicy` targeting a `Gateway` includes a `rateLimit` block, there is currently no supported way to disable or override that rate limit for a single `HTTPRoute` using a merged child `BackendTrafficPolicy`, even when `mergeType: JSONMerge` is used.
## Environment
- Envoy Gateway: `v1.8.0` (`docker.io/envoyproxy/gateway:v1.8.0`)
- API: `gateway.envoyproxy.io/v1alpha1`
## Steps to Reproduce
1. Apply a gateway-level `BackendTrafficPolicy` with `rateLimit.type: Local` and a rule (e.g. 100 req/s per IP).
2. Apply a route-scoped `BackendTrafficPolicy` targeting an `HTTPRoute`, with `mergeType: JSONMerge` and `rateLimit.local.rules: []`, intending to disable the inherited rate limit.
3. Inspect xDS via `egctl config envoy-proxy route ... -o json`.
## Observed Behavior
The route-scoped override is accepted and merged (`status.ancestors[].reason: Merged`), but the rendered Envoy xDS still contains the full local rate limit config from the parent:
```json
"typedPerFilterConfig": {
"envoy.filters.http.local_ratelimit": {
"filterEnabled": { "defaultValue": { "numerator": 100 } },
"filterEnforced": { "defaultValue": { "numerator": 100 } },
"descriptors": [{ "tokenBucket": { "maxTokens": 100, "tokensPerFill": 100, "fillInterval": "1s" } }],
"rateLimits": [{ "actions": [...] }]
}
}
```
An empty `rules: []` is treated as "no additional rules", not as "disable". `rateLimit: null` is rejected by the CRD schema (field is not nullable).
Setting a high `limit.requests` in the child rule only affects the top-level `tokenBucket.maxTokens`, **not** the descriptor-level `tokenBucket` (`descriptors[].tokenBucket`), which remains at the parent value and is the one actually enforced by Envoy when `rateLimits` actions generate descriptors.
## Root Cause
The `BackendTrafficPolicy` CRD (`spec.rateLimit.local`) exposes only `rules[]` — it has no `filterEnabled`, `filterEnforced`, or any explicit "disable" field:
```
$ kubectl explain backendtrafficpolicy.spec.rateLimit.local --recursive
FIELDS:
rules <[]Object>
clientSelectors, limit, shadowMode, shared, xRateLimitHeaders
(no filterEnabled / filterEnforced / enabled / disabled field)
```
There is therefore no API-level way to express "do not rate limit this route" in a child policy.
## Expected Behavior
At minimum one of:
1. **(Preferred) Mark `spec.rateLimit` as `nullable: true` in the CRD OpenAPI schema.** This is the most idiomatic Kubernetes solution: a child `BackendTrafficPolicy` with `mergeType: StrategicMerge` and `rateLimit: null` would act as a standard strategic merge deletion marker, cleanly suppressing the inherited rate limit for that route. Currently `rateLimit: null` is rejected by the API server because the field is not marked nullable.
2. **An explicit disable/opt-out field** on `spec.rateLimit` or `spec.rateLimit.local` (e.g. `disabled: true`) that causes EG to omit the `envoy.filters.http.local_ratelimit` filter for that route.
3. **Respect `rules: []` as "no rate limit"** — an explicitly empty rules list in a child policy (with `mergeType: JSONMerge`) should suppress the inherited rate limit, not silently fall back to the parent's config.
4. **Expose `filterEnabled`/`filterEnforced` fraction fields** in the CRD, so a child policy can set `numerator: 0` to disable enforcement per route.
## Workarounds (unsatisfactory)
- Move the route to a separate `Gateway`/listener with a different policy attachment (operational overhead).
- Move rate limiting from the gateway-level policy to per-route policies for every other route (high maintenance burden, error-prone).
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.