Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add period for rate limiter middleware #6055

Merged
merged 5 commits into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 86 additions & 9 deletions docs/content/middlewares/ratelimit.md
Expand Up @@ -3,7 +3,7 @@
To Control the Number of Requests Going to a Service
{: .subtitle }

The RateLimit middleware ensures that services will receive a _fair_ number of requests, and allows you define what is fair.
The RateLimit middleware ensures that services will receive a _fair_ number of requests, and allows one to define what fair is.

## Configuration Example

Expand All @@ -24,8 +24,8 @@ metadata:
name: test-ratelimit
spec:
rateLimit:
average: 100
burst: 50
average: 100
burst: 50
```

```yaml tab="Consul Catalog"
Expand Down Expand Up @@ -74,25 +74,32 @@ http:

### `average`

Average is the maximum rate, in requests/s, allowed for the given source.
It defaults to 0, which means no rate limiting.
`average` is the maximum rate, by default in requests by second, allowed for the given source.

It defaults to `0`, which means no rate limiting.

The rate is actually defined by dividing `average` by `period`.
So for a rate below 1 req/s, one needs to define a `period` larger than a second.

```yaml tab="Docker"
# 100 reqs/s
labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
```

```yaml tab="Kubernetes"
# 100 reqs/s
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-ratelimit
spec:
rateLimit:
average: 100
average: 100
```

```yaml tab="Consul Catalog"
# 100 reqs/s
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
```

