From 428eaa0fc427d860db5961a0f73b3cd03bb61f94 Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Sun, 30 Nov 2025 23:34:08 +0900 Subject: [PATCH 1/6] feat: support ratelimit shadow mode Signed-off-by: kkk777-7 --- api/v1alpha1/ratelimit_types.go | 9 ++++ api/v1alpha1/zz_generated.deepcopy.go | 5 ++ internal/gatewayapi/backendtrafficpolicy.go | 1 + ...ackendtrafficpolicy-with-ratelimit.in.yaml | 1 + ...ckendtrafficpolicy-with-ratelimit.out.yaml | 2 + internal/ir/xds.go | 5 ++ internal/ir/zz_generated.deepcopy.go | 5 ++ internal/xds/translator/ratelimit.go | 11 +++++ .../ratelimit-config/global-shadow-mode.yaml | 41 ++++++++++++++++ .../global-shadow-mode.routes.yaml | 47 +++++++++++++++++++ .../ratelimit-config/global-shadow-mode.yaml | 38 +++++++++++++++ site/content/en/latest/api/extension_types.md | 1 + 12 files changed, 166 insertions(+) create mode 100644 internal/xds/translator/testdata/in/ratelimit-config/global-shadow-mode.yaml create mode 100644 internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.routes.yaml create mode 100644 internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.yaml diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go index f7c2fc8ca0e..f2dfd30f8e3 100644 --- a/api/v1alpha1/ratelimit_types.go +++ b/api/v1alpha1/ratelimit_types.go @@ -68,6 +68,7 @@ type LocalRateLimit struct { // +optional // +kubebuilder:validation:MaxItems=16 // +kubebuilder:validation:XValidation:rule="self.all(foo, !has(foo.cost) || !has(foo.cost.response))", message="response cost is not supported for Local Rate Limits" + // +kubebuilder:validation:XValidation:rule="self.all(foo, !has(foo.shadowMode))", message="shadow mode is not supported for Local Rate Limits" Rules []RateLimitRule `json:"rules"` } @@ -110,6 +111,14 @@ type RateLimitRule struct { // // +optional Shared *bool `json:"shared,omitempty"` + // ShadowMode determines whether this rate limit rule enables shadow mode. + // When enabled, rate limiting functions execute as normal (cache lookup, statistics), + // but the result is always success regardless of whether the limit was exceeded. + // + // Only supported for Global Rate Limits. + // + // +optional + ShadowMode *bool `json:"shadowMode,omitempty"` } type RateLimitCost struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index d5661561773..1fc1599d7cc 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -6120,6 +6120,11 @@ func (in *RateLimitRule) DeepCopyInto(out *RateLimitRule) { *out = new(bool) **out = **in } + if in.ShadowMode != nil { + in, out := &in.ShadowMode, &out.ShadowMode + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitRule. diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 2dfef92eeaa..2efe7e1d92d 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -1213,6 +1213,7 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) { HeaderMatches: make([]*ir.StringMatch, 0), MethodMatches: make([]*ir.StringMatch, 0), Shared: rule.Shared, + ShadowMode: rule.ShadowMode, } for _, match := range rule.ClientSelectors { diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml index ca70dbd1179..5e6a542b23d 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.in.yaml @@ -177,3 +177,4 @@ backendTrafficPolicies: limit: requests: 30 unit: Hour + shadowMode: true diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml index b261a0dfd95..fccf671f4f2 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml @@ -66,6 +66,7 @@ backendTrafficPolicies: limit: requests: 30 unit: Hour + shadowMode: true type: Global targetRef: group: gateway.networking.k8s.io @@ -693,6 +694,7 @@ xdsIR: distinct: false exact: /test name: "" + shadowMode: true readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index bd60f26505f..022f8dfc834 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -2293,6 +2293,11 @@ type RateLimitRule struct { // // +optional Shared *bool `json:"shared,omitempty" yaml:"shared,omitempty"` + // ShadowMode determines whether this rate limit rule enables shadow mode. + // When enabled, rate limiting functions execute as normal (cache lookup, statistics), + // but the result is always success regardless of whether the limit was exceeded. + // +optional + ShadowMode *bool `json:"shadowMode,omitempty" yaml:"shadowMode,omitempty"` // Name is a unique identifier for this rule, set as //rule/. Name string `json:"name,omitempty" yaml:"name,omitempty"` } diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index a7186f23f4e..f968bc15e9e 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -3133,6 +3133,11 @@ func (in *RateLimitRule) DeepCopyInto(out *RateLimitRule) { *out = new(bool) **out = **in } + if in.ShadowMode != nil { + in, out := &in.ShadowMode, &out.ShadowMode + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitRule. diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go index 907e67dc8c1..8993003ae8a 100644 --- a/internal/xds/translator/ratelimit.go +++ b/internal/xds/translator/ratelimit.go @@ -665,6 +665,11 @@ func isRuleShared(rule *ir.RateLimitRule) bool { return rule != nil && rule.Shared != nil && *rule.Shared } +// Helper function to check if a specific rule is in shadow mode +func isRuleShadowMode(rule *ir.RateLimitRule) bool { + return rule != nil && rule.ShadowMode != nil && *rule.ShadowMode +} + // Helper function to map a global rule index to a domain-specific rule index // This ensures that both shared and non-shared rules have indices starting from 0 in their own domains. func getDomainRuleIndex(rules []*ir.RateLimitRule, globalRuleIdx int, ruleIsShared bool) int { @@ -718,6 +723,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi // 1) Header Matches for mIdx, match := range rule.HeaderMatches { pbDesc := new(rlsconfv3.RateLimitDescriptor) + pbDesc.ShadowMode = isRuleShadowMode(rule) // Distinct vs HeaderValueMatch if match.Distinct { // RequestHeader case @@ -742,6 +748,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi // 2) Method Match if len(rule.MethodMatches) > 0 { pbDesc := new(rlsconfv3.RateLimitDescriptor) + pbDesc.ShadowMode = isRuleShadowMode(rule) pbDesc.Key = getRouteRuleMethodDescriptor(domainRuleIdx) pbDesc.Value = getRouteRuleMethodDescriptor(domainRuleIdx) @@ -761,6 +768,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi // 3) Path Match if rule.PathMatch != nil { pbDesc := new(rlsconfv3.RateLimitDescriptor) + pbDesc.ShadowMode = isRuleShadowMode(rule) pbDesc.Key = getRouteRulePathDescriptor(domainRuleIdx) pbDesc.Value = getRouteRulePathDescriptor(domainRuleIdx) @@ -802,6 +810,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi if rule.CIDRMatch != nil { // MaskedRemoteAddress case pbDesc := new(rlsconfv3.RateLimitDescriptor) + pbDesc.ShadowMode = isRuleShadowMode(rule) pbDesc.Key = "masked_remote_address" pbDesc.Value = rule.CIDRMatch.CIDR @@ -816,6 +825,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi if rule.CIDRMatch.Distinct { pbDesc := new(rlsconfv3.RateLimitDescriptor) + pbDesc.ShadowMode = isRuleShadowMode(rule) pbDesc.Key = "remote_address" cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc} cur = pbDesc @@ -826,6 +836,7 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi // 3) No Match (apply to all traffic) if !rule.IsMatchSet() { pbDesc := new(rlsconfv3.RateLimitDescriptor) + pbDesc.ShadowMode = isRuleShadowMode(rule) pbDesc.Key = getRouteRuleDescriptor(domainRuleIdx, -1) pbDesc.Value = getRouteRuleDescriptor(domainRuleIdx, -1) head = pbDesc diff --git a/internal/xds/translator/testdata/in/ratelimit-config/global-shadow-mode.yaml b/internal/xds/translator/testdata/in/ratelimit-config/global-shadow-mode.yaml new file mode 100644 index 00000000000..50d9020f4ed --- /dev/null +++ b/internal/xds/translator/testdata/in/ratelimit-config/global-shadow-mode.yaml @@ -0,0 +1,41 @@ +http: +- name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + routes: + - name: "first-route" + traffic: + rateLimit: + global: + rules: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: + - name: "x-user-id" + exact: "one" + - name: "x-user-id" + exact: "two" + - name: "x-org-id" + exact: "three" + cidrMatch: + cidr: 0.0.0.0/0 + ip: 0.0.0.0 + maskLen: 0 + isIPv6: false + distinct: false + limit: + requests: 5 + unit: second + shadowMode: true + pathMatch: + exact: "foo/bar" + destination: + name: "first-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 diff --git a/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.routes.yaml b/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.routes.yaml new file mode 100644 index 00000000000..1b3b97eee1d --- /dev/null +++ b/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.routes.yaml @@ -0,0 +1,47 @@ +costSpecified: false +rateLimits: +- actions: + - ActionSpecifier: + GenericKey: + descriptor_key: first-route + descriptor_value: first-route + - ActionSpecifier: + HeaderValueMatch: + descriptor_key: rule-0-match-0 + descriptor_value: rule-0-match-0 + expect_match: + value: true + headers: + - HeaderMatchSpecifier: + StringMatch: + MatchPattern: + Exact: one + name: x-user-id + - ActionSpecifier: + HeaderValueMatch: + descriptor_key: rule-0-match-1 + descriptor_value: rule-0-match-1 + expect_match: + value: true + headers: + - HeaderMatchSpecifier: + StringMatch: + MatchPattern: + Exact: two + name: x-user-id + - ActionSpecifier: + HeaderValueMatch: + descriptor_key: rule-0-match-2 + descriptor_value: rule-0-match-2 + expect_match: + value: true + headers: + - HeaderMatchSpecifier: + StringMatch: + MatchPattern: + Exact: three + name: x-org-id + - ActionSpecifier: + MaskedRemoteAddress: + v4_prefix_mask_len: {} +routeName: first-route diff --git a/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.yaml b/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.yaml new file mode 100644 index 00000000000..53987c2f525 --- /dev/null +++ b/internal/xds/translator/testdata/out/ratelimit-config/global-shadow-mode.yaml @@ -0,0 +1,38 @@ +name: first-listener +domain: first-listener +descriptors: + - key: first-route + value: first-route + rate_limit: null + descriptors: + - key: rule-0-match-0 + value: rule-0-match-0 + rate_limit: null + descriptors: + - key: rule-0-match-1 + value: rule-0-match-1 + rate_limit: null + descriptors: + - key: rule-0-match-2 + value: rule-0-match-2 + rate_limit: null + descriptors: + - key: masked_remote_address + value: 0.0.0.0/0 + rate_limit: + requests_per_unit: 5 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: true + detailed_metric: false + shadow_mode: true + detailed_metric: false + shadow_mode: true + detailed_metric: false + shadow_mode: true + detailed_metric: false + shadow_mode: false + detailed_metric: false diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 5b47c218d71..be93ec77598 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -4335,6 +4335,7 @@ _Appears in:_ | `limit` | _[RateLimitValue](#ratelimitvalue)_ | true | | Limit holds the rate limit values.
This limit is applied for traffic flows when the selectors
compute to True, causing the request to be counted towards the limit.
The limit is enforced and the request is ratelimited, i.e. a response with
429 HTTP status code is sent back to the client when
the selected requests have reached the limit. | | `cost` | _[RateLimitCost](#ratelimitcost)_ | false | | Cost specifies the cost of requests and responses for the rule.
This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on
the request path and do not reduce the rate limit counters on the response path. | | `shared` | _boolean_ | false | | Shared determines whether this rate limit rule applies across all the policy targets.
If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes).
Default: false. | +| `shadowMode` | _boolean_ | false | | ShadowMode determines whether this rate limit rule enables shadow mode.
When enabled, rate limiting functions execute as normal (cache lookup, statistics),
but the result is always success regardless of whether the limit was exceeded.
Only supported for Global Rate Limits. | #### RateLimitSelectCondition From c6740a4ece201dab4bfe92813532126581cca0d6 Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Mon, 1 Dec 2025 00:55:12 +0900 Subject: [PATCH 2/6] update helm charts Signed-off-by: kkk777-7 --- ...y.envoyproxy.io_backendtrafficpolicies.yaml | 18 ++++++++++++++++++ ...y.envoyproxy.io_backendtrafficpolicies.yaml | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 8ad379ea307..d277cc143fb 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -1235,6 +1235,14 @@ spec: - requests - unit type: object + shadowMode: + description: |- + ShadowMode determines whether this rate limit rule enables shadow mode. + When enabled, rate limiting functions execute as normal (cache lookup, statistics), + but the result is always success regardless of whether the limit was exceeded. + + Only supported for Global Rate Limits. + type: boolean shared: description: |- Shared determines whether this rate limit rule applies across all the policy targets. @@ -1547,6 +1555,14 @@ spec: - requests - unit type: object + shadowMode: + description: |- + ShadowMode determines whether this rate limit rule enables shadow mode. + When enabled, rate limiting functions execute as normal (cache lookup, statistics), + but the result is always success regardless of whether the limit was exceeded. + + Only supported for Global Rate Limits. + type: boolean shared: description: |- Shared determines whether this rate limit rule applies across all the policy targets. @@ -1561,6 +1577,8 @@ spec: x-kubernetes-validations: - message: response cost is not supported for Local Rate Limits rule: self.all(foo, !has(foo.cost) || !has(foo.cost.response)) + - message: shadow mode is not supported for Local Rate Limits + rule: self.all(foo, !has(foo.shadowMode)) type: object type: description: |- diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 5944352f6f8..1d5d92e0026 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -1234,6 +1234,14 @@ spec: - requests - unit type: object + shadowMode: + description: |- + ShadowMode determines whether this rate limit rule enables shadow mode. + When enabled, rate limiting functions execute as normal (cache lookup, statistics), + but the result is always success regardless of whether the limit was exceeded. + + Only supported for Global Rate Limits. + type: boolean shared: description: |- Shared determines whether this rate limit rule applies across all the policy targets. @@ -1546,6 +1554,14 @@ spec: - requests - unit type: object + shadowMode: + description: |- + ShadowMode determines whether this rate limit rule enables shadow mode. + When enabled, rate limiting functions execute as normal (cache lookup, statistics), + but the result is always success regardless of whether the limit was exceeded. + + Only supported for Global Rate Limits. + type: boolean shared: description: |- Shared determines whether this rate limit rule applies across all the policy targets. @@ -1560,6 +1576,8 @@ spec: x-kubernetes-validations: - message: response cost is not supported for Local Rate Limits rule: self.all(foo, !has(foo.cost) || !has(foo.cost.response)) + - message: shadow mode is not supported for Local Rate Limits + rule: self.all(foo, !has(foo.shadowMode)) type: object type: description: |- From e11c385dc7440a3ea37d3eb0d92bc0e4d8b9430d Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Mon, 1 Dec 2025 00:55:31 +0900 Subject: [PATCH 3/6] add shadow mode e2e Signed-off-by: kkk777-7 --- .../ratelimit-global-shadow-mode.yaml | 38 +++++++++++++++ test/e2e/tests/ratelimit.go | 46 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 test/e2e/testdata/ratelimit-global-shadow-mode.yaml diff --git a/test/e2e/testdata/ratelimit-global-shadow-mode.yaml b/test/e2e/testdata/ratelimit-global-shadow-mode.yaml new file mode 100644 index 00000000000..d012beb0b41 --- /dev/null +++ b/test/e2e/testdata/ratelimit-global-shadow-mode.yaml @@ -0,0 +1,38 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: shadow-mode-ratelimit + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: shadow-mode-ratelimit + rateLimit: + type: Global + global: + rules: + - clientSelectors: + - methods: + - value: "GET" + limit: + requests: 3 + unit: Hour + shadowMode: true +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: shadow-mode-ratelimit + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /shadow + backendRefs: + - name: infra-backend-v1 + port: 8080 diff --git a/test/e2e/tests/ratelimit.go b/test/e2e/tests/ratelimit.go index 12583031f34..be4ebb45855 100644 --- a/test/e2e/tests/ratelimit.go +++ b/test/e2e/tests/ratelimit.go @@ -44,6 +44,7 @@ func init() { RateLimitGlobalSharedCidrMatchTest, RateLimitGlobalSharedGatewayHeaderMatchTest, RateLimitGlobalMergeTest, + RateLimitGlobalShadowModeTest, ) } @@ -1279,6 +1280,51 @@ var RateLimitGlobalMergeTest = suite.ConformanceTest{ }, } +var RateLimitGlobalShadowModeTest = suite.ConformanceTest{ + ShortName: "RateLimitGlobalShadowModeTest", + Description: "Limit requests with shadow mode enabled, verifying that requests are not actually limited", + Manifests: []string{"testdata/ratelimit-global-shadow-mode.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + shadowRouteNN := types.NamespacedName{Name: "shadow-mode-ratelimit", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gwapiv1.HTTPRoute{}, false, shadowRouteNN) + + t.Run("matched with shadow mode can't got limited", func(t *testing.T) { + ratelimitHeader := make(map[string]string) + + expectOkResp := http.ExpectedResponse{ + Request: http.Request{ + Path: "/shadow", + Method: "GET", + }, + Response: http.Response{ + StatusCode: 200, + Headers: ratelimitHeader, + }, + Namespace: ns, + } + expectOkResp.Response.Headers["X-Ratelimit-Limit"] = "3, 3;w=3600" + expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") + + // In shadow mode, requests are never blocked even when exceeding the rate limit. + // The rate limit is set to 3 requests per window, but we send 5 total requests. + // Shadow mode ensures all requests return 200 (never 429) + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp) + + // fire the next 2 requests (total: 3 requests, reaching the limit) + if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { + t.Errorf("failed to get expected response for the first three requests: %v", err) + } + + // send 2 more requests that exceed the limit, but still expect 200 (not 429) due to shadow mode + if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { + t.Errorf("failed to get expected response for requests exceeding the limit: %v", err) + } + }) + }, +} + // GotExactExpectedResponse consumes a request value; keeping value semantics avoids threading pointers through many tests. // //nolint:gocritic From 28be768f4ac3da1bd0d772a6dc0434e3d1cc230b Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Mon, 1 Dec 2025 00:56:37 +0900 Subject: [PATCH 4/6] update release note Signed-off-by: kkk777-7 --- release-notes/current.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 792358d9593..22f33ea2841 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -10,6 +10,7 @@ security updates: | # New features or capabilities added in this release. new features: | Added support for weight in BackendRef API to enable traffic splitting for non-x-route resources. + Added support for Global rate limit shadow mode. bug fixes: | Fixed configured OIDC authorization endpoint being overridden by discovered endpoints from issuer's well-known URL. From be11b3ba22e4482ba6990929b52899f5bc92ee18 Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Mon, 1 Dec 2025 01:13:00 +0900 Subject: [PATCH 5/6] update test helm files Signed-off-by: kkk777-7 --- test/helm/gateway-crds-helm/all.out.yaml | 18 ++++++++++++++++++ .../envoy-gateway-crds.out.yaml | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 18d794c24a3..452664f0da5 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -22368,6 +22368,14 @@ spec: - requests - unit type: object + shadowMode: + description: |- + ShadowMode determines whether this rate limit rule enables shadow mode. + When enabled, rate limiting functions execute as normal (cache lookup, statistics), + but the result is always success regardless of whether the limit was exceeded. + + Only supported for Global Rate Limits. + type: boolean shared: description: |- Shared determines whether this rate limit rule applies across all the policy targets. @@ -22680,6 +22688,14 @@ spec: - requests - unit type: object + shadowMode: + description: |- + ShadowMode determines whether this rate limit rule enables shadow mode. + When enabled, rate limiting functions execute as normal (cache lookup, statistics), + but the result is always success regardless of whether the limit was exceeded. + + Only supported for Global Rate Limits. + type: boolean shared: description: |- Shared determines whether this rate limit rule applies across all the policy targets. @@ -22694,6 +22710,8 @@ spec: x-kubernetes-validations: - message: response cost is not supported for Local Rate Limits rule: self.all(foo, !has(foo.cost) || !has(foo.cost.response)) + - message: shadow mode is not supported for Local Rate Limits + rule: self.all(foo, !has(foo.shadowMode)) type: object type: description: |- diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index e4a2010020e..89f8ed85f47 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -1712,6 +1712,14 @@ spec: - requests - unit type: object + shadowMode: + description: |- + ShadowMode determines whether this rate limit rule enables shadow mode. + When enabled, rate limiting functions execute as normal (cache lookup, statistics), + but the result is always success regardless of whether the limit was exceeded. + + Only supported for Global Rate Limits. + type: boolean shared: description: |- Shared determines whether this rate limit rule applies across all the policy targets. @@ -2024,6 +2032,14 @@ spec: - requests - unit type: object + shadowMode: + description: |- + ShadowMode determines whether this rate limit rule enables shadow mode. + When enabled, rate limiting functions execute as normal (cache lookup, statistics), + but the result is always success regardless of whether the limit was exceeded. + + Only supported for Global Rate Limits. + type: boolean shared: description: |- Shared determines whether this rate limit rule applies across all the policy targets. @@ -2038,6 +2054,8 @@ spec: x-kubernetes-validations: - message: response cost is not supported for Local Rate Limits rule: self.all(foo, !has(foo.cost) || !has(foo.cost.response)) + - message: shadow mode is not supported for Local Rate Limits + rule: self.all(foo, !has(foo.shadowMode)) type: object type: description: |- From c7ccdfa57bef41d0159fa1039981d664e755fd5c Mon Sep 17 00:00:00 2001 From: kkk777-7 Date: Sun, 7 Dec 2025 15:12:21 +0900 Subject: [PATCH 6/6] update crd comment Signed-off-by: kkk777-7 --- api/v1alpha1/ratelimit_types.go | 7 ++++--- ...teway.envoyproxy.io_backendtrafficpolicies.yaml | 14 ++++++++------ ...teway.envoyproxy.io_backendtrafficpolicies.yaml | 14 ++++++++------ site/content/en/latest/api/extension_types.md | 2 +- test/helm/gateway-crds-helm/all.out.yaml | 14 ++++++++------ .../gateway-crds-helm/envoy-gateway-crds.out.yaml | 14 ++++++++------ 6 files changed, 37 insertions(+), 28 deletions(-) diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go index f2dfd30f8e3..4baefa9ba02 100644 --- a/api/v1alpha1/ratelimit_types.go +++ b/api/v1alpha1/ratelimit_types.go @@ -111,9 +111,10 @@ type RateLimitRule struct { // // +optional Shared *bool `json:"shared,omitempty"` - // ShadowMode determines whether this rate limit rule enables shadow mode. - // When enabled, rate limiting functions execute as normal (cache lookup, statistics), - // but the result is always success regardless of whether the limit was exceeded. + // ShadowMode indicates whether this rate-limit rule runs in shadow mode. + // When enabled, all rate-limiting operations are performed (cache lookups, + // counter updates, telemetry generation), but the outcome is never enforced. + // The request always succeeds, even if the configured limit is exceeded. // // Only supported for Global Rate Limits. // diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index d277cc143fb..48730d750ee 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -1237,9 +1237,10 @@ spec: type: object shadowMode: description: |- - ShadowMode determines whether this rate limit rule enables shadow mode. - When enabled, rate limiting functions execute as normal (cache lookup, statistics), - but the result is always success regardless of whether the limit was exceeded. + ShadowMode indicates whether this rate-limit rule runs in shadow mode. + When enabled, all rate-limiting operations are performed (cache lookups, + counter updates, telemetry generation), but the outcome is never enforced. + The request always succeeds, even if the configured limit is exceeded. Only supported for Global Rate Limits. type: boolean @@ -1557,9 +1558,10 @@ spec: type: object shadowMode: description: |- - ShadowMode determines whether this rate limit rule enables shadow mode. - When enabled, rate limiting functions execute as normal (cache lookup, statistics), - but the result is always success regardless of whether the limit was exceeded. + ShadowMode indicates whether this rate-limit rule runs in shadow mode. + When enabled, all rate-limiting operations are performed (cache lookups, + counter updates, telemetry generation), but the outcome is never enforced. + The request always succeeds, even if the configured limit is exceeded. Only supported for Global Rate Limits. type: boolean diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 1d5d92e0026..c43c372cb9f 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -1236,9 +1236,10 @@ spec: type: object shadowMode: description: |- - ShadowMode determines whether this rate limit rule enables shadow mode. - When enabled, rate limiting functions execute as normal (cache lookup, statistics), - but the result is always success regardless of whether the limit was exceeded. + ShadowMode indicates whether this rate-limit rule runs in shadow mode. + When enabled, all rate-limiting operations are performed (cache lookups, + counter updates, telemetry generation), but the outcome is never enforced. + The request always succeeds, even if the configured limit is exceeded. Only supported for Global Rate Limits. type: boolean @@ -1556,9 +1557,10 @@ spec: type: object shadowMode: description: |- - ShadowMode determines whether this rate limit rule enables shadow mode. - When enabled, rate limiting functions execute as normal (cache lookup, statistics), - but the result is always success regardless of whether the limit was exceeded. + ShadowMode indicates whether this rate-limit rule runs in shadow mode. + When enabled, all rate-limiting operations are performed (cache lookups, + counter updates, telemetry generation), but the outcome is never enforced. + The request always succeeds, even if the configured limit is exceeded. Only supported for Global Rate Limits. type: boolean diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index be93ec77598..20b873c6dd8 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -4335,7 +4335,7 @@ _Appears in:_ | `limit` | _[RateLimitValue](#ratelimitvalue)_ | true | | Limit holds the rate limit values.
This limit is applied for traffic flows when the selectors
compute to True, causing the request to be counted towards the limit.
The limit is enforced and the request is ratelimited, i.e. a response with
429 HTTP status code is sent back to the client when
the selected requests have reached the limit. | | `cost` | _[RateLimitCost](#ratelimitcost)_ | false | | Cost specifies the cost of requests and responses for the rule.
This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on
the request path and do not reduce the rate limit counters on the response path. | | `shared` | _boolean_ | false | | Shared determines whether this rate limit rule applies across all the policy targets.
If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes).
Default: false. | -| `shadowMode` | _boolean_ | false | | ShadowMode determines whether this rate limit rule enables shadow mode.
When enabled, rate limiting functions execute as normal (cache lookup, statistics),
but the result is always success regardless of whether the limit was exceeded.
Only supported for Global Rate Limits. | +| `shadowMode` | _boolean_ | false | | ShadowMode indicates whether this rate-limit rule runs in shadow mode.
When enabled, all rate-limiting operations are performed (cache lookups,
counter updates, telemetry generation), but the outcome is never enforced.
The request always succeeds, even if the configured limit is exceeded.
Only supported for Global Rate Limits. | #### RateLimitSelectCondition diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 452664f0da5..0a5600959d5 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -22370,9 +22370,10 @@ spec: type: object shadowMode: description: |- - ShadowMode determines whether this rate limit rule enables shadow mode. - When enabled, rate limiting functions execute as normal (cache lookup, statistics), - but the result is always success regardless of whether the limit was exceeded. + ShadowMode indicates whether this rate-limit rule runs in shadow mode. + When enabled, all rate-limiting operations are performed (cache lookups, + counter updates, telemetry generation), but the outcome is never enforced. + The request always succeeds, even if the configured limit is exceeded. Only supported for Global Rate Limits. type: boolean @@ -22690,9 +22691,10 @@ spec: type: object shadowMode: description: |- - ShadowMode determines whether this rate limit rule enables shadow mode. - When enabled, rate limiting functions execute as normal (cache lookup, statistics), - but the result is always success regardless of whether the limit was exceeded. + ShadowMode indicates whether this rate-limit rule runs in shadow mode. + When enabled, all rate-limiting operations are performed (cache lookups, + counter updates, telemetry generation), but the outcome is never enforced. + The request always succeeds, even if the configured limit is exceeded. Only supported for Global Rate Limits. type: boolean diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index 89f8ed85f47..2ac6badd5b0 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -1714,9 +1714,10 @@ spec: type: object shadowMode: description: |- - ShadowMode determines whether this rate limit rule enables shadow mode. - When enabled, rate limiting functions execute as normal (cache lookup, statistics), - but the result is always success regardless of whether the limit was exceeded. + ShadowMode indicates whether this rate-limit rule runs in shadow mode. + When enabled, all rate-limiting operations are performed (cache lookups, + counter updates, telemetry generation), but the outcome is never enforced. + The request always succeeds, even if the configured limit is exceeded. Only supported for Global Rate Limits. type: boolean @@ -2034,9 +2035,10 @@ spec: type: object shadowMode: description: |- - ShadowMode determines whether this rate limit rule enables shadow mode. - When enabled, rate limiting functions execute as normal (cache lookup, statistics), - but the result is always success regardless of whether the limit was exceeded. + ShadowMode indicates whether this rate-limit rule runs in shadow mode. + When enabled, all rate-limiting operations are performed (cache lookups, + counter updates, telemetry generation), but the outcome is never enforced. + The request always succeeds, even if the configured limit is exceeded. Only supported for Global Rate Limits. type: boolean