不改代码也能造出故障
한국어 원문으로 표시합니다.
목표
HTTP 필터 사슬에 장애 주입과 속도 제한을 끼워 보고, 순서와 조건과 비율이 결과를 어떻게 바꾸는지 직접 확인한다.
왜 중요한가
필터는 애플리케이션을 건드리지 않고 그 앞에서 무엇인가를 하는 자리다. 그래서 '결제가 느려지면 우리는 어떻게 되나' 같은 질문에 추론 대신 실측으로 답할 수 있다. 다만 이 도구는 잘못 쓰면 그 자체가 장애가 된다 — 조건 없이 켠 장애 주입, 프록시 대수를 빠뜨린 속도 제한 한도, 순서가 뒤집힌 사슬이 전부 실제로 사고가 났던 모양이다. 한 번 손으로 만들어 두면 그 셋을 설정만 보고도 알아본다.
단계
- 업스트림을
8095에ok로 띄우세요./root/envd-filter/f-ok.yaml에 필터가router하나뿐인 설정을 두고(관리 9976, 리스너127.0.0.1:10076, 클러스터origin),/root/envd-filter/f-badorder.yaml에는router뒤에fault필터를 둔 설정을 두세요. 두 파일을 각각envoy --mode validate로 검사해/root/envd-filter/01-order.txt에ok_rc=와bad_rc=두 줄을 적으세요. /root/envd-filter/f-abort.yaml을 만드세요 —router앞에fault필터를 두고abort로 418 을, 조건은 요청 헤더x-fault: yes입니다. 띄운 뒤 헤더를 붙인 요청과 붙이지 않은 요청을 각각 보내/root/envd-filter/02-abort.txt에with=와without=두 줄(각각의 HTTP 코드)을 적으세요./root/envd-filter/f-delay.yaml을 만드세요 —fault필터의delay.fixed_delay를 2초로 두고 조건은 헤더x-slow: yes입니다. 띄운 뒤 헤더를 붙인 요청과 붙이지 않은 요청의 소요 시간을 재어/root/envd-filter/03-delay.txt에slow_ms=·fast_ms=·gap_ms=세 줄을 적으세요(밀리초 정수,gap_ms는 두 값의 차)./root/envd-filter/f-half.yaml을 만드세요 — 조건 없이abort로 503 을 주되percentage를 50%로 둡니다. 띄운 뒤 20번 요청해/root/envd-filter/04-percentage.txt에total=20,aborted=(503 을 받은 횟수),passed=(200 을 받은 횟수) 세 줄을 적으세요./root/envd-filter/f-lrl.yaml을 만드세요 —fault대신local_ratelimit필터를router앞에 두고,stat_prefix는lrl, 토큰은max_tokens3 ·tokens_per_fill3 ·fill_interval300s 이며 켜짐과 강제 모두 100% 입니다. 띄운 뒤 6번 요청해/root/envd-filter/05-ratelimit.txt에ok=·limited=·codes=세 줄을 적으세요(codes는 받은 코드들을 공백으로)./root/envd-filter/f-perroute.yaml을 만드세요 — 전역local_ratelimit은filter_enabled를 0% 로 두어 꺼 두고, 라우트/tight에는typed_per_filter_config로 토큰 1개(stat_prefix: tight),/loose에는 토큰 5개(stat_prefix: loose) 를 줍니다. 띄운 뒤/tight로 3번,/loose로 3번 요청해/root/envd-filter/06-perroute.txt에tight_ok=·tight_limited=·loose_ok=·loose_limited=네 줄을 적으세요.- 지금 떠 있는 Envoy 의
/stats에서 속도 제한 관련 통계를 뽑아/root/envd-filter/07-stats.txt에tight_rate_limited=·loose_rate_limited=·enabled_total=세 줄을 적으세요(앞 둘은 각stat_prefix의rate_limited값, 마지막은 두stat_prefix의enabled값을 더한 수). /root/envd-filter/08-report.md에router_last=(종단 필터가 마지막이어야 하면 yes),abort_status=(2단계에서 받은 코드),delay_ms=(3단계의gap_ms),tight_allowed=(6단계에서/tight가 통과시킨 횟수) 네 줄을 적고, 그 아래 배운 것을 네 줄 이상 적으세요.
참고
- Envoy 를 띄울 때는
setsid --fork nohup envoy -c <파일> --log-level warn --concurrency 1 > <로그> 2>&1 </dev/null를 쓰고, 다시 띄우기 전에는pkill -x envoy로 정리하세요. - 기동은 고정
sleep이 아니라/ready가 LIVE 를 돌려줄 때까지 도는 루프로 기다립니다. - 상태 코드만 받으려면
curl -s -o /dev/null -w '%{http_code}', 소요 시간은-w '%{time_total}'을 씁니다. 시간은 초 단위 소수라 밀리초로 바꾸려면 곱해야 합니다. - 업스트림은
python3 /opt/lab/envoy/upstream.py <포트> ok로 띄웁니다. - 흔한 실수 —
router를 사슬 맨 앞이나 중간에 두는 것. 그 뒤의 필터는 영원히 실행되지 않으므로 Envoy 가 설정을 통째로 거절합니다. - 흔한 실수 —
fill_interval을 짧게 두는 것. 실습 중에 토큰이 다시 차올라 결과가 흔들립니다.
사슬의 마지막은 정해져 있다
업스트림을 8095 에 ok 로 띄우세요. /root/envd-filter/f-ok.yaml 에 필터가 router 하나뿐인 설정을 두고(관리 9976, 리스너 127.0.0.1:10076, 클러스터 origin), /root/envd-filter/f-badorder.yaml 에는 router 뒤에 fault 필터를 둔 설정을 두세요. 두 파일을 각각 envoy --mode validate 로 검사해 /root/envd-filter/01-order.txt 에 ok_rc= 와 bad_rc= 두 줄을 적으세요.
필터 사슬의 마지막 필터는 요청을 실제로 업스트림에 내보내는 역할을 합니다. 그것이 router 이고, 이런 필터를 종단 필터라고 부릅니다. 종단 필터 뒤에 무엇을 두면 그 필터는 영원히 실행되지 않으므로, Envoy 는 그 상태를 실행 중에 두지 않고 설정을 읽는 시점에 거절합니다. 거절 메시지에 이유가 그대로 나오니 읽어 두세요.
조건이 맞는 요청만 끊는다
/root/envd-filter/f-abort.yaml 을 만드세요 — router 앞에 fault 필터를 두고 abort 로 418 을, 조건은 요청 헤더 x-fault: yes 입니다. 띄운 뒤 헤더를 붙인 요청과 붙이지 않은 요청을 각각 보내 /root/envd-filter/02-abort.txt 에 with= 와 without= 두 줄(각각의 HTTP 코드)을 적으세요.
장애 주입은 '이 서비스가 죽으면 우리 서비스는 어떻게 되나' 를 운영 중에 확인하는 도구입니다. 그래서 조건을 거는 것이 핵심입니다 — 조건 없이 켜면 모든 사용자가 대상이 됩니다. 헤더 조건을 쓰면 테스트 도구만 그 헤더를 붙여 자기 요청에만 장애를 겪게 할 수 있습니다. 418 은 실제 서비스에서는 쓰지 않는 코드라 실험용으로 눈에 잘 띕니다.
끊는 대신 느리게 만든다
/root/envd-filter/f-delay.yaml 을 만드세요 — fault 필터의 delay.fixed_delay 를 2초로 두고 조건은 헤더 x-slow: yes 입니다. 띄운 뒤 헤더를 붙인 요청과 붙이지 않은 요청의 소요 시간을 재어 /root/envd-filter/03-delay.txt 에 slow_ms=·fast_ms=·gap_ms= 세 줄을 적으세요(밀리초 정수, gap_ms 는 두 값의 차).
느려지는 것은 끊기는 것보다 다루기 어렵습니다. 끊기면 즉시 알지만, 느려지면 연결이 쌓이고 스레드가 묶이고 그다음에야 장애가 됩니다. 그래서 타임아웃과 서킷 브레이커가 제대로 걸려 있는지 확인하려면 끊는 실험이 아니라 느리게 하는 실험이 필요합니다. 소요 시간은 curl -w '%{time_total}' 로 잽니다 — 초 단위 소수이므로 밀리초로 바꾸려면 곱해야 합니다.
일부만 골라 끊는다
/root/envd-filter/f-half.yaml 을 만드세요 — 조건 없이 abort 로 503 을 주되 percentage 를 50%로 둡니다. 띄운 뒤 20번 요청해 /root/envd-filter/04-percentage.txt 에 total=20, aborted=(503 을 받은 횟수), passed=(200 을 받은 횟수) 세 줄을 적으세요.
비율은 요청마다 독립적으로 뽑는 확률이지 '스무 번 중 정확히 열 번' 이 아닙니다. 그래서 같은 설정으로 다시 세면 숫자가 달라집니다. 카나리 검증에서 '비율이 안 맞는다' 는 신고가 나오는 이유와 같습니다. 운영에서 장애 주입을 쓸 때 비율을 아주 낮게 시작하는 것도 이 성질 때문입니다 — 100분의 1이라도 요청이 많으면 충분한 표본이 됩니다.
프록시 안에서 토큰을 센다
/root/envd-filter/f-lrl.yaml 을 만드세요 — fault 대신 local_ratelimit 필터를 router 앞에 두고, stat_prefix 는 lrl, 토큰은 max_tokens 3 · tokens_per_fill 3 · fill_interval 300s 이며 켜짐과 강제 모두 100% 입니다. 띄운 뒤 6번 요청해 /root/envd-filter/05-ratelimit.txt 에 ok=·limited=·codes= 세 줄을 적으세요(codes 는 받은 코드들을 공백으로).
지역 속도 제한은 그 프록시 안에서만 토큰을 셉니다. 프록시가 열 대면 한도가 사실상 열 배가 되는 것이라, 전체 한도를 지키려면 대수로 나눠 설정하거나 외부 속도 제한 서비스를 써야 합니다. 대신 외부에 물어보지 않으므로 지연이 없고 그 서비스가 죽어도 영향이 없습니다. 한도를 넘긴 요청은 429 를 받습니다. fill_interval 을 길게 두면 실습 중에 토큰이 다시 차오르지 않아 결과가 안정됩니다.
같은 필터를 라우트마다 다르게 설정한다
/root/envd-filter/f-perroute.yaml 을 만드세요 — 전역 local_ratelimit 은 filter_enabled 를 0% 로 두어 꺼 두고, 라우트 /tight 에는 typed_per_filter_config 로 토큰 1개(stat_prefix: tight), /loose 에는 토큰 5개(stat_prefix: loose) 를 줍니다. 띄운 뒤 /tight 로 3번, /loose 로 3번 요청해 /root/envd-filter/06-perroute.txt 에 tight_ok=·tight_limited=·loose_ok=·loose_limited= 네 줄을 적으세요.
필터는 리스너 단위로 붙지만, 설정은 라우트나 가상 호스트 단위로 덮어쓸 수 있습니다 — 그것이 typed_per_filter_config 입니다. 로그인 경로만 빡빡하게, 조회 경로는 느슨하게 같은 요구가 이렇게 풀립니다. 전역 설정을 꺼 두고 라우트에서만 켜는 것도 흔한 모양입니다. 열쇠는 필터의 이름이고, 값 안에는 그 필터의 설정을 통째로 다시 적습니다. 각 라우트의 stat_prefix 를 다르게 두면 통계에서도 갈라 볼 수 있습니다.
필터가 남기는 숫자를 읽는다
지금 떠 있는 Envoy 의 /stats 에서 속도 제한 관련 통계를 뽑아 /root/envd-filter/07-stats.txt 에 tight_rate_limited=·loose_rate_limited=·enabled_total= 세 줄을 적으세요(앞 둘은 각 stat_prefix 의 rate_limited 값, 마지막은 두 stat_prefix 의 enabled 값을 더한 수).
필터는 자기 이름의 통계를 남깁니다. 지역 속도 제한의 이름은 <그 자리의 stat_prefix>.http_local_rate_limit.<항목> 모양이고, 항목에는 enabled(대상이 된 요청 수)·rate_limited(실제로 막힌 수)·ok(통과한 수)가 있습니다. 운영에서 중요한 것은 막힌 수 자체가 아니라 막힌 수와 대상이 된 수의 비율입니다 — 그 비율이 계속 높으면 한도가 현실과 맞지 않는 것입니다. 이름을 정확히 모르면 curl -s localhost:<admin>/stats | grep rate_limit 으로 먼저 찾아보세요.
필터 설계 메모를 남긴다
/root/envd-filter/08-report.md 에 router_last=(종단 필터가 마지막이어야 하면 yes), abort_status=(2단계에서 받은 코드), delay_ms=(3단계의 gap_ms), tight_allowed=(6단계에서 /tight 가 통과시킨 횟수) 네 줄을 적고, 그 아래 배운 것을 네 줄 이상 적으세요.
설명 줄에는 각 장치를 언제 쓰고 언제 쓰면 안 되는지를 적으세요. 예를 들어 장애 주입은 조건을 걸어 쓴다, 지역 속도 제한은 프록시 대수만큼 한도가 늘어난다 같은 것입니다. 값은 앞 단계 파일에서 가져오세요.