Expand All @@ -108,23 +115,93 @@ labels:
```

```toml tab="File (TOML)"
# 100 reqs/s
[http.middlewares]
[http.middlewares.test-ratelimit.rateLimit]
average = 100
```

```yaml tab="File (YAML)"
# 100 reqs/s
http:
middlewares:
test-ratelimit:
rateLimit:
average: 100
```

### `period`

`period`, in combination with `average`, defines the actual maximum rate, such as:

```go
r = average / period
```

It defaults to `1` second.

```yaml tab="Docker"
# 6 reqs/minute
labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=6"
- "traefik.http.middlewares.test-ratelimit.ratelimit.period=1m"
```

```yaml tab="Kubernetes"
# 6 reqs/minute
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-ratelimit
spec:
rateLimit:
period: 1m
average: 6
```

```yaml tab="Consul Catalog"
# 6 reqs/minute
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=6"
- "traefik.http.middlewares.test-ratelimit.ratelimit.period=1m"
```

```json tab="Marathon"
"labels": {
"traefik.http.middlewares.test-ratelimit.ratelimit.average": "6",
"traefik.http.middlewares.test-ratelimit.ratelimit.period": "1m",
}
```

```yaml tab="Rancher"
# 6 reqs/minute
labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=6"
- "traefik.http.middlewares.test-ratelimit.ratelimit.period=1m"
```

```toml tab="File (TOML)"
# 6 reqs/minute
[http.middlewares]
[http.middlewares.test-ratelimit.rateLimit]
average = 6
period = 1m
```

```yaml tab="File (YAML)"
# 6 reqs/minute
http:
middlewares:
test-ratelimit:
rateLimit:
average: 6
period: 1m
```

### `burst`

Burst is the maximum number of requests allowed to go through in the same arbitrarily small period of time.
It defaults to 1.
`burst` is the maximum number of requests allowed to go through in the same arbitrarily small period of time.

It defaults to `1`.

```yaml tab="Docker"
labels:
Expand All @@ -138,7 +215,7 @@ metadata:
name: test-ratelimit
spec:
rateLimit:
burst: 100
burst: 100
```

```yaml tab="Consul Catalog"
Expand Down
Expand Up @@ -88,6 +88,7 @@
- "traefik.http.middlewares.middleware12.passtlsclientcert.info.subject.serialnumber=true"
- "traefik.http.middlewares.middleware12.passtlsclientcert.pem=true"
- "traefik.http.middlewares.middleware13.ratelimit.average=42"
- "traefik.http.middlewares.middleware13.ratelimit.period=42"
- "traefik.http.middlewares.middleware13.ratelimit.burst=42"
- "traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.ipstrategy.depth=42"
- "traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/dynamic-configuration/file.toml
Expand Up @@ -216,6 +216,7 @@
[http.middlewares.Middleware13]
[http.middlewares.Middleware13.rateLimit]
average = 42
period = 42
burst = 42
[http.middlewares.Middleware13.rateLimit.sourceCriterion]
requestHeaderName = "foobar"
Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/dynamic-configuration/file.yaml
Expand Up @@ -243,6 +243,7 @@ http:
Middleware13:
rateLimit:
average: 42
period: 42
burst: 42
sourceCriterion:
ipstrategy:
Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/dynamic-configuration/kv-ref.md
Expand Up @@ -102,6 +102,7 @@
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/pem` | `true` |
| `traefik/http/middlewares/Middleware13/rateLimit/average` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/period` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/burst` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
Expand Down
Expand Up @@ -88,6 +88,7 @@
"traefik.http.middlewares.middleware12.passtlsclientcert.info.subject.serialnumber": "true",
"traefik.http.middlewares.middleware12.passtlsclientcert.pem": "true",
"traefik.http.middlewares.middleware13.ratelimit.average": "42",
"traefik.http.middlewares.middleware13.ratelimit.period": "42",
"traefik.http.middlewares.middleware13.ratelimit.burst": "42",
"traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.ipstrategy.depth": "42",
"traefik.http.middlewares.middleware13.ratelimit.sourcecriterion.ipstrategy.excludedips": "foobar, foobar",
Expand Down
1 change: 1 addition & 0 deletions pkg/config/dynamic/fixtures/sample.toml
Expand Up @@ -250,6 +250,7 @@
[http.middlewares.Middleware10]
[http.middlewares.Middleware10.rateLimit]
average = 42
period = "1s"
burst = 42
[http.middlewares.Middleware10.rateLimit.sourceCriterion]
requestHeaderName = "foobar"
Expand Down
10 changes: 9 additions & 1 deletion pkg/config/dynamic/middlewares.go
Expand Up @@ -6,8 +6,10 @@ import (
"fmt"
"io/ioutil"
"os"
"time"

"github.com/containous/traefik/v2/pkg/ip"
"github.com/containous/traefik/v2/pkg/types"
)

// +k8s:deepcopy-gen=true
Expand Down Expand Up @@ -296,9 +298,14 @@ type SourceCriterion struct {

// RateLimit holds the rate limiting configuration for a given router.
type RateLimit struct {
// Average is the maximum rate, in requests/s, allowed for the given source.
// Average is the maximum rate, by default in requests/s, allowed for the given source.
// It defaults to 0, which means no rate limiting.
// The rate is actually defined by dividing Average by Period. So for a rate below 1req/s,
// one needs to define a Period larger than a second.
Average int64 `json:"average,omitempty" toml:"average,omitempty" yaml:"average,omitempty"`
// Period, in combination with Average, defines the actual maximum rate, such as:
// r = Average / Period. It defaults to a second.
Period types.Duration
// Burst is the maximum number of requests allowed to arrive in the same arbitrarily small period of time.
// It defaults to 1.
Burst int64 `json:"burst,omitempty" toml:"burst,omitempty" yaml:"burst,omitempty"`
Expand All @@ -308,6 +315,7 @@ type RateLimit struct {
// SetDefaults sets the default values on a RateLimit.
func (r *RateLimit) SetDefaults() {
r.Burst = 1
r.Period = types.Duration(time.Second)
r.SourceCriterion = &SourceCriterion{
IPStrategy: &IPStrategy{},
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/label/label_test.go
Expand Up @@ -3,8 +3,10 @@ package label
import (
"fmt"
"testing"
"time"

"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -101,6 +103,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.serialnumber": "true",
"traefik.http.middlewares.Middleware11.passtlsclientcert.pem": "true",
"traefik.http.middlewares.Middleware12.ratelimit.average": "42",
"traefik.http.middlewares.Middleware12.ratelimit.period": "1s",
"traefik.http.middlewares.Middleware12.ratelimit.burst": "42",
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.requestheadername": "foobar",
"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.requesthost": "true",
Expand Down Expand Up @@ -324,6 +327,7 @@ func TestDecodeConfiguration(t *testing.T) {
RateLimit: &dynamic.RateLimit{
Average: 42,
Burst: 42,
Period: types.Duration(time.Second),
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
Expand Down Expand Up @@ -729,6 +733,7 @@ func TestEncodeConfiguration(t *testing.T) {
RateLimit: &dynamic.RateLimit{
Average: 42,
Burst: 42,
Period: types.Duration(time.Second),
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
Expand Down Expand Up @@ -1081,6 +1086,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.DomainComponent": "true",
"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.PEM": "true",
"traefik.HTTP.Middlewares.Middleware12.RateLimit.Average": "42",
"traefik.HTTP.Middlewares.Middleware12.RateLimit.Period": "1000000000",
"traefik.HTTP.Middlewares.Middleware12.RateLimit.Burst": "42",
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.RequestHeaderName": "foobar",
"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.RequestHost": "true",
Expand Down
26 changes: 20 additions & 6 deletions pkg/middlewares/ratelimiter/rate_limiter.go
Expand Up @@ -29,7 +29,7 @@ type rateLimiter struct {
rate rate.Limit // reqs/s
burst int64
// maxDelay is the maximum duration we're willing to wait for a bucket reservation to become effective, in nanoseconds.
// For now it is somewhat arbitrarily set to 1/rate.
// For now it is somewhat arbitrarily set to 1/(2*rate).
maxDelay time.Duration
sourceMatcher utils.SourceExtractor
next http.Handler
Expand Down Expand Up @@ -61,20 +61,34 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
}

burst := config.Burst
if burst <= 0 {
if burst < 1 {
burst = 1
}

// Logically, we should set maxDelay to ~infinity when config.Average == 0 (because it means to rate limiting),
period := time.Duration(config.Period)
if period == 0 {
period = time.Second
}

// Logically, we should set maxDelay to infinity when config.Average == 0 (because it means no rate limiting),
// but since the reservation will give us a delay = 0 anyway in this case, we're good even with any maxDelay >= 0.
var maxDelay time.Duration
if config.Average != 0 {
maxDelay = time.Second / time.Duration(config.Average*2)
var rtl float64
if config.Average > 0 {
rtl = float64(config.Average*int64(time.Second)) / float64(period)
// maxDelay does not scale well for rates below 1,
// so we just cap it to the corresponding value, i.e. 0.5s, in order to keep the effective rate predictable.
// One alternative would be to switch to a no-reservation mode (Allow() method) whenever we are in such a low rate regime.
if rtl < 1 {
maxDelay = 500 * time.Millisecond
} else {
maxDelay = time.Second / (time.Duration(rtl) * 2)
}
}

return &rateLimiter{
name: name,
rate: rate.Limit(config.Average),
rate: rate.Limit(rtl),
burst: burst,
maxDelay: maxDelay,
next: next,
Expand Down