Envoy로 속도 제한 활성화하기
Envoy로 속도 제한 활성화하기 (Enabling Rate Limits using Envoy)
이 작업은 Envoy의 네이티브 속도 제한을 사용해서 Istio 서비스에 대한 트래픽을 동적으로 제한하는 방법을 보여드려요. 전역 속도 제한과 로컬 속도 제한을 함께 활용하는 법을 배워요.
출처: Istio 문서
본문
이 작업은 Envoy의 네이티브 속도 제한을 사용해서 Istio 서비스에 대한 트래픽을 동적으로 제한하는 방법을 보여드려요. 작업에서 인그레스 게이트웨이를 통해 productpage 서비스에 대해 서비스의 모든 인스턴스에 걸쳐 분당 1개 요청을 허용하는 전역 속도 제한을 적용할 거예요. 추가로 각 개별 productpage 인스턴스에 대해 분당 4개 요청을 허용하는 로컬 속도 제한을 적용할 거예요. 이렇게 하면 productpage 서비스가 인그레스 게이트웨이를 통해 분당 최대 1개 요청을 처리하지만, 각 productpage 인스턴스는 분당 최대 4개 요청을 처리할 수 있어서 메시 내부 트래픽을 허용할 수 있어요.
시작하기 전에 (Before you begin)
[!note] 이 문서에서 설명하는 속도 제한은 EnvoyFilter API로 구현돼요. EnvoyFilter는 언제든지 변경될 수 있는 내부 구현 세부 사항을 노출해요. 특히 업그레이드 시 매우 주의해서 사용하세요.
- 설치 가이드의 지침을 따라 Kubernetes 클러스터에 Istio를 설정하세요.
- Bookinfo 샘플 애플리케이션을 배포하세요.
속도 제한 (Rate limits)
Envoy는 전역(global)과 로컬(local) 두 종류의 속도 제한을 지원해요. 전역 속도 제한은 전역 gRPC 속도 제한 서비스를 사용해서 전체 메시에 대한 속도 제한을 제공해요. 로컬 속도 제한은 서비스 인스턴스당 요청 비율을 제한하는 데 사용돼요. 로컬 속도 제한은 전역 속도 제한 서비스의 부하를 줄이기 위해 전역 속도 제한과 함께 사용할 수 있어요. 이 작업에서는 전역과 로컬 속도 제한을 모두 사용해서 서비스의 특정 경로에 대한 트래픽을 속도 제한하도록 Envoy를 구성할 거예요.
전역 속도 제한 (Global rate limit)
Envoy를 사용해서 메시에 대한 전역 속도 제한을 설정할 수 있어요. Envoy의 전역 속도 제한은 속도 제한 서비스에 쿼터를 요청하기 위해 gRPC API를 사용해요. 아래에서는 Go로 작성되고 Redis 백엔드를 사용하는 API의 참조 구현을 사용해요.
- 다음 configmap을 사용해서 참조 구현이
/productpage경로에 대한 요청을 1 req/min으로, 이후 고급 예시를 위한 값api를 2 req/min으로, 그 외 모든 요청을 100 req/min으로 속도 제한하도록 구성하세요.
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: ratelimit-config
data:
config.yaml: |
domain: product
descriptors:
- key: PATH
value: "/productpage"
rate_limit:
unit: minute
requests_per_unit: 1
- key: PATH
value: "api"
rate_limit:
unit: minute
requests_per_unit: 2
- key: PATH
rate_limit:
unit: minute
requests_per_unit: 100
EOF
- Envoy의 속도 제한 서비스 프로토콜을 구현하는 전역 속도 제한 서비스를 만드세요. 참고로 Envoy가 제공하는 참조 구현을 기반으로 한 데모 구성을 여기에서 찾을 수 있어요.
$ kubectl apply -f @samples/ratelimit/rate-limit-service.yaml@
- Envoy의 전역 속도 제한 http 필터를 사용해서 전역 속도 제한을 활성화하려면
ingressgateway에EnvoyFilter를 적용하세요. 이 패치는 Envoy의 전역 필터envoy.filters.http.ratelimit를HTTP_FILTER체인에 삽입해요.rate_limit_service필드는 외부 속도 제한 서비스, 이 경우outbound|8081||ratelimit.default.svc.cluster.local을 지정해요.
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-ratelimit
namespace: istio-system
spec:
workloadSelector:
# select by label in the same namespace
labels:
istio: ingressgateway
configPatches:
# The Envoy config you want to modify
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
# Adds the Envoy Rate Limit Filter in HTTP filter chain.
value:
name: envoy.filters.http.ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
# domain can be anything! Either match it to the ratelimiter service config (single domain) or set the domain per route configuration (multiple domains). See examples below
domain: ratelimit
failure_mode_deny: true
timeout: 10s
rate_limit_service:
grpc_service:
envoy_grpc:
cluster_name: outbound|8081||ratelimit.default.svc.cluster.local
authority: ratelimit.default.svc.cluster.local
transport_api_version: V3
EOF
- 속도 제한할 라우트 구성을 정의하는 또 다른
EnvoyFilter를ingressgateway에 적용하세요. 이는bookinfo.com:80이라는 이름의 가상 호스트에서 온 모든 라우트에 속도 제한 액션을 추가하고,RateLimitPerRoute필터 확장을 통해 액션 도메인을 설정해요.
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-ratelimit-svc
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: VIRTUAL_HOST
match:
context: GATEWAY
routeConfiguration:
vhost:
name: ""
route:
action: ANY
patch:
operation: MERGE
# Applies the rate limit rules.
value:
typed_per_filter_config:
envoy.filters.http.ratelimit:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimitPerRoute
domain: product # overrides 'ratelimit' domain
rate_limits:
- actions: # any actions in here
- request_headers:
header_name: ":path"
descriptor_key: "PATH"
EOF
전역 속도 제한 고급 사례 (Global rate limit advanced case)
이 예시는 /api/* uri를 매칭하기 위해 정규식을 사용하고, VirtualService http name을 사용해 라우트 수준에 삽입되는 속도 제한 액션을 정의해요. 이전 예시에서 삽입한 PATH 값 api가 여기서 사용돼요.
/api/v1/products접두사가api라는 라우트로 이동하도록 VirtualService를 변경하세요.
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: bookinfo
spec:
gateways:
- bookinfo-gateway
hosts:
- '*'
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
route:
- destination:
host: productpage
port:
number: 9080
- match:
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage
port:
number: 9080
name: api
EOF
- 1부터 99까지의 제품에 대해 라우트 수준에서 속도 제한 액션을 추가하고
ratelimit도메인을 오버라이드하도록 EnvoyFilter를 적용하세요.
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-ratelimit-svc-api
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_ROUTE
match:
context: GATEWAY
routeConfiguration:
vhost:
name: "*:8080"
route:
name: "api"
patch:
operation: MERGE
value:
typed_per_filter_config:
envoy.filters.http.ratelimit:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimitPerRoute
domain: product
route:
rate_limits:
- actions:
- header_value_match:
descriptor_key: "PATH"
descriptor_value: "api"
headers:
- name: ":path"
safe_regex_match:
google_re2: {}
regex: "/api/v1/products/[1-9]{1,2}"
EOF
로컬 속도 제한 (Local rate limit)
Envoy는 L4 연결과 HTTP 요청의 로컬 속도 제한을 지원해요. 이를 통해 프록시 자체, 즉 인스턴스 수준에서 다른 서비스를 호출하지 않고 속도 제한을 적용할 수 있어요. 다음 EnvoyFilter는 productpage 서비스를 통한 모든 트래픽에 로컬 속도 제한을 활성화해요. HTTP_FILTER 패치는 로컬 envoy 필터 envoy.filters.http.local_ratelimit를 HTTP connection manager 필터 체인에 삽입해요. 로컬 속도 제한 필터의 토큰 버킷은 분당 4개 요청을 허용하도록 구성돼요. 필터는 차단된 요청에 x-local-rate-limit 응답 헤더를 추가하도록도 구성돼요.
[!note] Envoy 속도 제한 페이지에 언급된 통계는 기본적으로 비활성화돼 있어요. 배포 시 다음 어노테이션으로 활성화할 수 있어요.
template:
metadata:
annotations:
proxy.istio.io/config: |-
proxyStatsMatcher:
inclusionRegexps:
- ".*http_local_rate_limit.*"
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-local-ratelimit-svc
namespace: istio-system
spec:
workloadSelector:
labels:
app: productpage
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
value:
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 4
tokens_per_fill: 4
fill_interval: 60s
filter_enabled:
runtime_key: local_rate_limit_enabled
default_value:
numerator: 100
denominator: HUNDRED
filter_enforced:
runtime_key: local_rate_limit_enforced
default_value:
numerator: 100
denominator: HUNDRED
response_headers_to_add:
- append: false
header:
key: x-local-rate-limit
value: 'true'
EOF
위 구성은 로컬 속도 제한을 모든 vhost/route에 적용해요. 또는 특정 라우트로 제한할 수도 있어요. 다음 EnvoyFilter는 productpage 서비스의 포트 9080으로의 모든 트래픽에 로컬 속도 제한을 활성화해요. 이전 구성과 달리 HTTP_FILTER 패치에는 token_bucket이 포함되지 않아요. 대신 token_bucket은 두 번째(HTTP_ROUTE) 패치에 정의되는데, 이 패치는 inbound|http|9080 가상 호스트로 가는 라우트에 대해 로컬 envoy 필터 envoy.filters.http.local_ratelimit의 typed_per_filter_config를 포함해요.
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: filter-local-ratelimit-svc
namespace: istio-system
spec:
workloadSelector:
labels:
app: productpage
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.local_ratelimit
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
value:
stat_prefix: http_local_rate_limiter
- applyTo: HTTP_ROUTE
match:
context: SIDECAR_INBOUND
routeConfiguration:
vhost:
name: "inbound|http|9080"
route:
action: ANY
patch:
operation: MERGE
value:
typed_per_filter_config:
envoy.filters.http.local_ratelimit:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
value:
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 4
tokens_per_fill: 4
fill_interval: 60s
filter_enabled:
runtime_key: local_rate_limit_enabled
default_value:
numerator: 100
denominator: HUNDRED
filter_enforced:
runtime_key: local_rate_limit_enforced
default_value:
numerator: 100
denominator: HUNDRED
response_headers_to_add:
- append: false
header:
key: x-local-rate-limit
value: 'true'
EOF
결과 검증하기 (Verify the results)
전역 속도 제한 검증하기
Bookinfo 샘플로 트래픽을 보내세요. 웹 브라우저에서 http://$GATEWAY_URL/productpage에 접속하거나 다음 명령을 실행하세요.
$ for i in {1..2}; do curl -s "http://$GATEWAY_URL/productpage" -o /dev/null -w "%{http_code}\n"; sleep 3; done
200
429
$ for i in {1..3}; do curl -s "http://$GATEWAY_URL/api/v1/products/${i}" -o /dev/null -w "%{http_code}\n"; sleep 3; done
200
200
429
[!note]
$GATEWAY_URL은 Bookinfo 예시에서 설정한 값이에요./productpage의 경우 첫 번째 요청은 통과하지만 1분 안의 이후 모든 요청은 429 응답을 받게 돼요./api/v1/products/*의 경우 1-99 사이의 아무 숫자로 두 번 요청해야 1분 안에 429 응답을 받게 돼요.
로컬 속도 제한 검증하기
인그레스 게이트웨이의 전역 속도 제한은 productpage 서비스에 대한 요청을 1 req/min으로 제한하지만, productpage 인스턴스의 로컬 속도 제한은 4 req/min을 허용해요. 이를 확인하려면 먼저 EnvoyFilter가 productpage 사이드카 프록시로 전파될 때까지 기다리세요. 리스너 구성에 local_ratelimit이 나타나는지 확인해서 전파를 검증할 수 있어요.
$ PRODUCTPAGE_POD=$(kubectl get pod -l app=productpage -o jsonpath='{.items[0].metadata.name}')
$ istioctl proxy-config listener "$PRODUCTPAGE_POD" -o json | grep local_ratelimit
"name": "envoy.filters.http.local_ratelimit",
그런 다음 ratings 파드에서 다음 curl 명령으로 내부 productpage 요청을 보내세요.
$ kubectl exec "$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')" -c ratings -- bash -c 'for i in {1..5}; do curl -s productpage:9080/productpage -o /dev/null -w "%{http_code}\n"; sleep 1; done'
200
200
200
200
429
productpage 인스턴스당 분당 4개 이하의 요청만 통과해야 해요.
정리 (Cleanup)
$ kubectl delete envoyfilter filter-ratelimit -nistio-system
$ kubectl delete envoyfilter filter-ratelimit-svc -nistio-system
$ kubectl delete envoyfilter filter-ratelimit-svc-api -nistio-system
$ kubectl delete envoyfilter filter-local-ratelimit-svc -nistio-system
$ kubectl delete cm ratelimit-config
$ kubectl delete -f @samples/ratelimit/rate-limit-service.yaml@
더 알아보기 (Learn more)
- Envoy 속도 제한 필터에 대한 자세한 내용은 Envoy 문서를 참고하세요.
- EnvoyFilter 사용 시 주의사항은 EnvoyFilter 개요를 참고하세요.