From 3820aa416b7f13b8ea50854d00b9f0acfce5acf9 Mon Sep 17 00:00:00 2001 From: Gian Ortz Date: Sun, 30 Aug 2020 19:43:08 -0300 Subject: [PATCH] Add annotation to set value for burst multiplier on rate limit --- .../nginx-configuration/annotations.md | 5 +- .../ingress/annotations/ratelimit/main.go | 10 ++-- .../annotations/ratelimit/main_test.go | 52 +++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md index 2253c1b0f9..4e7c1bf283 100755 --- a/docs/user-guide/nginx-configuration/annotations.md +++ b/docs/user-guide/nginx-configuration/annotations.md @@ -457,8 +457,9 @@ By default the controller redirects all requests to an existing service that pro These annotations define limits on connections and transmission rates. These can be used to mitigate [DDoS Attacks](https://www.nginx.com/blog/mitigating-ddos-attacks-with-nginx-and-nginx-plus). * `nginx.ingress.kubernetes.io/limit-connections`: number of concurrent connections allowed from a single IP address. A 503 error is returned when exceeding this limit. -* `nginx.ingress.kubernetes.io/limit-rps`: number of requests accepted from a given IP each second. The burst limit is set to 5 times the limit. When clients exceed this limit, [limit-req-status-code](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#limit-req-status-code) ***default:*** 503 is returned. -* `nginx.ingress.kubernetes.io/limit-rpm`: number of requests accepted from a given IP each minute. The burst limit is set to 5 times the limit. When clients exceed this limit, [limit-req-status-code](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#limit-req-status-code) ***default:*** 503 is returned. +* `nginx.ingress.kubernetes.io/limit-rps`: number of requests accepted from a given IP each second. The burst limit is set to this limit multiplied by the burst multiplier, the default multiplier is 5. When clients exceed this limit, [limit-req-status-code](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#limit-req-status-code) ***default:*** 503 is returned. +* `nginx.ingress.kubernetes.io/limit-rpm`: number of requests accepted from a given IP each minute. The burst limit is set to this limit multiplied by the burst multiplier, the default multiplier is 5. When clients exceed this limit, [limit-req-status-code](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#limit-req-status-code) ***default:*** 503 is returned. +* `nginx.ingress.kubernetes.io/limit-burst-multiplier`: multiplier of the limit rate for burst size. The default burst multiplier is 5, this annotation override the default multiplier. When clients exceed this limit, [limit-req-status-code](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#limit-req-status-code) ***default:*** 503 is returned. * `nginx.ingress.kubernetes.io/limit-rate-after`: initial number of kilobytes after which the further transmission of a response to a given connection will be rate limited. This feature must be used with [proxy-buffering](#proxy-buffering) enabled. * `nginx.ingress.kubernetes.io/limit-rate`: number of kilobytes per second allowed to send to a given connection. The zero value disables rate limiting. This feature must be used with [proxy-buffering](#proxy-buffering) enabled. * `nginx.ingress.kubernetes.io/limit-whitelist`: client IP source ranges to be excluded from rate-limiting. The value is a comma separated list of CIDRs. diff --git a/internal/ingress/annotations/ratelimit/main.go b/internal/ingress/annotations/ratelimit/main.go index 3990f0f263..3decd59d32 100644 --- a/internal/ingress/annotations/ratelimit/main.go +++ b/internal/ingress/annotations/ratelimit/main.go @@ -157,6 +157,10 @@ func (a ratelimit) Parse(ing *networking.Ingress) (interface{}, error) { rpm, _ := parser.GetIntAnnotation("limit-rpm", ing) rps, _ := parser.GetIntAnnotation("limit-rps", ing) conn, _ := parser.GetIntAnnotation("limit-connections", ing) + burstMultiplier, err := parser.GetIntAnnotation("limit-burst-multiplier", ing) + if err != nil { + burstMultiplier = defBurst + } val, _ := parser.GetStringAnnotation("limit-whitelist", ing) @@ -181,19 +185,19 @@ func (a ratelimit) Parse(ing *networking.Ingress) (interface{}, error) { Connections: Zone{ Name: fmt.Sprintf("%v_conn", zoneName), Limit: conn, - Burst: conn * defBurst, + Burst: conn * burstMultiplier, SharedSize: defSharedSize, }, RPS: Zone{ Name: fmt.Sprintf("%v_rps", zoneName), Limit: rps, - Burst: rps * defBurst, + Burst: rps * burstMultiplier, SharedSize: defSharedSize, }, RPM: Zone{ Name: fmt.Sprintf("%v_rpm", zoneName), Limit: rpm, - Burst: rpm * defBurst, + Burst: rpm * burstMultiplier, SharedSize: defSharedSize, }, LimitRate: lr, diff --git a/internal/ingress/annotations/ratelimit/main_test.go b/internal/ingress/annotations/ratelimit/main_test.go index 3878d3a0a1..fb5d12c02f 100644 --- a/internal/ingress/annotations/ratelimit/main_test.go +++ b/internal/ingress/annotations/ratelimit/main_test.go @@ -136,12 +136,64 @@ func TestRateLimiting(t *testing.T) { if rateLimit.Connections.Limit != 5 { t.Errorf("expected 5 in limit by ip but %v was returend", rateLimit.Connections) } + if rateLimit.Connections.Burst != 5*5 { + t.Errorf("expected %d in burst limit by ip but %v was returend", 5*3, rateLimit.Connections) + } if rateLimit.RPS.Limit != 100 { t.Errorf("expected 100 in limit by rps but %v was returend", rateLimit.RPS) } + if rateLimit.RPS.Burst != 100*5 { + t.Errorf("expected %d in burst limit by rps but %v was returend", 100*3, rateLimit.RPS) + } if rateLimit.RPM.Limit != 10 { t.Errorf("expected 10 in limit by rpm but %v was returend", rateLimit.RPM) } + if rateLimit.RPM.Burst != 10*5 { + t.Errorf("expected %d in burst limit by rpm but %v was returend", 10*3, rateLimit.RPM) + } + if rateLimit.LimitRateAfter != 100 { + t.Errorf("expected 100 in limit by limitrateafter but %v was returend", rateLimit.LimitRateAfter) + } + if rateLimit.LimitRate != 10 { + t.Errorf("expected 10 in limit by limitrate but %v was returend", rateLimit.LimitRate) + } + + data = map[string]string{} + data[parser.GetAnnotationWithPrefix("limit-connections")] = "5" + data[parser.GetAnnotationWithPrefix("limit-rps")] = "100" + data[parser.GetAnnotationWithPrefix("limit-rpm")] = "10" + data[parser.GetAnnotationWithPrefix("limit-rate-after")] = "100" + data[parser.GetAnnotationWithPrefix("limit-rate")] = "10" + data[parser.GetAnnotationWithPrefix("limit-burst-multiplier")] = "3" + + ing.SetAnnotations(data) + + i, err = NewParser(mockBackend{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + rateLimit, ok = i.(*Config) + if !ok { + t.Errorf("expected a RateLimit type") + } + if rateLimit.Connections.Limit != 5 { + t.Errorf("expected 5 in limit by ip but %v was returend", rateLimit.Connections) + } + if rateLimit.Connections.Burst != 5*3 { + t.Errorf("expected %d in burst limit by ip but %v was returend", 5*3, rateLimit.Connections) + } + if rateLimit.RPS.Limit != 100 { + t.Errorf("expected 100 in limit by rps but %v was returend", rateLimit.RPS) + } + if rateLimit.RPS.Burst != 100*3 { + t.Errorf("expected %d in burst limit by rps but %v was returend", 100*3, rateLimit.RPS) + } + if rateLimit.RPM.Limit != 10 { + t.Errorf("expected 10 in limit by rpm but %v was returend", rateLimit.RPM) + } + if rateLimit.RPM.Burst != 10*3 { + t.Errorf("expected %d in burst limit by rpm but %v was returend", 10*3, rateLimit.RPM) + } if rateLimit.LimitRateAfter != 100 { t.Errorf("expected 100 in limit by limitrateafter but %v was returend", rateLimit.LimitRateAfter) }