RateLimit
본문
RateLimit
rateLimit 미들웨어는 서비스가 공정한 양의 요청을 받도록 보장하며, 무엇이 공정한지 정의할 수 있게 해 줍니다.
이것은 토큰 버킷(token bucket) 구현을 기반으로 해요. 이 비유에서 average와 period 파라미터는 버킷이 다시 채워지는 속도를 정의하고, burst는 버킷의 크기(용량)입니다.
Rate와 Burst
rate는 average를 period로 나눠 정의합니다. 1 req/s 미만의 rate가 필요하면 period를 1초보다 크게 정의하면 돼요.
구성 예시
구조화된 (YAML)
# 여기서는 초당 평균 100개의 요청이 허용됩니다.
# 추가로 200개의 요청 버스트가 허용됩니다.
# Redis 분산 rate limiting이 사용 가능한 모든 옵션으로 구성됩니다.
http:
middlewares:
test-ratelimit:
rateLimit:
average: 100
period: 1s
burst: 200
redis:
endpoints:
- "redis-primary.example.com:6379"
- "redis-replica.example.com:6379"
username: "ratelimit-user"
password: "secure-password"
db: 2
poolSize: 50
minIdleConns: 10
maxActiveConns: 200
readTimeout: 3s
writeTimeout: 3s
dialTimeout: 5s
tls:
ca: "/etc/ssl/redis-ca.crt"
cert: "/etc/ssl/redis-client.crt"
key: "/etc/ssl/redis-client.key"
insecureSkipVerify: false
구조화된 (TOML)
# 여기서는 초당 평균 100개의 요청이 허용됩니다.
# 추가로 200개의 요청 버스트가 허용됩니다.
# Redis 분산 rate limiting이 사용 가능한 모든 옵션으로 구성됩니다.
[http.middlewares]
[http.middlewares.test-ratelimit.rateLimit]
average = 100
period = "1s"
burst = 200
[http.middlewares.test-ratelimit.rateLimit.redis]
endpoints = ["redis-primary.example.com:6379", "redis-replica.example.com:6379"]
username = "ratelimit-user"
password = "secure-password"
db = 2
poolSize = 50
minIdleConns = 10
maxActiveConns = 200
readTimeout = "3s"
writeTimeout = "3s"
dialTimeout = "5s"
[http.middlewares.test-ratelimit.rateLimit.redis.tls]
ca = "/etc/ssl/redis-ca.crt"
cert = "/etc/ssl/redis-client.crt"
key = "/etc/ssl/redis-client.key"
insecureSkipVerify = false
Labels
# 여기서는 초당 평균 100개의 요청이 허용됩니다.
# 추가로 200개의 요청 버스트가 허용됩니다.
# Redis 분산 rate limiting이 사용 가능한 모든 옵션으로 구성됩니다.
labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.test-ratelimit.ratelimit.period=1s"
- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=200"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.endpoints=redis-primary.example.com:6379,redis-replica.example.com:6379"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.username=ratelimit-user"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.password=secure-password"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.db=2"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.poolSize=50"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.minIdleConns=10"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.maxActiveConns=200"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.readTimeout=3s"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.writeTimeout=3s"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.dialTimeout=5s"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.ca=/etc/ssl/redis-ca.crt"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.cert=/etc/ssl/redis-client.crt"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.key=/etc/ssl/redis-client.key"
- "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.insecureSkipVerify=false"
Tags
// 여기서는 초당 평균 100개의 요청이 허용됩니다.
// 추가로 200개의 요청 버스트가 허용됩니다.
// Redis 분산 rate limiting이 사용 가능한 모든 옵션으로 구성됩니다.
{
"Tags": [
"traefik.http.middlewares.test-ratelimit.ratelimit.average=100",
"traefik.http.middlewares.test-ratelimit.ratelimit.period=1s",
"traefik.http.middlewares.test-ratelimit.ratelimit.burst=200",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.endpoints=redis-primary.example.com:6379,redis-replica.example.com:6379",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.username=ratelimit-user",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.password=secure-password",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.db=2",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.poolSize=50",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.minIdleConns=10",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.maxActiveConns=200",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.readTimeout=3s",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.writeTimeout=3s",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.dialTimeout=5s",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.ca=/etc/ssl/redis-ca.crt",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.cert=/etc/ssl/redis-client.crt",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.key=/etc/ssl/redis-client.key",
"traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.insecureSkipVerify=false"
]
}
Kubernetes
# 여기서는 초당 평균 100개의 요청이 허용됩니다.
# 추가로 200개의 요청 버스트가 허용됩니다.
# Redis 분산 rate limiting이 사용 가능한 모든 옵션으로 구성됩니다.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-ratelimit
spec:
rateLimit:
average: 100
period: 1s
burst: 200
redis:
endpoints:
- "redis-primary.example.com:6379"
- "redis-replica.example.com:6379"
secret: redis-credentials
db: 2
poolSize: 50
minIdleConns: 10
maxActiveConns: 200
readTimeout: 3s
writeTimeout: 3s
dialTimeout: 5s
tls:
caSecret: redis-ca
certSecret: redis-client-cert
insecureSkipVerify: false
---
apiVersion: v1
kind: Secret
metadata:
name: redis-credentials
namespace: default
data:
username: cmF0ZWxpbWl0LXVzZXI= # base64 encoded "ratelimit-user"
password: c2VjdXJlLXBhc3N3b3Jk # base64 encoded "secure-password"
---
apiVersion: v1
kind: Secret
metadata:
name: redis-ca
namespace: default
data:
tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
---
apiVersion: v1
kind: Secret
metadata:
name: redis-client-cert
namespace: default
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
구성 옵션
| Field | Description | Default | Required |
| average | period를 사용해 rate를 정의하는 요청 수. 0이면 rate limiting 없음. 자세한 내용은 여기. | 0 | No |
| period | rate를 정의하는 데 사용하는 시간 기간. 자세한 내용은 여기. | 1s | No |
| burst | 정확히 같은 순간에 통과할 수 있는 최대 요청 수. 자세한 내용은 여기. | 1 | No |
| sourceCriterion.requestHost | 요청 호스트를 소스로 간주할지 여부. sourceCriterion에 대한 자세한 내용은 여기. | false | No |
| sourceCriterion.requestHeaderName | 들어오는 요청을 그룹화하는 데 사용하는 헤더 이름. sourceCriterion에 대한 자세한 내용은 여기. | "" | No |
| sourceCriterion.ipStrategy.depth | X-Forwarded-For 헤더에서 선택할 IP의 깊이 위치(오른쪽부터 시작). 0이면 깊이 없음. X-Forwarded-For의 총 IP 수보다 크면 클라이언트 IP는 비어 있게 돼요. 0보다 크면 excludedIPs 옵션은 평가되지 않습니다. sourceCriterion, ipStrategy, depth에 대한 자세한 내용은 아래를 참고하세요. | 0 | No |
| sourceCriterion.ipStrategy.excludedIPs | X-Forwarded-For 헤더를 훑어 목록에 없는 첫 IP를 선택할 수 있게 해요. depth가 지정되면 excludedIPs는 무시됩니다. sourceCriterion, ipStrategy, excludedIPs에 대한 자세한 내용은 아래를 참고하세요. | | No |
| sourceCriterion.ipStrategy.ipv6Subnet | ipv6Subnet이 제공되고 선택된 IP가 IPv6라면, 그 IP는 속한 서브넷의 첫 IP로 변환됩니다. sourceCriterion, ipStrategy.ipv6Subnet에 대한 자세한 내용은 아래를 참고하세요. | | No |
| redis | redis 구성은 여러 Traefik 인스턴스에 걸쳐 rate limit 토큰을 저장하기 위해 Redis를 사용해 분산 rate limiting을 활성화해요. 이를 통해 Traefik 프록시 클러스터 전반에 일관된 rate limit을 강제할 수 있습니다. Redis가 구성되지 않으면 Traefik은 rate limiting에 인메모리 저장소를 사용하는데, 이는 개별 Traefik 인스턴스에서만 동작해요. | | No |
| redis.endpoints | 분산 rate limiting을 위한 Redis 서버 엔드포인트 목록. Redis 클러스터나 고가용성 구성에서는 여러 엔드포인트를 지정할 수 있어요. | "localhost:6379" | No |
| redis.username | Redis 인증용 사용자 이름. | "" | No |
| redis.password | Redis 인증용 비밀번호. Kubernetes에서는 secrets를 통해 제공할 수 있어요. | "" | No |
| redis.db | 선택할 Redis 데이터베이스 번호. | 0 | No |
| redis.poolSize | 풀의 기본 소켓 연결 수를 정의해요. 0으로 설정하면 runtime.GOMAXPROCS가 보고하는 CPU 코어당 10개 연결로 기본 설정됩니다. 풀에 충분한 연결이 없으면 poolSize를 넘어 maxActiveConns까지 새 연결이 할당됩니다. | 0 | No |
| redis.minIdleConns | 풀에서 유지할 최소 유휴 연결 수. 새 연결을 맺는 것이 느릴 때 유용해요. 0이면 유휴 연결이 자동으로 닫히지 않습니다. | 0 | No |
| redis.maxActiveConns | 풀이 주어진 순간에 할당할 수 있는 최대 연결 수. 0이면 제한 없음을 의미해요. | 0 | No |
| redis.readTimeout | 소켓 읽기 타임아웃. 도달하면 명령이 차단하는 대신 타임아웃으로 실패합니다. 0이면 타임아웃 없음. | 3s | No |
| redis.writeTimeout | 소켓 쓰기 타임아웃. 도달하면 명령이 차단하는 대신 타임아웃으로 실패합니다. 0이면 타임아웃 없음. | 3s | No |
| redis.dialTimeout | 새 연결을 맺기 위한 타임아웃. 0이면 타임아웃 없음. | 5s | No |
| redis.tls.ca | Redis에 대한 보안 연결에 사용하는 인증 기관의 경로. 시스템 번들로 기본 설정됩니다. | "" | No |
| redis.tls.cert | Redis에 대한 보안 연결에 사용하는 공개 인증서의 경로. 이 옵션이 설정되면 key 옵션이 필요해요. | "" | No |
| redis.tls.key | Redis에 대한 보안 연결에 사용하는 개인 키의 경로. 이 옵션이 설정되면 cert 옵션이 필요해요. | "" | No |
| redis.tls.insecureSkipVerify | insecureSkipVerify가 true면, Redis로의 TLS 연결은 서버가 제시하는 인증서를 호스트 이름과 관계없이 모두 수락해요. | false | No |
sourceCriterion
sourceCriterion 옵션은 어떤 기준으로 요청을 공통 소스에서 온 것으로 그룹화할지 정의합니다. 여러 전략이 동시에 정의되면 오류가 발생해요. 아무것도 설정하지 않으면 기본값은 요청의 원격 주소(remote address) 필드를 사용합니다(ipStrategy로).
ipStrategy
ipStrategy 옵션은 Traefik이 클라이언트 IP를 어떻게 결정할지 구성하는 세 가지 파라미터를 정의합니다: depth, excludedIPs, ipv6Subnet.
미들웨어로서 rate limiting은 실제 백엔드로의 프록시가 일어나기 전에 실행됩니다. 게다가 이전 네트워크 홉은 프록시의 마지막 단계, 즉 이미 rate limiting을 통과한 뒤에만 X-Forwarded-For에 추가돼요. 따라서 rate limiting 중에는 이전 네트워크 홉이 아직 X-Forwarded-For에 없으므로, 이를 찾거나 의존할 수 없습니다.
sourceCriterion.ipStrategy.ipv6Subnet
이 전략은 Depth와 RemoteAddr 전략에만 적용됩니다. ipv6Subnet이 제공되고 선택된 IP가 IPv6라면, 그 IP는 속한 서브넷의 첫 IP로 변환됩니다.
새 IPv6를 얻어 이 미들웨어를 우회하는 것을 막기 위해 IPv6 주소를 서브넷으로 그룹화할 때 유용해요.
- ipv6Subnet 값이 0-128 범위 밖이면 무시됩니다.
ipv6Subnet 예시
ipv6Subnet이 제공되면 IP는 다음과 같이 변환됩니다.
| IP | ipv6Subnet | clientIP |
| "::abcd:1111:2222:3333" | 64 | "::0:0:0:0" |
| "::abcd:1111:2222:3333" | 80 | "::abcd:0:0:0:0" |
| "::abcd:1111:2222:3333" | 96 | "::abcd:1111:0:0:0" |
sourceCriterion.ipStrategy.depth
depth가 2로 설정되고 요청의 X-Forwarded-For 헤더가 "10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"이라면, "실제" 클라이언트 IP는 "10.0.0.1"(depth 4 지점)이지만 기준으로 사용되는 IP는 "12.0.0.1"(depth=2)입니다.
| X-Forwarded-For | depth | clientIP |
| "10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1" | 1 | "13.0.0.1" |
| "10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1" | 3 | "11.0.0.1" |
| "10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1" | 5 | "" |
sourceCriterion.ipStrategy.excludedIPs
이름이 암시하는 것과 달리, 이 옵션은 rate limiter에서 IP를 제외하는 것이 아니므로, 일부 IP의 rate limiting을 비활성화하는 데 사용할 수 없습니다.
excludedIPs는 다소 구별되는 두 부류의 사용 사례를 다루기 위한 것입니다:
- 같은 (집합의) 리버스 프록시 뒤에 있는 IP들을 구별해서, 각각이 다른 것과 독립적으로 자신의 rate-limit "버킷"(토큰 버킷 참고)에 기여하도록 합니다. 이 경우, 실제 clientIP를 찾기 위해 제외할 X-Forwarded-For IP 목록과 일치하도록 excludedIPs를 설정해야 합니다.
각 IP를 구별된 소스로 사용하는 예시:
| X-Forwarded-For | excludedIPs | clientIP |
| "10.0.0.1,11.0.0.1,12.0.0.1" | "11.0.0.1,12.0.0.1" | "10.0.0.1" |
| "10.0.0.2,11.0.0.1,12.0.0.1" | "11.0.0.1,12.0.0.1" | "10.0.0.2" |
- IP 집합(역시 공통 리버스 프록시 집합 뒤에 있는)을 함께 그룹화해서 같은 소스로 간주하고, 모두 같은 rate-limit 버킷에 기여하도록 합니다.
IP를 같은 소스로 그룹화하는 예시:
| X-Forwarded-For | excludedIPs | clientIP |
| "10.0.0.1,11.0.0.1,12.0.0.1" | "12.0.0.1" | "11.0.0.1" |
| "10.0.0.2,11.0.0.1,12.0.0.1" | "12.0.0.1" | "11.0.0.1" |
| "10.0.0.3,11.0.0.1,12.0.0.1" | "12.0.0.1" | "11.0.0.1" |