mirror of
https://github.com/traefik/traefik.git
synced 2026-06-17 19:09:29 +03:00
Support limit-rpm annotation for ingress-nginx
This commit is contained in:
@@ -381,6 +381,7 @@ The following annotations are organized by category for easier navigation.
|
||||
| Annotation | Limitations / Notes |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |-----------------------------------------------------------------------------------------------------------|
|
||||
| <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`. |
|
||||
|
||||
### Buffering
|
||||
|
||||
@@ -453,8 +454,7 @@ In practice, Traefik is slightly more lenient under bursty load, as it smooths o
|
||||
| <a id="opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors" href="#opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors" title="#opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors">`nginx.ingress.kubernetes.io/disable-proxy-intercept-errors`</a> | |
|
||||
| <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-rpm" href="#opt-nginx-ingress-kubernetes-iolimit-rpm" title="#opt-nginx-ingress-kubernetes-iolimit-rpm">`nginx.ingress.kubernetes.io/limit-rpm`</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> | |
|
||||
|
||||
@@ -80,6 +80,7 @@ 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"`
|
||||
|
||||
CustomHeaders *string `annotation:"nginx.ingress.kubernetes.io/custom-headers"`
|
||||
|
||||
@@ -37,6 +37,7 @@ func Test_parseIngressConfig(t *testing.T) {
|
||||
"nginx.ingress.kubernetes.io/proxy-buffer-size": "16k",
|
||||
"nginx.ingress.kubernetes.io/proxy-buffers-number": "8",
|
||||
"nginx.ingress.kubernetes.io/proxy-max-temp-file-size": "100m",
|
||||
"nginx.ingress.kubernetes.io/limit-rpm": "120",
|
||||
},
|
||||
expected: ingressConfig{
|
||||
SSLPassthrough: ptr.To(true),
|
||||
@@ -59,6 +60,7 @@ func Test_parseIngressConfig(t *testing.T) {
|
||||
ProxyBufferSize: ptr.To("16k"),
|
||||
ProxyBuffersNumber: ptr.To(8),
|
||||
ProxyMaxTempFileSize: ptr.To("100m"),
|
||||
LimitRPM: ptr.To(120),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-with-limit-rpm
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/limit-rpm: "10"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: whoami.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-rpm-zero
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/limit-rpm: "0"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: whoami-zero.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
@@ -1203,6 +1203,8 @@ func (p *Provider) applyMiddlewares(namespace, ingressName, routerKey, rulePath,
|
||||
|
||||
applyUpstreamVhost(routerKey, ingressConfig, rt, conf)
|
||||
|
||||
applyLimitRPMConfiguration(routerKey, ingressConfig, rt, conf)
|
||||
|
||||
applyLimitRPSConfiguration(routerKey, ingressConfig, rt, conf)
|
||||
|
||||
if err := p.applyAuthTLSPassCertificateToUpstream(namespace, routerKey, ingressConfig, rt, conf); err != nil {
|
||||
@@ -1307,6 +1309,24 @@ func (p *Provider) applyCustomHTTPErrors(namespace, ingressName, routerName stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyLimitRPMConfiguration(routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) {
|
||||
limitRPM := ptr.Deref(ingressConfig.LimitRPM, 0)
|
||||
if limitRPM <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
rateLimitMiddlewareName := routerName + "-limit-rpm"
|
||||
conf.HTTP.Middlewares[rateLimitMiddlewareName] = &dynamic.Middleware{
|
||||
RateLimit: &dynamic.RateLimit{
|
||||
Average: int64(limitRPM),
|
||||
Period: ptypes.Duration(time.Minute),
|
||||
Burst: int64(limitRPM) * defaultLimitBurstMultiplier,
|
||||
},
|
||||
}
|
||||
|
||||
rt.Middlewares = append(rt.Middlewares, rateLimitMiddlewareName)
|
||||
}
|
||||
|
||||
func applyLimitRPSConfiguration(routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) {
|
||||
limitRPS := ptr.Deref(ingressConfig.LimitRPS, 0)
|
||||
if limitRPS <= 0 {
|
||||
|
||||
@@ -6638,6 +6638,104 @@ func TestLoadIngresses(t *testing.T) {
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Limit RPM",
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-limit-rpm.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-rpm-rule-0-path-0": {
|
||||
Rule: "Host(`whoami.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Middlewares: []string{"default-ingress-with-limit-rpm-rule-0-path-0-limit-rpm", "default-ingress-with-limit-rpm-rule-0-path-0-retry"},
|
||||
Service: "default-ingress-with-limit-rpm-whoami-80",
|
||||
},
|
||||
"default-ingress-with-limit-rpm-zero-rule-0-path-0": {
|
||||
Rule: "Host(`whoami-zero.localhost`) && Path(`/`)",
|
||||
RuleSyntax: "default",
|
||||
Middlewares: []string{"default-ingress-with-limit-rpm-zero-rule-0-path-0-retry"},
|
||||
Service: "default-ingress-with-limit-rpm-zero-whoami-80",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-limit-rpm-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
"default-ingress-with-limit-rpm-rule-0-path-0-limit-rpm": {
|
||||
RateLimit: &dynamic.RateLimit{
|
||||
Average: 10,
|
||||
Burst: 50,
|
||||
Period: ptypes.Duration(time.Minute),
|
||||
},
|
||||
},
|
||||
"default-ingress-with-limit-rpm-zero-rule-0-path-0-retry": {
|
||||
Retry: &dynamic.Retry{
|
||||
Attempts: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-limit-rpm-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-rpm",
|
||||
},
|
||||
},
|
||||
"default-ingress-with-limit-rpm-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-rpm-zero",
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-limit-rpm": {
|
||||
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-rpm-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{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
Reference in New Issue
Block a user