add per minute rate limiting

This commit is contained in:
Seth Pollack 2017-08-01 23:24:48 -04:00
parent d5f11007bb
commit 40a9a54082
No known key found for this signature in database
GPG key ID: 0BD21A8B536BBEBC
5 changed files with 50 additions and 11 deletions

View file

@ -193,13 +193,15 @@ Please check the [rewrite](/examples/rewrite/nginx/README.md) example.
### Rate limiting
The annotations `ingress.kubernetes.io/limit-connections` and `ingress.kubernetes.io/limit-rps` define a limit on the connections that can be opened by a single client IP address. This can be used to mitigate [DDoS Attacks](https://www.nginx.com/blog/mitigating-ddos-attacks-with-nginx-and-nginx-plus).
The annotations `ingress.kubernetes.io/limit-connections`, `ingress.kubernetes.io/limit-rps`, and `ingress.kubernetes.io/limit-rpm` define a limit on the connections that can be opened by a single client IP address. This can be used to mitigate [DDoS Attacks](https://www.nginx.com/blog/mitigating-ddos-attacks-with-nginx-and-nginx-plus).
`ingress.kubernetes.io/limit-connections`: number of concurrent connections allowed from a single IP address.
`ingress.kubernetes.io/limit-rps`: number of connections that may be accepted from a given IP each second.
If you specify both annotations in a single Ingress rule, `limit-rps` takes precedence.
`ingress.kubernetes.io/limit-rpm`: number of connections that may be accepted from a given IP each minute.
If you specify multiple annotations in a single Ingress rule, `limit-rpm`, and then `limit-rps` takes precedence.
### SSL Passthrough

View file

@ -349,6 +349,17 @@ func buildRateLimitZones(variable string, input interface{}) []string {
}
}
if loc.RateLimit.RPM.Limit > 0 {
zone := fmt.Sprintf("limit_req_zone %v zone=%v:%vm rate=%vr/m;",
variable,
loc.RateLimit.RPM.Name,
loc.RateLimit.RPM.SharedSize,
loc.RateLimit.RPM.Limit)
if !zones.Has(zone) {
zones.Insert(zone)
}
}
if loc.RateLimit.RPS.Limit > 0 {
zone := fmt.Sprintf("limit_req_zone %v zone=%v:%vm rate=%vr/s;",
variable,
@ -366,7 +377,7 @@ func buildRateLimitZones(variable string, input interface{}) []string {
}
// buildRateLimit produces an array of limit_req to be used inside the Path of
// Ingress rules. The order: connections by IP first and RPS next.
// Ingress rules. The order: connections by IP first, then RPS, and RPM last.
func buildRateLimit(input interface{}) []string {
limits := []string{}
@ -387,6 +398,12 @@ func buildRateLimit(input interface{}) []string {
limits = append(limits, limit)
}
if loc.RateLimit.RPM.Limit > 0 {
limit := fmt.Sprintf("limit_req zone=%v burst=%v nodelay;",
loc.RateLimit.RPM.Name, loc.RateLimit.RPM.Burst)
limits = append(limits, limit)
}
return limits
}

View file

@ -27,6 +27,7 @@ import (
const (
limitIP = "ingress.kubernetes.io/limit-connections"
limitRPS = "ingress.kubernetes.io/limit-rps"
limitRPM = "ingress.kubernetes.io/limit-rpm"
// allow 5 times the specified limit as burst
defBurst = 5
@ -45,6 +46,8 @@ type RateLimit struct {
Connections Zone `json:"connections"`
// RPS indicates a limit with the number of connections per second
RPS Zone `json:"rps"`
RPM Zone `json:"rpm"`
}
// Equal tests for equality between two RateLimit types
@ -58,6 +61,9 @@ func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool {
if !(&rt1.Connections).Equal(&rt2.Connections) {
return false
}
if !(&rt1.RPM).Equal(&rt2.RPM) {
return false
}
if !(&rt1.RPS).Equal(&rt2.RPS) {
return false
}
@ -111,13 +117,15 @@ func NewParser() parser.IngressAnnotation {
// rule used to rewrite the defined paths
func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
rpm, _ := parser.GetIntAnnotation(limitRPM, ing)
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
conn, _ := parser.GetIntAnnotation(limitIP, ing)
if rps == 0 && conn == 0 {
if rpm == 0 && rps == 0 && conn == 0 {
return &RateLimit{
Connections: Zone{},
RPS: Zone{},
RPM: Zone{},
}, nil
}
@ -136,5 +144,11 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
Burst: rps * defBurst,
SharedSize: defSharedSize,
},
RPM: Zone{
Name: fmt.Sprintf("%v_rpm", zoneName),
Limit: rpm,
Burst: rpm * defBurst,
SharedSize: defSharedSize,
},
}, nil
}

View file

@ -75,6 +75,7 @@ func TestBadRateLimiting(t *testing.T) {
data := map[string]string{}
data[limitIP] = "0"
data[limitRPS] = "0"
data[limitRPM] = "0"
ing.SetAnnotations(data)
_, err := NewParser().Parse(ing)
@ -85,6 +86,7 @@ func TestBadRateLimiting(t *testing.T) {
data = map[string]string{}
data[limitIP] = "5"
data[limitRPS] = "100"
data[limitRPM] = "10"
ing.SetAnnotations(data)
i, err := NewParser().Parse(ing)
@ -101,4 +103,7 @@ func TestBadRateLimiting(t *testing.T) {
if rateLimit.RPS.Limit != 100 {
t.Errorf("expected 100 in limit by rps but %v was returend", rateLimit.RPS)
}
if rateLimit.RPM.Limit != 10 {
t.Errorf("expected 10 in limit by rpm but %v was returend", rateLimit.RPM)
}
}

View file

@ -49,13 +49,14 @@ Key:
| Name | Meaning
| --- | ---
| `configuration-snippet` | Arbitrary text to put in the generated configuration file. (nginx)
| `enable-cors` | Enable CORS headers in response. (nginx)
| `limit-connections` | Limit concurrent connections per IP address[1]. (nginx)
| `limit-rps` | Limit requests per second per IP address[1]. (nginx)
| `affinity` | Specify a method to stick clients to origins across requests. Found in `nginx`, where the only supported value is `cookie`. (nginx)
| `session-cookie-name` | When `affinity` is set to `cookie`, the name of the cookie to use. (nginx)
| `session-cookie-hash` | When `affinity` is set to `cookie`, the hash algorithm used: `md5`, `sha`, `index`. (nginx)
| `configuration-snippet` | Arbitrary text to put in the generated configuration file. (nginx)
| `enable-cors` | Enable CORS headers in response. (nginx)
| `limit-connections` | Limit concurrent connections per IP address[1]. (nginx)
| `limit-rps` | Limit requests per second per IP address[1]. (nginx)
| `limit-rpm` | Limit requests per minute per IP address. (nginx)
| `affinity` | Specify a method to stick clients to origins across requests. Found in `nginx`, where the only supported value is `cookie`. (nginx)
| `session-cookie-name` | When `affinity` is set to `cookie`, the name of the cookie to use. (nginx)
| `session-cookie-hash` | When `affinity` is set to `cookie`, the hash algorithm used: `md5`, `sha`, `index`. (nginx)
| `proxy-body-size` | Maximum request body size. (nginx, haproxy)
| `follow-redirects` | Follow HTTP redirects in the response and deliver the redirect target to the client. (trafficserver)