Add Redis rate limiter

This commit is contained in:
longquan0104 2025-03-10 17:02:05 +07:00 committed by GitHub
parent c166a41c99
commit 550d96ea67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 2268 additions and 69 deletions

View file

@ -1790,6 +1790,90 @@ spec:
Period, in combination with Average, defines the actual maximum rate, such as:
r = Average / Period. It defaults to a second.
x-kubernetes-int-or-string: true
redis:
description: Redis hold the configs of Redis as bucket in rate
limiter.
properties:
db:
description: DB defines the Redis database that will be selected
after connecting to the server.
type: integer
dialTimeout:
anyOf:
- type: integer
- type: string
description: |-
DialTimeout sets the timeout for establishing new connections.
Default value is 5 seconds.
pattern: ^([0-9]+(ns|us|µs|ms|s|m|h)?)+$
x-kubernetes-int-or-string: true
endpoints:
description: |-
Endpoints contains either a single address or a seed list of host:port addresses.
Default value is ["localhost:6379"].
items:
type: string
type: array
maxActiveConns:
description: |-
MaxActiveConns defines the maximum number of connections allocated by the pool at a given time.
Default value is 0, meaning there is no limit.
type: integer
minIdleConns:
description: |-
MinIdleConns defines the minimum number of idle connections.
Default value is 0, and idle connections are not closed by default.
type: integer
poolSize:
description: |-
PoolSize defines the initial number of socket connections.
If the pool runs out of available connections, additional ones will be created beyond PoolSize.
This can be limited using MaxActiveConns.
// Default value is 0, meaning 10 connections per every available CPU as reported by runtime.GOMAXPROCS.
type: integer
readTimeout:
anyOf:
- type: integer
- type: string
description: |-
ReadTimeout defines the timeout for socket read operations.
Default value is 3 seconds.
pattern: ^([0-9]+(ns|us|µs|ms|s|m|h)?)+$
x-kubernetes-int-or-string: true
secret:
description: Secret defines the name of the referenced Kubernetes
Secret containing Redis credentials.
type: string
tls:
description: |-
TLS defines TLS-specific configurations, including the CA, certificate, and key,
which can be provided as a file path or file content.
properties:
caSecret:
description: |-
CASecret is the name of the referenced Kubernetes Secret containing the CA to validate the server certificate.
The CA certificate is extracted from key `tls.ca` or `ca.crt`.
type: string
certSecret:
description: |-
CertSecret is the name of the referenced Kubernetes Secret containing the client certificate.
The client certificate is extracted from the keys `tls.crt` and `tls.key`.
type: string
insecureSkipVerify:
description: InsecureSkipVerify defines whether the server
certificates should be validated.
type: boolean
type: object
writeTimeout:
anyOf:
- type: integer
- type: string
description: |-
WriteTimeout defines the timeout for socket write operations.
Default value is 3 seconds.
pattern: ^([0-9]+(ns|us|µs|ms|s|m|h)?)+$
x-kubernetes-int-or-string: true
type: object
sourceCriterion:
description: |-
SourceCriterion defines what criterion is used to group requests as originating from a common source.

View file

@ -0,0 +1,39 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[api]
insecure = true
[log]
level = "DEBUG"
noColor = true
[entryPoints]
[entryPoints.web]
address = ":8081"
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.router1]
service = "service1"
middlewares = [ "ratelimit" ]
rule = "Path(`/`)"
[http.middlewares]
[http.middlewares.ratelimit.rateLimit]
average = 100
burst = 1
[http.middlewares.ratelimit.rateLimit.redis]
endpoints = ["{{ .RedisEndpoint }}"]
[http.services]
[http.services.service1]
[http.services.service1.loadBalancer]
passHostHeader = true
[[http.services.service1.loadBalancer.servers]]
url = "http://{{.Server1}}:80"

View file

@ -1,6 +1,7 @@
package integration
import (
"net"
"net/http"
"testing"
"time"
@ -12,7 +13,8 @@ import (
type RateLimitSuite struct {
BaseSuite
ServerIP string
ServerIP string
RedisEndpoint string
}
func TestRateLimitSuite(t *testing.T) {
@ -26,6 +28,7 @@ func (s *RateLimitSuite) SetupSuite() {
s.composeUp()
s.ServerIP = s.getComposeServiceIP("whoami1")
s.RedisEndpoint = net.JoinHostPort(s.getComposeServiceIP("redis"), "6379")
}
func (s *RateLimitSuite) TearDownSuite() {
@ -58,3 +61,34 @@ func (s *RateLimitSuite) TestSimpleConfiguration() {
s.T().Fatalf("requests throughput was too fast wrt to rate limiting: 100 requests in %v", elapsed)
}
}
func (s *RateLimitSuite) TestRedisRateLimitSimpleConfiguration() {
file := s.adaptFile("fixtures/ratelimit/simple_redis.toml", struct {
Server1 string
RedisEndpoint string
}{
Server1: s.ServerIP,
RedisEndpoint: s.RedisEndpoint,
})
s.traefikCmd(withConfigFile(file))
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit", "redis"))
require.NoError(s.T(), err)
start := time.Now()
count := 0
for {
err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
require.NoError(s.T(), err)
count++
if count > 100 {
break
}
}
stop := time.Now()
elapsed := stop.Sub(start)
if elapsed < time.Second*99/100 {
s.T().Fatalf("requests throughput was too fast wrt to rate limiting: 100 requests in %v", elapsed)
}
}

View file

@ -2,3 +2,10 @@ version: "3.8"
services:
whoami1:
image: traefik/whoami
redis:
image: redis:5.0
command:
- redis-server
- --port
- 6379