LabHub
배우기 러닝패스 코스

Istio深化 — なぜそう流れるのか

VirtualService をルート表に変換し、順序と再試行を確かめる

LabHub 에서 이어서 보기

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

목표

VirtualService 가 Envoy 라우트 표로 번역되는 규칙을 따라 손으로 표를 세우고, Host 헤더·첫 매치 우선·가중치·재시도를 요청으로 확인한다.

왜 중요한가

VirtualService 의 실수는 대개 규칙 하나가 아니라 규칙 사이의 순서와 겹침에서 나온다. 정적 분석은 그것을 잡지 못하므로, 번역 규칙을 알고 Envoy 에서 어떻게 동작할지 그릴 줄 알아야 변경 리뷰에서 걸러 낼 수 있다.

단계

  1. /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.yamlhttp 는 이 순서로 네 개입니다 — 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).
  2. 서비스 reviews 는 네임스페이스 default, 포트 9080 입니다. 이 VirtualService 가 사이드카에서 만드는 라우트 표의 이름들을 /root/ist2-route/02-names.txt 에 세 줄로 적으세요 — route_config=(라우트 표 이름), virtual_host=(가상 호스트 이름), domains=(그 가상 호스트의 도메인 중 짧은 이름 네 가지를 쉼표로, 순서 무관).
  3. /root/ist2-route/route.yaml 에 Envoy 설정을 쓰세요 — 관리 포트 9984, 리스너 127.0.0.1:10084, 2단계의 라우트 표 이름·가상 호스트 이름·domains 네 개. 라우트는 이름을 붙여 이 순서로 셋 — jason(경로 / + 헤더 end-user 정확히 jasonoutbound|9080|v2|reviews.default.svc.cluster.local), api(접두사 /apioutbound|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.txtreviews=, reviews.default.svc.cluster.local=, ratings= 세 줄로 HTTP 코드를 적으세요.
  4. 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 입니다.
  5. 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.txtanalyze_rc=(vs-bad 분석의 종료 코드), jason_after=(route-bad 에서 jason 이 간 서브셋) 두 줄을 적으세요. 확인한 뒤에는 route.yaml 로 되돌려 띄워 두세요.
  6. 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.txtv1=, v2=, total= 세 줄을 적으세요.
  7. 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}). 업스트림을 8114fail 로 띄우고 Envoy 를 다시 띄운 뒤 /flaky한 번만 부르세요. /root/ist2-route/07-retry.txtstatus=(HTTP 코드), upstream_rq_retry=, upstream_rq_total=(broken 클러스터의 두 통계 값)을 적으세요.
  8. /root/ist2-route/08-report.mdroute_config=, virtual_host=, first_match_wins=(5단계에서 본 대로 처음 맞는 라우트 하나만 쓰이면 yes), retry_attempts=(VirtualService 의 attempts 값) 네 줄을 적고, 그 아래 - 로 시작하는 설명을 네 줄 이상 적으세요.

참고

라우트 네 개짜리 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.yamlhttp 는 이 순서로 네 개입니다 — 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 정확히 jasonoutbound|9080|v2|reviews.default.svc.cluster.local), api(접두사 /apioutbound|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.txtreviews=, reviews.default.svc.cluster.local=, ratings= 세 줄로 HTTP 코드를 적으세요.

라우트 표를 고르는 것은 리스너(포트)이고, 표 안에서 가상 호스트를 고르는 것은 Host 헤더입니다. 어느 가상 호스트의 domains 에도 없는 Host 로 오면 라우트를 찾지 못해 404 가 납니다 — 사이드카가 모르는 서비스 이름을 부를 때의 모습입니다. curl -H 'Host: reviews' localhost:10084/ 처럼 헤더를 바꿔 가며 부르세요. 헤더 매치는 match.headersstring_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_jasonjason 규칙에도 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.txtanalyze_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.txtv1=, 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}). 업스트림을 8114fail 로 띄우고 Envoy 를 다시 띄운 뒤 /flaky한 번만 부르세요. /root/ist2-route/07-retry.txtstatus=(HTTP 코드), upstream_rq_retry=, upstream_rq_total=(broken 클러스터의 두 통계 값)을 적으세요.

VirtualService 의 retries.attempts 는 Envoy 의 num_retries, retryOnretry_on, timeout 은 라우트의 timeout 이 됩니다. 원래 요청 하나에 재시도가 최대 attempts 번 붙으므로 업스트림은 1+attempts 번 두드려집니다 — 장애 난 서비스에 재시도가 부하를 몇 배로 얹는 이유입니다. 통계는 누적이라 다시 띄운 직후 한 번만 불러야 숫자가 깔끔합니다. /stats?filter= 로 broken 클러스터의 두 값을 고르세요.

VirtualService 번역표로 정리한다

/root/ist2-route/08-report.mdroute_config=, virtual_host=, first_match_wins=(5단계에서 본 대로 처음 맞는 라우트 하나만 쓰이면 yes), retry_attempts=(VirtualService 의 attempts 값) 네 줄을 적고, 그 아래 - 로 시작하는 설명을 네 줄 이상 적으세요.

값은 앞 단계 파일에서 옮기세요. 설명 줄에는 'VirtualService 를 고칠 때 무엇을 확인하겠다' 를 적으면 이 표가 다음 변경 검토 때 쓸모 있습니다.