diff --git a/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md b/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md
index 94ea3a2fff..b52ce31983 100644
--- a/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md
+++ b/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md
@@ -384,6 +384,7 @@ The following annotations are organized by category for easier navigation.
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |-----------------------------------------------------------------------------------------------------------|
| `nginx.ingress.kubernetes.io/limit-rps` | Exceeding the limit returns `429 Too Many Requests` instead of NGINX's default `503 Service Unavailable`. |
| `nginx.ingress.kubernetes.io/limit-rpm` | Exceeding the limit returns `429 Too Many Requests` instead of NGINX's default `503 Service Unavailable`. |
+| `nginx.ingress.kubernetes.io/limit-burst-multiplier` | 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
| `nginx.ingress.kubernetes.io/limit-rate-after` | |
| `nginx.ingress.kubernetes.io/limit-rate` | |
| `nginx.ingress.kubernetes.io/limit-whitelist` | |
-| `nginx.ingress.kubernetes.io/limit-burst-multiplier` | |
| `nginx.ingress.kubernetes.io/limit-connections` | |
| `nginx.ingress.kubernetes.io/global-rate-limit` | |
| `nginx.ingress.kubernetes.io/global-rate-limit-window` | |
diff --git a/pkg/provider/kubernetes/ingress-nginx/annotations.go b/pkg/provider/kubernetes/ingress-nginx/annotations.go
index e15d170b8e..8a1d2da951 100644
--- a/pkg/provider/kubernetes/ingress-nginx/annotations.go
+++ b/pkg/provider/kubernetes/ingress-nginx/annotations.go
@@ -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"`
diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/ingress-with-limit-burst-multiplier.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/ingress-with-limit-burst-multiplier.yml
new file mode 100644
index 0000000000..724f3fcd39
--- /dev/null
+++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/ingress-with-limit-burst-multiplier.yml
@@ -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
diff --git a/pkg/provider/kubernetes/ingress-nginx/kubernetes.go b/pkg/provider/kubernetes/ingress-nginx/kubernetes.go
index 8f3f3d715a..94be769f22 100644
--- a/pkg/provider/kubernetes/ingress-nginx/kubernetes.go
+++ b/pkg/provider/kubernetes/ingress-nginx/kubernetes.go
@@ -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),
},
}
diff --git a/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go b/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go
index 43dab89314..cd25e09b2d 100644
--- a/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go
+++ b/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go
@@ -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{