feat(provider/k8s/ingress-nginx): add limit-burst-multiplier annotation support

This commit is contained in:
Kangmin Kim 2026-04-01 00:24:07 +09:00 committed by GitHub
parent ea92a3e150
commit ea7f300c85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 151 additions and 5 deletions

View File

@ -384,6 +384,7 @@ The following annotations are organized by category for easier navigation.
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |-----------------------------------------------------------------------------------------------------------|
| <a id="opt-nginx-ingress-kubernetes-iolimit-rps" href="#opt-nginx-ingress-kubernetes-iolimit-rps" title="#opt-nginx-ingress-kubernetes-iolimit-rps">`nginx.ingress.kubernetes.io/limit-rps`</a> | Exceeding the limit returns `429 Too Many Requests` instead of NGINX's default `503 Service Unavailable`. |
| <a id="opt-nginx-ingress-kubernetes-iolimit-rpm" href="#opt-nginx-ingress-kubernetes-iolimit-rpm" title="#opt-nginx-ingress-kubernetes-iolimit-rpm">`nginx.ingress.kubernetes.io/limit-rpm`</a> | Exceeding the limit returns `429 Too Many Requests` instead of NGINX's default `503 Service Unavailable`. |
| <a id="opt-nginx-ingress-kubernetes-iolimit-burst-multiplier" href="#opt-nginx-ingress-kubernetes-iolimit-burst-multiplier" title="#opt-nginx-ingress-kubernetes-iolimit-burst-multiplier">`nginx.ingress.kubernetes.io/limit-burst-multiplier`</a> | Default to a multiplier of 5 if the configured value is less than 1. Exceeding the limit returns `429 Too Many Requests` instead of NGINX's default `503 Service Unavailable`. |
### Buffering
@ -456,7 +457,6 @@ In practice, Traefik is slightly more lenient under bursty load, as it smooths o
| <a id="opt-nginx-ingress-kubernetes-iolimit-rate-after" href="#opt-nginx-ingress-kubernetes-iolimit-rate-after" title="#opt-nginx-ingress-kubernetes-iolimit-rate-after">`nginx.ingress.kubernetes.io/limit-rate-after`</a> | |
| <a id="opt-nginx-ingress-kubernetes-iolimit-rate" href="#opt-nginx-ingress-kubernetes-iolimit-rate" title="#opt-nginx-ingress-kubernetes-iolimit-rate">`nginx.ingress.kubernetes.io/limit-rate`</a> | |
| <a id="opt-nginx-ingress-kubernetes-iolimit-whitelist" href="#opt-nginx-ingress-kubernetes-iolimit-whitelist" title="#opt-nginx-ingress-kubernetes-iolimit-whitelist">`nginx.ingress.kubernetes.io/limit-whitelist`</a> | |
| <a id="opt-nginx-ingress-kubernetes-iolimit-burst-multiplier" href="#opt-nginx-ingress-kubernetes-iolimit-burst-multiplier" title="#opt-nginx-ingress-kubernetes-iolimit-burst-multiplier">`nginx.ingress.kubernetes.io/limit-burst-multiplier`</a> | |
| <a id="opt-nginx-ingress-kubernetes-iolimit-connections" href="#opt-nginx-ingress-kubernetes-iolimit-connections" title="#opt-nginx-ingress-kubernetes-iolimit-connections">`nginx.ingress.kubernetes.io/limit-connections`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioglobal-rate-limit" href="#opt-nginx-ingress-kubernetes-ioglobal-rate-limit" title="#opt-nginx-ingress-kubernetes-ioglobal-rate-limit">`nginx.ingress.kubernetes.io/global-rate-limit`</a> | |
| <a id="opt-nginx-ingress-kubernetes-ioglobal-rate-limit-window" href="#opt-nginx-ingress-kubernetes-ioglobal-rate-limit-window" title="#opt-nginx-ingress-kubernetes-ioglobal-rate-limit-window">`nginx.ingress.kubernetes.io/global-rate-limit-window`</a> | |

View File

