mirror of
https://github.com/traefik/traefik.git
synced 2026-05-05 04:16:25 +02:00
Add limit-connections support
This commit is contained in:
parent
c1d3c08390
commit
2433b18fef
@ -376,6 +376,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`. |
|
||||
| <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> | Exceeding the limit returns `429 Too Many Requests` instead of NGINX's default `503 Service Unavailable`. The concurrent connection limit is evaluated per client IP address. Values less than or equal to `0` are safely ignored. |
|
||||
|
||||
### Buffering
|
||||
|
||||
@ -454,7 +455,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-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> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioglobal-rate-limit-key" href="#opt-nginx-ingress-kubernetes-ioglobal-rate-limit-key" title="#opt-nginx-ingress-kubernetes-ioglobal-rate-limit-key">`nginx.ingress.kubernetes.io/global-rate-limit-key`</a> | |
|
||||
|
||||
@ -89,6 +89,7 @@ type IngressConfig struct {
|
||||
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"`
|
||||
LimitConnections *int `annotation:"nginx.ingress.kubernetes.io/limit-connections"`
|
||||
|
||||
CustomHeaders *string `annotation:"nginx.ingress.kubernetes.io/custom-headers"`
|
||||
UpstreamVHost *string `annotation:"nginx.ingress.kubernetes.io/upstream-vhost"`
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-with-limit-connections
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/limit-connections: "10"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: whoami.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
@ -14232,6 +14232,105 @@ func TestLoadIngresses(t *testing.T) {
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Limit connections",
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-limit-connections.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-connections-rule-0-path-0": {
|
||||
EntryPoints: []string{"http"},
|
||||
Rule: `Host("whoami.localhost") && PathPrefix("/")`,
|
||||
RuleSyntax: "default",
|
||||
Middlewares: []string{"default-ingress-with-limit-connections-rule-0-path-0-limit-connections", "default-ingress-with-limit-connections-rule-0-path-0-retry"},
|
||||
Service: "default-ingress-with-limit-connections-whoami-80",
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
Metadata: &dynamic.ObservabilityMetadata{
|
||||
Ingress: &dynamic.KubernetesIngressMetadata{
|
||||
Namespace: "default",
|
||||
IngressName: "ingress-with-limit-connections",
|
||||
ServiceName: "whoami",
|
||||
ServicePort: "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-ingress-with-limit-connections-rule-0-path-0-tls": {
|
||||
EntryPoints: []string{"https"},
|
||||
Rule: `Host("whoami.localhost") && PathPrefix("/")`,
|
||||
RuleSyntax: "default",
|
||||
Middlewares: []string{"default-ingress-with-limit-connections-rule-0-path-0-tls-limit-connections", "default-ingress-with-limit-connections-rule-0-path-0-tls-retry"},
|
||||
Service: "default-ingress-with-limit-connections-whoami-80",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
Metadata: &dynamic.ObservabilityMetadata{
|
||||
Ingress: &dynamic.KubernetesIngressMetadata{
|
||||
Namespace: "default",
|
||||
IngressName: "ingress-with-limit-connections",
|
||||
ServiceName: "whoami",
|
||||
ServicePort: "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-ingress-with-limit-connections-rule-0-path-0-retry": {Retry: &dynamic.Retry{Attempts: 3}},
|
||||
"default-ingress-with-limit-connections-rule-0-path-0-tls-retry": {Retry: &dynamic.Retry{Attempts: 3}},
|
||||
"default-ingress-with-limit-connections-rule-0-path-0-limit-connections": {
|
||||
InFlightReq: &dynamic.InFlightReq{
|
||||
Amount: 10,
|
||||
SourceCriterion: &dynamic.SourceCriterion{
|
||||
IPStrategy: &dynamic.IPStrategy{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-ingress-with-limit-connections-rule-0-path-0-tls-limit-connections": {
|
||||
InFlightReq: &dynamic.InFlightReq{
|
||||
Amount: 10,
|
||||
SourceCriterion: &dynamic.SourceCriterion{
|
||||
IPStrategy: &dynamic.IPStrategy{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"unavailable-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-ingress-with-limit-connections-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-connections",
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{
|
||||
"default-ingress-with-limit-connections": {
|
||||
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{
|
||||
|
||||
@ -34,6 +34,7 @@ func (p *Provider) buildMiddlewares(ctx context.Context, loc *location, hostname
|
||||
p.buildRewriteTarget(loc)
|
||||
p.buildUpstreamVhost(loc)
|
||||
p.buildRateLimit(loc)
|
||||
p.buildLimitConnections(loc)
|
||||
p.buildAuthTLSPassCert(loc)
|
||||
p.buildCustomHeaders(loc)
|
||||
p.buildSnippetAuth(loc)
|
||||
@ -310,6 +311,20 @@ func (p *Provider) buildRateLimit(loc *location) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) buildLimitConnections(loc *location) {
|
||||
limit := ptr.Deref(loc.Config.LimitConnections, 0)
|
||||
if limit <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
loc.LimitConnections = &dynamic.InFlightReq{
|
||||
Amount: int64(limit),
|
||||
SourceCriterion: &dynamic.SourceCriterion{
|
||||
IPStrategy: &dynamic.IPStrategy{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) buildAuthTLSPassCert(loc *location) {
|
||||
if !ptr.Deref(loc.Config.AuthTLSPassCertificateToUpstream, false) || loc.Config.AuthTLSSecret == nil {
|
||||
return
|
||||
|
||||
@ -193,6 +193,9 @@ type location struct {
|
||||
// RateLimitRPS, if non-nil, applies a per-second request rate limit.
|
||||
RateLimitRPS *dynamic.RateLimit
|
||||
|
||||
// LimitConnections, if non-nil, caps concurrent in-flight requests per source IP.
|
||||
LimitConnections *dynamic.InFlightReq
|
||||
|
||||
// AuthTLSPassCert, if non-nil, forwards the client TLS certificate to the backend.
|
||||
AuthTLSPassCert *dynamic.AuthTLSPassCertificateToUpstream
|
||||
|
||||
|
||||
@ -501,6 +501,12 @@ func (p *Provider) applyMiddlewares(mc *model, loc *location, routerKey string,
|
||||
rt.Middlewares = append(rt.Middlewares, name)
|
||||
}
|
||||
|
||||
if loc.LimitConnections != nil {
|
||||
name := routerKey + "-limit-connections"
|
||||
conf.HTTP.Middlewares[name] = &dynamic.Middleware{InFlightReq: loc.LimitConnections}
|
||||
rt.Middlewares = append(rt.Middlewares, name)
|
||||
}
|
||||
|
||||
if loc.AuthTLSPassCert != nil && rt.TLS != nil {
|
||||
name := routerKey + "-pass-certificate-to-upstream"
|
||||
conf.HTTP.Middlewares[name] = &dynamic.Middleware{AuthTLSPassCertificateToUpstream: loc.AuthTLSPassCert}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user