VirtualService をルート表に変換し、順序と再試行を確かめる
한국어 원문으로 표시합니다.
목표
VirtualService 가 Envoy 라우트 표로 번역되는 규칙을 따라 손으로 표를 세우고, Host 헤더·첫 매치 우선·가중치·재시도를 요청으로 확인한다.
왜 중요한가
VirtualService 의 실수는 대개 규칙 하나가 아니라 규칙 사이의 순서와 겹침에서 나온다. 정적 분석은 그것을 잡지 못하므로, 번역 규칙을 알고 Envoy 에서 어떻게 동작할지 그릴 줄 알아야 변경 리뷰에서 걸러 낼 수 있다.
단계
/root/ist2-route/dr.yaml(DestinationRulereviews, hostreviews.default.svc.cluster.local, 서브셋v1·v2·broken— 라벨version이 각각 같은 이름)과/root/ist2-route/vs.yaml(VirtualServicereviews, hosts[reviews])을 쓰세요.vs.yaml의http는 이 순서로 네 개입니다 —jason(헤더end-user가 정확히jason이면 v2),api(경로 접두사/api면 v1),flaky(경로 접두사/flaky면 broken,retries: {attempts: 3, retryOn: 5xx},timeout: 2s),default(v1 에weight75, v2 에 25).istioctl analyze --use-kube=false vs.yaml dr.yaml의 출력과 종료 코드를/root/ist2-route/01-analyze.txt에 담으세요(마지막 줄rc=0).- 서비스
reviews는 네임스페이스default, 포트 9080 입니다. 이 VirtualService 가 사이드카에서 만드는 라우트 표의 이름들을/root/ist2-route/02-names.txt에 세 줄로 적으세요 —route_config=(라우트 표 이름),virtual_host=(가상 호스트 이름),domains=(그 가상 호스트의 도메인 중 짧은 이름 네 가지를 쉼표로, 순서 무관). /root/ist2-route/route.yaml에 Envoy 설정을 쓰세요 — 관리 포트9984, 리스너127.0.0.1:10084, 2단계의 라우트 표 이름·가상 호스트 이름·domains 네 개. 라우트는 이름을 붙여 이 순서로 셋 —jason(경로/+ 헤더end-user정확히jason→outbound|9080|v2|reviews.default.svc.cluster.local),api(접두사/api→outbound|9080|v1|reviews.default.svc.cluster.local),default(접두사/→outbound|9080|v1|reviews.default.svc.cluster.local). 클러스터는 v1(127.0.0.1:8105)·v2(127.0.0.1:8106) 둘. 업스트림 둘을ok로 띄우고 Envoy 를 띄운 뒤 Host 헤더 세 가지로/를 불러/root/ist2-route/03-hosts.txt에reviews=,reviews.default.svc.cluster.local=,ratings=세 줄로 HTTP 코드를 적으세요.route.yaml로 떠 있는 Envoy 에 Hostreviews로 세 요청을 보내고, 응답 본문의 포트로 서브셋을 가려/root/ist2-route/04-match.txt에 적으세요 —anonymous=(헤더 없이/),jason=(end-user: jason으로/),api_as_jason=(end-user: jason으로/api/list). 값은v1또는v2입니다.vs.yaml에서default규칙을 맨 앞으로 옮긴/root/ist2-route/vs-bad.yaml을 만들고istioctl analyze --use-kube=false vs-bad.yaml dr.yaml의 종료 코드를 확인하세요. 같은 실수를 Envoy 로:route.yaml에서default라우트를 맨 앞으로 옮긴/root/ist2-route/route-bad.yaml로 Envoy 를 다시 띄우고end-user: jason으로/를 불러 보세요./root/ist2-route/05-order.txt에analyze_rc=(vs-bad 분석의 종료 코드),jason_after=(route-bad 에서 jason 이 간 서브셋) 두 줄을 적으세요. 확인한 뒤에는route.yaml로 되돌려 띄워 두세요.route.yaml을/root/ist2-route/route-split.yaml로 복사하고default라우트만weighted_clusters로 바꾸세요(v1 75, v2 25 — VirtualService 의 default 규칙 그대로).--concurrency 1로 다시 띄운 뒤 Hostreviews로/를 정확히 40 번 보내/root/ist2-route/06-split.txt에v1=,v2=,total=세 줄을 적으세요.route-split.yaml을/root/ist2-route/route-retry.yaml로 복사하고 두 가지를 더하세요 — 클러스터outbound|9080|broken|reviews.default.svc.cluster.local(127.0.0.1:8114, 늘 503 을 내는 업스트림), 그리고default앞에flaky라우트(접두사/flaky→ 그 클러스터,timeout: 2s,retry_policy: {retry_on: 5xx, num_retries: 3}). 업스트림을8114에fail로 띄우고 Envoy 를 다시 띄운 뒤/flaky를 한 번만 부르세요./root/ist2-route/07-retry.txt에status=(HTTP 코드),upstream_rq_retry=,upstream_rq_total=(broken 클러스터의 두 통계 값)을 적으세요./root/ist2-route/08-report.md에route_config=,virtual_host=,first_match_wins=(5단계에서 본 대로 처음 맞는 라우트 하나만 쓰이면yes),retry_attempts=(VirtualService 의 attempts 값) 네 줄을 적고, 그 아래-로 시작하는 설명을 네 줄 이상 적으세요.
참고
- 이 파드에는 진짜 istiod 도 진짜 사이드카도 없습니다. 그래서
istioctl proxy-config로 실제 생성물을 볼 수 없고, 번역 규칙을 알고 손으로 등가 Envoy 설정을 만들어 동작을 확인합니다. 같은 규칙이 운영 클러스터의proxy-config출력에 그대로 보입니다. - 모든 요청에
-H 'Host: reviews'를 붙이세요. 가상 호스트는 Host 헤더로 고릅니다. - 분배와 재시도를 세는 6·7단계는 Envoy 를
--concurrency 1로 띄우세요. 통계는 누적되므로 다시 띄운 직후에 셉니다. - Envoy 를 띄울 때는
setsid --fork nohup envoy -c <파일> --log-level warn > <로그> 2>&1 </dev/null로 셸에서 완전히 떼어 놓으세요. 다시 띄우기 전에는pkill -x envoy로 정리합니다 (pkill -f 'envoy -c'는 그 문자열이 든 셸 자신까지 죽입니다). - 업스트림 흉내용 서버가 이미지에 있습니다:
python3 /opt/lab/envoy/upstream.py <포트> ok|fail|slow. 응답 본문은<모드>:<포트> <경로>입니다. - 설정을 고친 뒤에는 띄우기 전에
envoy --mode validate -c <파일>로 먼저 거르세요. 클러스터 이름에|가 들어가므로 YAML 에서는 반드시 따옴표로 감쌉니다.
라우트 네 개짜리 VirtualService 를 쓰고 분석을 통과시킨다
/root/ist2-route/dr.yaml(DestinationRule reviews, host reviews.default.svc.cluster.local, 서브셋 v1·v2·broken — 라벨 version 이 각각 같은 이름)과 /root/ist2-route/vs.yaml(VirtualService reviews, hosts [reviews])을 쓰세요. vs.yaml 의 http 는 이 순서로 네 개입니다 — jason(헤더 end-user 가 정확히 jason 이면 v2), api(경로 접두사 /api 면 v1), flaky(경로 접두사 /flaky 면 broken, retries: {attempts: 3, retryOn: 5xx}, timeout: 2s), default(v1 에 weight 75, v2 에 25). istioctl analyze --use-kube=false vs.yaml dr.yaml 의 출력과 종료 코드를 /root/ist2-route/01-analyze.txt 에 담으세요(마지막 줄 rc=0).
VirtualService 는 '어디로 보낼지', DestinationRule 은 '보낸 뒤 어떻게 다룰지와 서브셋의 정의' 입니다. analyze 는 두 파일을 함께 읽어 VS 가 가리키는 서브셋이 DR 에 있는지 같은 참조 관계를 봅니다. http 항목마다 name 을 붙여 두면 Envoy 쪽 라우트에도 같은 이름이 붙어 나중에 찾기 쉽습니다.
라우트 표·가상 호스트·도메인의 이름을 도출한다
서비스 reviews 는 네임스페이스 default, 포트 9080 입니다. 이 VirtualService 가 사이드카에서 만드는 라우트 표의 이름들을 /root/ist2-route/02-names.txt 에 세 줄로 적으세요 — route_config=(라우트 표 이름), virtual_host=(가상 호스트 이름), domains=(그 가상 호스트의 도메인 중 짧은 이름 네 가지를 쉼표로, 순서 무관).
나가는 쪽에서 사이드카는 포트마다 라우트 표를 하나 둡니다. 같은 포트 9080 을 쓰는 서비스가 여럿이면 한 표 안에 가상 호스트가 서비스마다 하나씩 들어가고, 요청의 Host 헤더로 고릅니다. 그래서 표 이름은 포트, 가상 호스트 이름은 FQDN:포트 입니다. 같은 네임스페이스의 앱은 reviews, reviews.default 처럼 짧게도 부르므로 domains 에는 FQDN 을 뒤에서부터 잘라 낸 이름들이 함께 들어갑니다(공식 문서의 proxy-config 예시 참고).
그 이름으로 라우트 표를 세우고 Host 헤더로 고르는 것을 본다
/root/ist2-route/route.yaml 에 Envoy 설정을 쓰세요 — 관리 포트 9984, 리스너 127.0.0.1:10084, 2단계의 라우트 표 이름·가상 호스트 이름·domains 네 개. 라우트는 이름을 붙여 이 순서로 셋 — jason(경로 / + 헤더 end-user 정확히 jason → outbound|9080|v2|reviews.default.svc.cluster.local), api(접두사 /api → outbound|9080|v1|reviews.default.svc.cluster.local), default(접두사 / → outbound|9080|v1|reviews.default.svc.cluster.local). 클러스터는 v1(127.0.0.1:8105)·v2(127.0.0.1:8106) 둘. 업스트림 둘을 ok 로 띄우고 Envoy 를 띄운 뒤 Host 헤더 세 가지로 / 를 불러 /root/ist2-route/03-hosts.txt 에 reviews=, reviews.default.svc.cluster.local=, ratings= 세 줄로 HTTP 코드를 적으세요.
라우트 표를 고르는 것은 리스너(포트)이고, 표 안에서 가상 호스트를 고르는 것은 Host 헤더입니다. 어느 가상 호스트의 domains 에도 없는 Host 로 오면 라우트를 찾지 못해 404 가 납니다 — 사이드카가 모르는 서비스 이름을 부를 때의 모습입니다. curl -H 'Host: reviews' localhost:10084/ 처럼 헤더를 바꿔 가며 부르세요. 헤더 매치는 match.headers 에 string_match: {exact: …} 로 씁니다.
헤더 매치와 경로 매치가 겹칠 때 누가 이기는가
route.yaml 로 떠 있는 Envoy 에 Host reviews 로 세 요청을 보내고, 응답 본문의 포트로 서브셋을 가려 /root/ist2-route/04-match.txt 에 적으세요 — anonymous=(헤더 없이 /), jason=(end-user: jason 으로 /), api_as_jason=(end-user: jason 으로 /api/list). 값은 v1 또는 v2 입니다.
Envoy 는 라우트를 위에서부터 보고 처음 맞는 하나만 씁니다. api_as_jason 은 jason 규칙에도 api 규칙에도 맞는데, 어느 쪽이 먼저 적혀 있는지가 답을 정합니다. 본문이 ok:8105 면 v1, ok:8106 이면 v2 입니다.
catch-all 을 앞에 두면 — 분석은 조용하고 트래픽은 틀린다
vs.yaml 에서 default 규칙을 맨 앞으로 옮긴 /root/ist2-route/vs-bad.yaml 을 만들고 istioctl analyze --use-kube=false vs-bad.yaml dr.yaml 의 종료 코드를 확인하세요. 같은 실수를 Envoy 로: route.yaml 에서 default 라우트를 맨 앞으로 옮긴 /root/ist2-route/route-bad.yaml 로 Envoy 를 다시 띄우고 end-user: jason 으로 / 를 불러 보세요. /root/ist2-route/05-order.txt 에 analyze_rc=(vs-bad 분석의 종료 코드), jason_after=(route-bad 에서 jason 이 간 서브셋) 두 줄을 적으세요. 확인한 뒤에는 route.yaml 로 되돌려 띄워 두세요.
default 는 조건이 없으므로 모든 요청에 맞습니다. 그것이 맨 앞에 있으면 뒤의 규칙은 절대 쓰이지 않습니다. 그런데 analyze 는 각 규칙이 참조하는 것이 존재하는지를 볼 뿐, 규칙 사이의 가림은 보지 않아서 깨끗하다고 말합니다. 그래서 VirtualService 를 고칠 때는 '넓은 것은 뒤로' 를 사람이 지켜야 합니다. yq '.spec.http |= [.[3], .[0], .[1], .[2]]' 처럼 목록 순서를 바꿀 수 있습니다.
weight 는 weighted_clusters 가 된다 — 마흔 번 센다
route.yaml 을 /root/ist2-route/route-split.yaml 로 복사하고 default 라우트만 weighted_clusters 로 바꾸세요(v1 75, v2 25 — VirtualService 의 default 규칙 그대로). --concurrency 1 로 다시 띄운 뒤 Host reviews 로 / 를 정확히 40 번 보내 /root/ist2-route/06-split.txt 에 v1=, v2=, total= 세 줄을 적으세요.
가중치는 요청마다 주사위를 굴리는 것이라 마흔 번에 정확히 30 대 10 이 나오지 않습니다. 합이 40 이고 v1 이 더 많으면 제대로 된 것입니다. Istio 에서 카나리 비율을 1% 로 걸고 요청 몇십 개로 확인하면 v2 가 한 번도 안 나올 수 있다는 것도 같은 이유입니다.
retries 와 timeout 이 retry_policy 가 된다 — 몇 번 두드리는지 센다
route-split.yaml 을 /root/ist2-route/route-retry.yaml 로 복사하고 두 가지를 더하세요 — 클러스터 outbound|9080|broken|reviews.default.svc.cluster.local(127.0.0.1:8114, 늘 503 을 내는 업스트림), 그리고 default 앞에 flaky 라우트(접두사 /flaky → 그 클러스터, timeout: 2s, retry_policy: {retry_on: 5xx, num_retries: 3}). 업스트림을 8114 에 fail 로 띄우고 Envoy 를 다시 띄운 뒤 /flaky 를 한 번만 부르세요. /root/ist2-route/07-retry.txt 에 status=(HTTP 코드), upstream_rq_retry=, upstream_rq_total=(broken 클러스터의 두 통계 값)을 적으세요.
VirtualService 의 retries.attempts 는 Envoy 의 num_retries, retryOn 은 retry_on, timeout 은 라우트의 timeout 이 됩니다. 원래 요청 하나에 재시도가 최대 attempts 번 붙으므로 업스트림은 1+attempts 번 두드려집니다 — 장애 난 서비스에 재시도가 부하를 몇 배로 얹는 이유입니다. 통계는 누적이라 다시 띄운 직후 한 번만 불러야 숫자가 깔끔합니다. /stats?filter= 로 broken 클러스터의 두 값을 고르세요.
VirtualService 번역표로 정리한다
/root/ist2-route/08-report.md 에 route_config=, virtual_host=, first_match_wins=(5단계에서 본 대로 처음 맞는 라우트 하나만 쓰이면 yes), retry_attempts=(VirtualService 의 attempts 값) 네 줄을 적고, 그 아래 - 로 시작하는 설명을 네 줄 이상 적으세요.
값은 앞 단계 파일에서 옮기세요. 설명 줄에는 'VirtualService 를 고칠 때 무엇을 확인하겠다' 를 적으면 이 표가 다음 변경 검토 때 쓸모 있습니다.