@ -84,8 +84,9 @@ type IngressConfig struct {
WhitelistSourceRange *string `annotation:"nginx.ingress.kubernetes.io/whitelist-source-range"`
AllowlistSourceRange *string `annotation:"nginx.ingress.kubernetes.io/allowlist-source-range"`
LimitRPM *int `annotation:"nginx.ingress.kubernetes.io/limit-rpm"`
LimitRPS *int `annotation:"nginx.ingress.kubernetes.io/limit-rps"`
LimitRPM *int `annotation:"nginx.ingress.kubernetes.io/limit-rpm"`
LimitRPS *int `annotation:"nginx.ingress.kubernetes.io/limit-rps"`
LimitBurstMultiplier *int `annotation:"nginx.ingress.kubernetes.io/limit-burst-multiplier"`
CustomHeaders *string `annotation:"nginx.ingress.kubernetes.io/custom-headers"`
UpstreamVhost *string `annotation:"nginx.ingress.kubernetes.io/upstream-vhost"`

View File

@ -0,0 +1,43 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-limit-burst-multiplier
namespace: default
annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-burst-multiplier: "10"
spec:
ingressClassName: nginx
rules:
- host: whoami-burst.localhost
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: whoami
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-limit-burst-multiplier-zero
namespace: default
annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-burst-multiplier: "0"
spec:
ingressClassName: nginx
rules:
- host: whoami-burst-zero.localhost
http:
paths:
- path: /
pathType: Exact
backend:
service:
name: whoami
port:
number: 80

View File

@ -1454,6 +1454,16 @@ func (p *Provider) applyCustomHTTPErrors(namespace, ingressName, routerName stri
return nil
}
func getLimitBurstMultiplier(config IngressConfig) int64 {
multiplier := ptr.Deref(config.LimitBurstMultiplier, defaultLimitBurstMultiplier)
if multiplier < 1 {
multiplier = defaultLimitBurstMultiplier
}
return int64(multiplier)
}
func applyLimitRPMConfiguration(routerName string, ingressConfig IngressConfig, rt *dynamic.Router, conf *dynamic.Configuration) {
limitRPM := ptr.Deref(ingressConfig.LimitRPM, 0)
if limitRPM <= 0 {
@ -1465,7 +1475,7 @@ func applyLimitRPMConfiguration(routerName string, ingressConfig IngressConfig,
RateLimit: &dynamic.RateLimit{
Average: int64(limitRPM),
Period: ptypes.Duration(time.Minute),
Burst: int64(limitRPM) * defaultLimitBurstMultiplier,
Burst: int64(limitRPM) * getLimitBurstMultiplier(ingressConfig),
},
}
@ -1483,7 +1493,7 @@ func applyLimitRPSConfiguration(routerName string, ingressConfig IngressConfig,
RateLimit: &dynamic.RateLimit{
Average: int64(limitRPS),
Period: ptypes.Duration(time.Second),
Burst: int64(limitRPS) * defaultLimitBurstMultiplier,
Burst: int64(limitRPS) * getLimitBurstMultiplier(ingressConfig),
},
}

View File

@ -9640,6 +9640,98 @@ func TestLoadIngresses(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Limit Burst Multiplier",
paths: []string{
"services.yml",
"ingressclasses.yml",
"ingresses/ingress-with-limit-burst-multiplier.yml",
},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{Routers: map[string]*dynamic.TCPRouter{}, Services: map[string]*dynamic.TCPService{}},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-ingress-with-limit-burst-multiplier-rule-0-path-0": {
EntryPoints: []string{"http"},
Rule: `Host("whoami-burst.localhost") && Path("/")`,
RuleSyntax: "default",
Middlewares: []string{"default-ingress-with-limit-burst-multiplier-rule-0-path-0-limit-rps", "default-ingress-with-limit-burst-multiplier-rule-0-path-0-retry"},
Service: "default-ingress-with-limit-burst-multiplier-whoami-80",
},
"default-ingress-with-limit-burst-multiplier-rule-0-path-0-tls": {
EntryPoints: []string{"https"},
Rule: `Host("whoami-burst.localhost") && Path("/")`,
RuleSyntax: "default",
Middlewares: []string{"default-ingress-with-limit-burst-multiplier-rule-0-path-0-tls-limit-rps", "default-ingress-with-limit-burst-multiplier-rule-0-path-0-tls-retry"},
Service: "default-ingress-with-limit-burst-multiplier-whoami-80",
TLS: &dynamic.RouterTLSConfig{},
},
"default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0": {
EntryPoints: []string{"http"},
Rule: `Host("whoami-burst-zero.localhost") && Path("/")`,
RuleSyntax: "default",
Middlewares: []string{"default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-limit-rps", "default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-retry"},
Service: "default-ingress-with-limit-burst-multiplier-zero-whoami-80",
},
"default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-tls": {
EntryPoints: []string{"https"},
Rule: `Host("whoami-burst-zero.localhost") && Path("/")`,
RuleSyntax: "default",
Middlewares: []string{"default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-tls-limit-rps", "default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-tls-retry"},
Service: "default-ingress-with-limit-burst-multiplier-zero-whoami-80",
TLS: &dynamic.RouterTLSConfig{},
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-ingress-with-limit-burst-multiplier-rule-0-path-0-retry": {Retry: &dynamic.Retry{Attempts: 3}},
"default-ingress-with-limit-burst-multiplier-rule-0-path-0-tls-retry": {Retry: &dynamic.Retry{Attempts: 3}},
"default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-retry": {Retry: &dynamic.Retry{Attempts: 3}},
"default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-tls-retry": {Retry: &dynamic.Retry{Attempts: 3}},
"default-ingress-with-limit-burst-multiplier-rule-0-path-0-limit-rps": {
RateLimit: &dynamic.RateLimit{Average: 10, Burst: 100, Period: ptypes.Duration(time.Second)},
},
"default-ingress-with-limit-burst-multiplier-rule-0-path-0-tls-limit-rps": {
RateLimit: &dynamic.RateLimit{Average: 10, Burst: 100, Period: ptypes.Duration(time.Second)},
},
"default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-limit-rps": {
RateLimit: &dynamic.RateLimit{Average: 10, Burst: 50, Period: ptypes.Duration(time.Second)},
},
"default-ingress-with-limit-burst-multiplier-zero-rule-0-path-0-tls-limit-rps": {
RateLimit: &dynamic.RateLimit{Average: 10, Burst: 50, Period: ptypes.Duration(time.Second)},
},
},
Services: map[string]*dynamic.Service{
"default-ingress-with-limit-burst-multiplier-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{{URL: "http://10.10.0.1:80"}, {URL: "http://10.10.0.2:80"}},
Strategy: "wrr",
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
ServersTransport: "default-ingress-with-limit-burst-multiplier",
},
},
"default-ingress-with-limit-burst-multiplier-zero-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{{URL: "http://10.10.0.1:80"}, {URL: "http://10.10.0.2:80"}},
Strategy: "wrr",
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval},
ServersTransport: "default-ingress-with-limit-burst-multiplier-zero",
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{
"default-ingress-with-limit-burst-multiplier": {
ForwardingTimeouts: &dynamic.ForwardingTimeouts{DialTimeout: ptypes.Duration(60 * time.Second), ReadTimeout: ptypes.Duration(60 * time.Second), WriteTimeout: ptypes.Duration(60 * time.Second), IdleConnTimeout: ptypes.Duration(60 * time.Second)},
},
"default-ingress-with-limit-burst-multiplier-zero": {
ForwardingTimeouts: &dynamic.ForwardingTimeouts{DialTimeout: ptypes.Duration(60 * time.Second), ReadTimeout: ptypes.Duration(60 * time.Second), WriteTimeout: ptypes.Duration(60 * time.Second), IdleConnTimeout: ptypes.Duration(60 * time.Second)},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Use Regex with Prefix pathType and StrictValidatePathType enabled",
paths: []string{