LabHub
开始
学习 学习路径 课程

Envoy 内部结构

两台 Envoy,一个计数器

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

진짜 속도 제한 서비스(envoyproxy/ratelimit)와 Redis 를 띄우고, 같은 서비스를 부르는 Envoy 두 대가 한도 하나를 나눠 쓰는 것을 로컬 제한과 나란히 센다.

왜 중요한가

로컬 제한만 쓰면 프록시를 늘릴 때마다 한도가 늘어난다. 오토스케일링 앞에서는 막으려던 폭주가 올수록 문이 넓어진다. 전역 제한은 그 문제를 풀지만 요청마다 서비스 하나를 더 거치게 하고, 그 서비스가 죽었을 때의 동작(기본값: 통과)을 모르면 장애 때 아무도 막히지 않은 이유를 찾지 못한다. 두 가지를 직접 세어 보면 둘을 왜 함께 쓰는지가 남는다.

단계

  1. Redis 를 127.0.0.1:6379 에서 디스크에 쓰지 않도록(--save '', --appendonly no) 데몬으로 띄우세요. 그리고 /root/envd-rl/01-redis.txtredis-cli ping 의 출력과 redis-cli config get save 의 출력을 차례로 저장하세요.
  2. /root/envd-rl/runtime/config/edge.yaml 에 도메인 edge 의 규칙 두 개를 쓰세요 — 설명자 plan=free시간당 3번, plan=pro시간당 100번. 그리고 이미지에 든 ratelimit(envoyproxy/ratelimit) 을 띄우세요. 설정은 환경 변수로 줍니다: Redis 127.0.0.1:6379, RUNTIME_ROOT=/root/envd-rl/runtime, RUNTIME_SUBDIRECTORY=., RUNTIME_WATCH_ROOT=false, USE_STATSD=false, 그리고 HTTP 8180·gRPC 8181·디버그 6070 포트(셋 다 127.0.0.1).
  3. 업스트림 python3 /opt/lab/envoy/echo.py 8092 를 띄우고, /root/envd-rl/envoy-a.yaml 로 관리 포트 9983, 리스너 127.0.0.1:10083 인 Envoy 를 띄우세요(--base-id 21). HTTP 필터는 local_ratelimit(stat_prefix local_rl) → ratelimit(도메인 edge, 클러스터 ratelimit 로 gRPC, failure_mode_deny: false, enable_x_ratelimit_headers: DRAFT_VERSION_03) → router 순서입니다. 라우트는 둘 — /local 은 그 라우트에만 로컬 토큰 버킷(토큰 2개·60초마다 2개)을 걸고, /그 라우트에 rate_limits 로 요청 헤더 x-plan 을 설명자 키 plan 으로 만듭니다.
  4. /root/envd-rl/envoy-b.yaml 에 첫 Envoy 와 같되 관리 포트 9984, 리스너 127.0.0.1:10084 인 설정을 쓰고 --base-id 22 로 띄우세요. 그다음 두 Envoy 에 x-plan: pro 요청을 하나씩 보내, 응답의 x-ratelimit-remaining이어서 줄어드는지 보고 두 값을 /root/envd-rl/04-remaining.txta=·b= 두 줄로 적으세요(a 를 먼저 보냅니다).
  5. x-plan: free 요청 여덟 개를 A(10083)·B(10084) 에 번갈아(A 부터) / 로 보내고, 차례대로 /root/envd-rl/05-global.txta1=코드, b1=코드, a2=코드b4=코드 여덟 줄로 적으세요.
  6. 같은 방식으로 여덟 개를 이번에는 /local 로 보내고 /root/envd-rl/06-local.txta1=코드b4=코드 여덟 줄로 적으세요.
  7. ratelimit 프로세스를 멈춘 채 A 로 x-plan: free 요청을 하나 보내고 그 상태 코드를 /root/envd-rl/07-fail.txtdown_code= 로 적으세요. 그 뒤 A 의 통계 cluster.echo.ratelimit.errorcluster.echo.ratelimit.failure_mode_allowed 값을 error=·allowed= 두 줄로 덧붙이고, 서비스를 다시 띄웁니다.
  8. /root/envd-rl/08-report.md 에 네 줄 — global_ok_total=(5단계의 200 개수), local_ok_total=(6단계의 200 개수), fail_open_code=(7단계의 down_code), counted_in=(전역 제한에서 실제로 수를 센 곳: envoy 또는 redis) — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.

