Ssoon

[7주차] 요청 경로에서 Istio 확장 : 외부 콜아웃으로 요청 속도 제한 본문

Istio Hands-on Study [1기]

[7주차] 요청 경로에서 Istio 확장 : 외부 콜아웃으로 요청 속도 제한

구구달스 2025. 4. 23. 16:49

🚀 Istio와 Envoy로 Rate-Limiting 구현하기


🛠️ Rate-Limiting이란?

Rate-Limiting의 필요성

  • Rate-Limiting은 특정 서비스나 워크로드에 대한 요청 수를 제한시스템 과부하를 방지하고 안정성을 유지하는 기술입니다. Istio에서는 Envoy의 HTTP 필터를 활용외부 Rate-Limit 서버를 호출함으로써 이 기능을 구현할 수 있습니다. 이를 통해 서비스의 모든 복제본(replica)이 동일한 Rate-Limit 정책을 따르도록 보장합니다.

  • Envoy는 네트워크 필터, 로컬 Rate-Limiting, 글로벌 Rate-Limiting 등 여러 방식으로 Rate-Limiting을 지원하지만, 여기서는 글로벌 Rate-Limiting에 초점을 맞춥니다. 글로벌 방식은 모든 Envoy 프록시가 단일 Rate-Limit 서버와 통신일관된 제한을 적용합니다.
Rate-Limiting은 시스템 안정성을 위해 요청 수를 제한하며,
글로벌 방식은 모든 서비스 복제본에 일관된 정책을 적용합니다.

🔧 글로벌 Rate-Limiting 동작 원리

