Envoy 二台、カウンター一つ
한국어 원문으로 표시합니다.
목표
진짜 속도 제한 서비스(envoyproxy/ratelimit)와 Redis 를 띄우고, 같은 서비스를 부르는 Envoy 두 대가 한도 하나를 나눠 쓰는 것을 로컬 제한과 나란히 센다.
왜 중요한가
로컬 제한만 쓰면 프록시를 늘릴 때마다 한도가 늘어난다. 오토스케일링 앞에서는 막으려던 폭주가 올수록 문이 넓어진다. 전역 제한은 그 문제를 풀지만 요청마다 서비스 하나를 더 거치게 하고, 그 서비스가 죽었을 때의 동작(기본값: 통과)을 모르면 장애 때 아무도 막히지 않은 이유를 찾지 못한다. 두 가지를 직접 세어 보면 둘을 왜 함께 쓰는지가 남는다.
단계
- Redis 를
127.0.0.1:6379에서 디스크에 쓰지 않도록(--save '',--appendonly no) 데몬으로 띄우세요. 그리고/root/envd-rl/01-redis.txt에redis-cli ping의 출력과redis-cli config get save의 출력을 차례로 저장하세요. /root/envd-rl/runtime/config/edge.yaml에 도메인edge의 규칙 두 개를 쓰세요 — 설명자plan=free는 시간당 3번,plan=pro는 시간당 100번. 그리고 이미지에 든ratelimit(envoyproxy/ratelimit) 을 띄우세요. 설정은 환경 변수로 줍니다: Redis127.0.0.1:6379,RUNTIME_ROOT=/root/envd-rl/runtime,RUNTIME_SUBDIRECTORY=.,RUNTIME_WATCH_ROOT=false,USE_STATSD=false, 그리고 HTTP8180·gRPC8181·디버그6070포트(셋 다127.0.0.1).- 업스트림
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_prefixlocal_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으로 만듭니다. /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.txt에a=·b=두 줄로 적으세요(a 를 먼저 보냅니다).x-plan: free요청 여덟 개를 A(10083)·B(10084) 에 번갈아(A 부터)/로 보내고, 차례대로/root/envd-rl/05-global.txt에a1=코드,b1=코드,a2=코드…b4=코드여덟 줄로 적으세요.- 같은 방식으로 여덟 개를 이번에는
/local로 보내고/root/envd-rl/06-local.txt에a1=코드…b4=코드여덟 줄로 적으세요. ratelimit프로세스를 멈춘 채 A 로x-plan: free요청을 하나 보내고 그 상태 코드를/root/envd-rl/07-fail.txt에down_code=로 적으세요. 그 뒤 A 의 통계cluster.echo.ratelimit.error와cluster.echo.ratelimit.failure_mode_allowed값을error=·allowed=두 줄로 덧붙이고, 서비스를 다시 띄웁니다./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) — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.
참고
ratelimit과redis-server는 이미지에 들어 있습니다. 둘 다127.0.0.1에만 묶으세요.- Envoy 를 두 대 띄웁니다.
--base-id를 서로 다르게(21·22) 주고--concurrency 1을 붙이세요. 하나만 끌 때는curl -X POST localhost:<관리포트>/quitquitquit입니다. - free 한도는 시간 창입니다. 같은 시간 창 안에서 다시 해 보면 200 이 더 적게 나오는데, 그것도 전역으로 센다는 증거입니다. 로컬 버킷은 60초마다 다시 찹니다.
- 7단계에서 A·B 를 다시 띄우지 마세요. 통계는 프로세스 안에 있어서 다시 띄우면 0 으로 돌아갑니다.
- 흔한 실수 —
rate_limits를 가상 호스트에 두는 것. 그러면/local에도 전역 제한이 걸려 로컬 제한의 결과가 섞입니다.
셀 곳부터 세운다 — Redis
Redis 를 127.0.0.1:6379 에서 디스크에 쓰지 않도록(--save '', --appendonly no) 데몬으로 띄우세요. 그리고 /root/envd-rl/01-redis.txt 에 redis-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.txt 에 a=·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.txt 에 a1=코드, 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.txt 에 a1=코드 … b4=코드 여덟 줄로 적으세요.
/local 라우트에는 전역 제한이 없고 로컬 토큰 버킷(토큰 2개)만 있습니다. 토큰 버킷은 Envoy 한 대 안에 있으므로 A 도 2개, B 도 2개를 줍니다. 전역 결과와 나란히 놓으면 프록시를 늘릴 때 무엇이 달라지는지가 숫자로 보입니다. 버킷은 60초마다 채워지니 다시 해 볼 때는 1분을 기다리세요.
제한 서비스가 죽으면 통과시킨다
ratelimit 프로세스를 멈춘 채 A 로 x-plan: free 요청을 하나 보내고 그 상태 코드를 /root/envd-rl/07-fail.txt 에 down_code= 로 적으세요. 그 뒤 A 의 통계 cluster.echo.ratelimit.error 와 cluster.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) — 을 적고 그 아래 배운 것을 네 줄 이상 적으세요.
값은 앞 단계 파일에서 옮기세요. 설명 줄에는 '프록시를 세 대로 늘리면 로컬 제한과 전역 제한의 실제 한도가 각각 어떻게 되는가' 와 '두 가지를 함께 두면 무엇이 좋은가(로컬은 제한 서비스를 보호하는 첫 벽)' 를 적어 두세요.