참고

셀 곳부터 세운다 — Redis

Redis 를 127.0.0.1:6379 에서 디스크에 쓰지 않도록(--save '', --appendonly no) 데몬으로 띄우세요. 그리고 /root/envd-rl/01-redis.txtredis-cli ping 의 출력과 redis-cli config get save 의 출력을 차례로 저장하세요.

전역 속도 제한 서비스는 상태가 없습니다. 몇 번 왔는지는 전부 뒤의 저장소(Redis)에 두고, 서비스는 창(window)마다 열쇠 하나를 올리기만 합니다. 그래서 서비스를 여러 대 띄워도 한도가 하나로 유지됩니다. 카운터는 창이 지나면 버려질 값이라 디스크에 남길 이유가 없습니다. redis-server --port 6379 --bind 127.0.0.1 --save '' --appendonly no --daemonize yes 처럼 띄웁니다.

속도 제한 서비스를 띄운다

/root/envd-rl/runtime/config/edge.yaml 에 도메인 edge 의 규칙 두 개를 쓰세요 — 설명자 plan=free시간당 3번, plan=pro시간당 100번. 그리고 이미지에 든 ratelimit(envoyproxy/ratelimit) 을 띄우세요. 설정은 환경 변수로 줍니다: Redis 127.0.0.1:6379, RUNTIME_ROOT=/root/envd-rl/runtime, RUNTIME_SUBDIRECTORY=., RUNTIME_WATCH_ROOT=false, USE_STATSD=false, 그리고 HTTP 8180·gRPC 8181·디버그 6070 포트(셋 다 127.0.0.1).

이 서비스는 RUNTIME_ROOT/RUNTIME_SUBDIRECTORY/config/ 아래의 YAML 을 읽습니다. 한 파일이 도메인 하나이고, 설명자는 key·value 가 요청이 보낸 것과 정확히 맞아야 규칙이 걸립니다. 맞는 규칙이 없으면 제한하지 않습니다. 제대로 읽었는지는 디버그 포트의 curl localhost:6070/rlconfig 가 보여 줍니다 — edge.plan_free: unit=HOUR requests_per_unit=3 같은 줄이 나와야 합니다. 띄울 때는 env 변수=값 … setsid --fork nohup ratelimit > 로그 2>&1 </dev/null 모양으로 씁니다.

첫 Envoy 가 요청마다 설명자를 만들어 묻는다

업스트림 python3 /opt/lab/envoy/echo.py 8092 를 띄우고, /root/envd-rl/envoy-a.yaml 로 관리 포트 9983, 리스너 127.0.0.1:10083 인 Envoy 를 띄우세요(--base-id 21). HTTP 필터는 local_ratelimit(stat_prefix local_rl) → ratelimit(도메인 edge, 클러스터 ratelimit 로 gRPC, failure_mode_deny: false, enable_x_ratelimit_headers: DRAFT_VERSION_03) → router 순서입니다. 라우트는 둘 — /local 은 그 라우트에만 로컬 토큰 버킷(토큰 2개·60초마다 2개)을 걸고, /그 라우트에 rate_limits 로 요청 헤더 x-plan 을 설명자 키 plan 으로 만듭니다.

전역 제한 필터는 스스로 세지 않습니다. 라우트(또는 가상 호스트)의 rate_limits 가 요청에서 설명자를 만들고, 필터는 그것을 서비스에 보낼 뿐입니다. rate_limits 를 가상 호스트에 두면 그 호스트의 모든 라우트에 걸리고, 라우트에 빈 목록(rate_limits: [])을 적어도 꺼지지 않습니다(실측) — 그래서 / 라우트에 둡니다. gRPC 로 묻는 클러스터에는 HTTP/2 를 켜야 합니다. x-plan 헤더가 없는 요청은 설명자가 만들어지지 않아 제한 없이 지나갑니다.

두 번째 Envoy 도 같은 서비스를 부른다