외부 Rate-Limit 서버와의 통합

  • 글로벌 Rate-Limiting은 Envoy 프록시가 외부 Rate-Limit 서버에 요청 속성(예: 헤더, 경로)을 전송제한 여부를 판단받는 방식으로 동작합니다.
    ate-Limit 서버는 Redis 또는 Memcache 같은 백엔드 키-값 저장소Rate-Limit 카운터를 저장해 요청 횟수를 추적합니다.

  • Rate-Limit 서버는 Envoy 커뮤니티에서 제공하는 오픈소스 프로젝트(envoyproxy/ratelimit)를 사용하거나, Envoy의 Rate-Limiting API(http://mng.bz/xvXB)를 구현한 커스텀 서버를 배포할 수 있습니다.
글로벌 Rate-Limiting은 Envoy가 외부 Rate-Limit 서버와 통신해 요청 제한을 결정하며,
Redis 같은 저장소를 사용해 카운터를 관리합니다.

🛠️ Rate-Limit 서버 설정 준비

Rate-Limit 서버 배포 전 설정

  • Rate-Limiting을 구현하려면 먼저 Rate-Limit 서버를 배포하고, 원하는 Rate-Limit 동작을 설정해야 합니다.
    예를 들어, 특정 경로(/api/catalog)에 대해 초당 10회 요청으로 제한하려면 Rate-Limit 서버의 설정 파일에 이를 정의합니다. 
domain: istioinaction
descriptors:
  - key: path
    value: "/api/catalog"
    rate_limit:
      unit: second
      requests_per_unit: 10
  • 이 설정은 /api/catalog 경로에 대해 초당 10회 요청을 허용하도록 Rate-Limit 서버를 구성합니다. 서버는 Redis와 연결되어 카운터를 저장하고, Envoy 프록시와 통신해 제한 여부를 판단합니다.
Rate-Limit 서버는 Rate-Limit 정책을 정의하고 Redis 같은 저장소와 연동해 동작하며,
배포 전 설정이 필요합니다.

📌 핵심 요약

  • Rate-Limiting의 중요성: Rate-Limiting은 서비스 과부하를 방지하며, 글로벌 방식은 모든 복제본에 일관된 제한을 적용합니다.
  • 글로벌 Rate-Limiting: Envoy 프록시가 외부 Rate-Limit 서버를 호출해 요청 제한을 판단하며, Redis 같은 저장소로 카운터를 관리합니다.
  • Rate-Limit 서버: Envoy 커뮤니티의 오픈소스 Rate-Limit 서버를 사용하거나, Envoy Rate-Limiting API를 구현한 서버를 배포할 수 있습니다.

🚀 Envoy로 글로벌 Rate-Limiting 구현하기


🛠️ Envoy Rate-Limiting 이해하기

Rate-Limiting의 동작 원리

  • Envoy의 글로벌 Rate-Limiting은 HTTP 필터를 통해 동작하며, HttpConnectionManager(HCM) 의 필터 체인에 설정됩니다. 요청이 Rate-Limit 필터를 통과할 때, Envoy는 요청의 특정 속성(descriptors)을 추출Rate-Limit 서버(RLS)로 전송합니다. descriptors는 요청의 remote address, 헤더, 목적지 등 다양한 속성을 포함할 수 있습니다.

  • RLS는 미리 정의된 속성 세트와 요청 속성을 비교카운터를 증가시키고, 설정된 임계값을 초과하면 요청을 제한합니다. 예를 들어, 특정 헤더 값에 따라 요청 횟수를 제한할 수 있습니다.
Envoy의 글로벌 Rate-Limiting은 요청 속성을 RLS로 보내 평가하며,
임계값 초과 시 요청을 제한합니다.

🔧 Rate-Limit 서버 설정하기

RLS 설정 예제

  • RLS를 설정하려면 요청 속성에 따라 Rate-Limit 정책을 정의해야 합니다.
    예를 들어, x-loyalty 헤더를 기준으로 사용자 등급별 요청 제한을 설정할 수 있습니다:
    • Gold (x-loyalty: gold): 분당 10회 요청
    • Silver (x-loyalty: silver): 분당 5회 요청
    • Bronze (x-loyalty: bronze): 분당 3회 요청
    • 알 수 없는 등급: 분당 1회 요청
  • 이를 위한 RLS 설정은 Kubernetes ConfigMap으로 정의합니다:
apiVersion: v1
kind: ConfigMap
metadata:
  name: catalog-ratelimit-config
  namespace: istioinaction
data:
  config.yaml: |
    domain: catalog-ratelimit #이 Rate Limit 설정의 도메인을 catalog-ratelimit라고 정의
    descriptors: #Rate Limit 규칙들을 정의하는 리스트
    - key: header_match #HTTP 헤더를 기준으로 트래픽을 매칭하겠다는 기준을 정의
      value: no_loyalty #header_match 키에 대해 값이 no_loyalty인 경우
      rate_limit: #이 조건에 매칭되는 트래픽에 적용할 Rate Limit 설정을 정의
        unit: MINUTE #시간 단위를 분(MINUTE)으로 설정
        requests_per_unit: 1 #지정된 시간 단위(여기서는 1분) 동안 허용되는 요청 수
    - key: header_match
      value: gold_request #header_match 키에 대해 값이 gold_request인 경우
      rate_limit:
        unit: MINUTE
        requests_per_unit: 10 #1분 동안 최대 10번의 요청을 허용
    - key: header_match
      value: silver_request #header_match 키에 대해 값이 silver_request인 경우
      rate_limit:
        unit: MINUTE
        requests_per_unit: 5
    - key: header_match
      value: bronze_request @header_match 키에 대해 값이 bronze_request인 경우
      rate_limit:
        unit: MINUTE
        requests_per_unit: 3
  • 이 설정은 catalog-ratelimit 도메인에서 x-loyalty 헤더 값에 따라 Rate-Limit 정책을 적용합니다.
RLS는 ConfigMap을 통해 요청 속성별 Rate-Limit 정책을 정의하며,
예를 들어 헤더 값에 따라 다른 제한을 설정할 수 있습니다.

🔍 Envoy 요청 경로 설정하기

Rate-Limit Actions 정의

  • RLS가 설정된 후, Envoy가 어떤 속성을 RLS로 보낼지 정의해야 합니다. 이를 Envoy에서는 Rate-Limit Actions라고 부르며, EnvoyFilter 리소스를 통해 설정합니다.
    예를 들어, /items 경로로 들어오는 요청에서 x-loyalty 헤더를 확인하도록 설정할 수 있습니다:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: catalog-ratelimit-actions
  namespace: istioinaction
spec:
  workloadSelector:
    labels:
      app: catalog
  configPatches:
  - applyTo: VIRTUAL_HOST #Envoy 프록시의 Virtual Host 설정에 변경 사항을 적용
    match:
      context: SIDECAR_INBOUND #이 필터를 사이드카 프록시의 인바운드 트래픽에 적용
      routeConfiguration:
        vhost:
          route:
            action: ANY #Virtual Host 내의 모든 라우트에 대해 설정을 적용
    patch:
      operation: MERGE #기존 Virtual Host 설정에 새로운 rate_limits 설정을 병합
      value:
        rate_limits: #라우트에 적용할 Rate Limit 규칙 목록을 정의
        - actions:
          - header_value_match:
              descriptor_value: no_loyalty #요청 헤더에 "x-loyalty" 헤더가 없거나
              expect_match: false #헤더 값이 지정된 값과 일치하지 않는 경우
              headers: 
              - name: "x-loyalty" #"x-loyalty"라는 이름의 헤더를 검사
        - actions:
          - header_value_match:
              descriptor_value: bronze_request 
              #요청 헤더의 "x-loyalty" 값이 "bronze"와 정확히 일치하는 경우 "bronze_request" descriptor를 사용하여 Rate Limit를 적용
              headers:
              - name: "x-loyalty" #"x-loyalty"라는 이름의 헤더를 검사
                exact_match: bronze #헤더 값이 정확히 "bronze"와 일치
        - actions:
          - header_value_match:
              descriptor_value: silver_request
              headers:
              - name: "x-loyalty"
                exact_match: silver
        - actions:
          - header_value_match:
              descriptor_value: gold_request
              headers:
              - name: "x-loyalty"
                exact_match: gold
  • 이 EnvoyFilter는 catalog 워크로드의 모든 인바운드 요청에 대해 x-loyalty 헤더를 확인하고, 해당 값을 RLS로 전송해 Rate-Limit을 적용합니다.
EnvoyFilter를 사용해 요청 속성을 정의하고 RLS로 전송해
Rate-Limit을 적용할 수 있습니다.

🛠️ 배포 및 테스트

RLS와 EnvoyFilter 배포

  • RLS와 Redis를 배포하고 EnvoyFilter를 적용합니다:
  • k8s configmap 으로 규칙을 배포하고, 속도 제한 서버를 레디스 백엔드와 함께 배포
$ kubectl apply -f ch14/rate-limit/rlsconfig.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/rls.yaml -n istioinaction
configmap/catalog-ratelimit-config created
service/redis created
deployment.apps/redis created
service/ratelimit created
deployment.apps/ratelimit created

$ kubectl get pod -n istioinaction
NAME                       READY   STATUS    RESTARTS   AGE
catalog-6cf4b97d-rk8hb     2/2     Running   0          42m
ratelimit-99d5d9c5-z7d2s   1/1     Running   0          14m
redis-6cf4ff9768-4h2cl     1/1     Running   0          14m
webapp-7685bcb84-s47k8     2/2     Running   0          42m
  • EnvoyFilter 리소스를 적용
$ kubectl apply -f ch14/rate-limit/catalog-ratelimit.yaml -n istioinaction
kubectl apply -f ch14/rate-limit/catalog-ratelimit-actions.yaml -n istioinaction
envoyfilter.networking.istio.io/catalog-ratelimit-filter created
envoyfilter.networking.istio.io/catalog-ratelimit-actions created
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-QMAIJOE:~$ kubectl get envoyfilter -A
NAMESPACE       NAME                        AGE
...
istioinaction   catalog-ratelimit-actions   2s
istioinaction   catalog-ratelimit-filter    3s
istioinaction   tap-filter                  40m

 

 

테스트

테스트를 위해 sleep 앱을 배포해 클라이언트를 시뮬레이션합니다:

$ kubectl apply -f ch9/sleep.yaml -n istioinaction
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created
  • 기본 요청(헤더 없음)을 실행해 Rate-Limit 동작을 확인합니다:
$ kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl http://catalog/items -v
...
< HTTP/1.1 429 Too Many Requests
< x-envoy-ratelimited: true
< date: Tue, 20 May 2025 01:27:54 GMT
< server: envoy
< content-length: 0
< x-envoy-upstream-service-time: 17
<
* Connection #0 to host catalog left intact
  • 이 요청은 분당 1회로 제한됩니다. x-loyalty 헤더를 추가해 다른 등급을 테스트합니다:
$ kubectl exec -it deploy/sleep -n istioinaction -c sleep -- curl -H "x-loyalty: silver" http://catalog/items -v
* Host catalog:80 was resolved.
* IPv6: (none)
* IPv4: 10.200.3.118
*   Trying 10.200.3.118:80...
* Connected to catalog (10.200.3.118) port 80
> GET /items HTTP/1.1
> Host: catalog
> User-Agent: curl/8.5.0
> Accept: */*
> x-loyalty: silver
>
< HTTP/1.1 200 OK
< x-powered-by: Express
< vary: Origin, Accept-Encoding
< access-control-allow-credentials: true
< cache-control: no-cache
< pragma: no-cache
...
  • silver 등급은 분당 5회 요청이 가능합니다.
    Rate-Limit이 제대로 적용되지 않으면, EnvoyFilter 설정과 RLS 로그를 확인합니다:
    Envoy 프록시의 inbound|3000|| 라는 이름의 라우팅 구성 정보를 JSON 형식으로 가져옵니다. 이는 해당 Envoy 프록시가 3000번 포트로 들어오는 인바운드 트래픽을 어떻게 처리하는지 확인하는 데 사용됩니다.
$ docker exec -it myk8s-control-plane istioctl proxy-config route deploy/catalog.istioinaction --name 'inbound|3000||' -o json | grep headerValueMatch -A10
                                "headerValueMatch": {
                                    "descriptorValue": "no_loyalty",
                                    "expectMatch": false,
                                    "headers": [
                                        {
                                            "name": "x-loyalty"
                                        }
                                    ]
                                }
                            }
                        ]
--
                                  "headerValueMatch": {
                                    "descriptorValue": "bronze_request",
                                    "headers": [
                                        {
                                            "name": "x-loyalty",
                                            "exactMatch": "bronze"
                                        }
                                    ]
                                }
                            }
                        ]
--
                                  "headerValueMatch": {
                                    "descriptorValue": "silver_request",
                                    "headers": [
                                        {
                                            "name": "x-loyalty",
                                            "exactMatch": "silver"
                                        }
                                    ]
                                }
                            }
                        ]
--
                                  "headerValueMatch": {
                                    "descriptorValue": "gold_request",
                                    "headers": [
                                        {
                                            "name": "x-loyalty",
                                            "exactMatch": "gold"
                                        }
                                    ]
                                }
                            }
                        ]
--

 

RLS와 EnvoyFilter를 배포한 후, sleep 앱으로 테스트해
Rate-Limit 동작을 확인할 수 있습니다.

  • 다음 실습을 위해 리소스 삭제
$ kubectl delete envoyfilter -n istioinaction --all
envoyfilter.networking.istio.io "catalog-ratelimit-actions" deleted
envoyfilter.networking.istio.io "catalog-ratelimit-filter" deleted
envoyfilter.networking.istio.io "tap-filter" deleted

$ kubectl delete -f ch14/rate-limit/rlsconfig.yaml -n istioinaction
kubectl delete -f ch14/rate-limit/rls.yaml -n istioinaction
configmap "catalog-ratelimit-config" deleted
service "redis" deleted
deployment.apps "redis" deleted
service "ratelimit" deleted
deployment.apps "ratelimit" deleted

📌 핵심 요약

  • Rate-Limiting 원리: Envoy의 Rate-Limit 필터는 요청 속성(descriptors)을 RLS로 보내 평가하며, 임계값 초과 시 요청을 제한합니다.
  • RLS 설정: ConfigMap을 통해 x-loyalty 헤더 기반으로 등급별 Rate-Limit 정책을 정의합니다.
  • EnvoyFilter: Rate-Limit Actions를 설정해 요청 속성을 RLS로 전송하며, 특정 경로에 대한 제한을 적용합니다.

 

 
Comments