/root/envd-rl/envoy-b.yaml 에 첫 Envoy 와 같되 관리 포트 9984, 리스너 127.0.0.1:10084 인 설정을 쓰고 --base-id 22 로 띄우세요. 그다음 두 Envoy 에 x-plan: pro 요청을 하나씩 보내, 응답의 x-ratelimit-remaining이어서 줄어드는지 보고 두 값을 /root/envd-rl/04-remaining.txta=·b= 두 줄로 적으세요(a 를 먼저 보냅니다).

두 Envoy 가 따로 센다면 두 응답의 remaining 이 같은 수에서 시작합니다. 같은 서비스·같은 Redis 열쇠를 올린다면 두 번째가 첫 번째보다 하나 작습니다. 헤더 이름은 대소문자를 가리지 않으니 curl -s -D - -o /dev/null 로 헤더만 받아 찾으세요. 두 Envoy 를 같은 --base-id 로 띄우면 두 번째가 공유 메모리 이름이 겹쳐 뜨지 않습니다.

번갈아 보내도 한도는 하나다

x-plan: free 요청 여덟 개를 A(10083)·B(10084) 에 번갈아(A 부터) / 로 보내고, 차례대로 /root/envd-rl/05-global.txta1=코드, b1=코드, a2=코드b4=코드 여덟 줄로 적으세요.

free 는 시간당 3번입니다. 두 Envoy 가 한도를 나눠 쓰면 여덟 개 가운데 세 개만 200 이고 나머지는 429 여야 합니다 — 어느 Envoy 가 받았는지와 상관없이. 이 시간 창에서 이미 free 를 보낸 적이 있다면 200 이 더 적을 수 있는데, 그것도 전역으로 센다는 증거입니다. Redis 에서 redis-cli --scan --pattern 'edge_plan_free_*' 로 열쇠를 보고 get 해 보면 두 Envoy 의 요청이 한 열쇠에 모여 있습니다.

같은 여덟 개를 로컬 제한으로 보내면

같은 방식으로 여덟 개를 이번에는 /local 로 보내고 /root/envd-rl/06-local.txta1=코드b4=코드 여덟 줄로 적으세요.

/local 라우트에는 전역 제한이 없고 로컬 토큰 버킷(토큰 2개)만 있습니다. 토큰 버킷은 Envoy 한 대 안에 있으므로 A 도 2개, B 도 2개를 줍니다. 전역 결과와 나란히 놓으면 프록시를 늘릴 때 무엇이 달라지는지가 숫자로 보입니다. 버킷은 60초마다 채워지니 다시 해 볼 때는 1분을 기다리세요.

제한 서비스가 죽으면 통과시킨다

ratelimit 프로세스를 멈춘 채 A 로 x-plan: free 요청을 하나 보내고 그 상태 코드를 /root/envd-rl/07-fail.txtdown_code= 로 적으세요. 그 뒤 A 의 통계 cluster.echo.ratelimit.errorcluster.echo.ratelimit.failure_mode_allowed 값을 error=·allowed= 두 줄로 덧붙이고, 서비스를 다시 띄웁니다.

free 는 이미 한도를 다 썼으니, 서비스가 살아 있다면 429 입니다. 서비스가 죽었을 때 failure_mode_deny: false (기본값)는 요청을 통과시킵니다. 속도 제한은 보호 장치라 그것이 고장 났다고 서비스 전체를 막지는 않겠다는 선택입니다. 대신 그 순간은 통계에만 남습니다. 통계 이름의 cluster.echo 는 요청이 향하던 업스트림 클러스터 이름입니다.

대수를 늘리면 한도가 어떻게 되는지 적는다

/root/envd-rl/08-report.md 에 네 줄 — global_ok_total=(5단계의 200 개수), local_ok_total=(6단계의 200 개수), fail_open_code=(7단계의 down_code), counted_in=(전역 제한에서 실제로 수를 센 곳: envoy 또는 redis) — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.

값은 앞 단계 파일에서 옮기세요. 설명 줄에는 '프록시를 세 대로 늘리면 로컬 제한과 전역 제한의 실제 한도가 각각 어떻게 되는가' 와 '두 가지를 함께 두면 무엇이 좋은가(로컬은 제한 서비스를 보호하는 첫 벽)' 를 적어 두세요.