Istio 심화 — 왜 그렇게 흐르는가 · VirtualService 가 라우트 표가 되기까지 · 이론
규칙은 맞는데 요청이 엉뚱하게 가는 이유
한 줄 요약
VirtualService 의 http[] 는 Envoy 라우트 표의 routes[] 가 된다 — 순서까지 그대로. 표 이름은 서비스 포트, 가상 호스트 이름은 FQDN:포트, weight 는 weighted_clusters, retries 는 retry_policy 다. 번역이 이렇게 곧아서, VirtualService 의 실수는 Envoy 에서 그대로 재현된다.
왜 이게 필요했나
VirtualService 를 고친 뒤 "왜 이 요청이 v1 로 가지?" 를 설명하지 못하는 경우가 많다. YAML 만 보면 규칙은 다 맞아 보인다. 문제는 대개 규칙 사이의 관계다. 앞의 넓은 규칙이 뒤의 좁은 규칙을 가리거나, 두 규칙이 같은 요청에 맞는데 의도와 다른 쪽이 먼저 적혀 있다. 그리고 이 종류의 실수는 istioctl analyze 가 잡지 못한다. analyze 는 각 규칙이 가리키는 서브셋·게이트웨이가 존재하는지를 볼 뿐, 규칙끼리의 가림은 보지 않는다.
그래서 번역 규칙을 알아야 한다. 규칙을 알면 VirtualService 를 읽으며 머릿속에서 Envoy 라우트 표를 그릴 수 있고, 운영에서는 istioctl proxy-config routes 출력과 VirtualService 를 줄 단위로 맞대 볼 수 있다.
어떻게 동작하나
나가는 쪽 사이드카는 포트마다 라우트 표를 하나 둔다. 같은 9080 을 쓰는 서비스가 여럿이면 한 표 안에 서비스마다 가상 호스트가 하나씩 들어가고, 요청의 Host 헤더로 고른다.
| VirtualService | Envoy |
| --- | --- |
| 서비스 포트 9080 | route_config.name: "9080" |
| hosts: [reviews] | 가상 호스트 reviews.default.svc.cluster.local:9080, domains 에 reviews·reviews.default·reviews.default.svc·FQDN |
| http[i].match | routes[i].match(prefix·headers …) |
| http[i].route[].destination.subset | route.cluster: outbound\|9080\|v1\|… |
| weight | route.weighted_clusters |
| retries.attempts / retryOn | retry_policy.num_retries / retry_on |
| timeout | route.timeout |
어느 가상 호스트의 domains 에도 없는 Host 로 오면 404 다. 표 안에서는 위에서부터 처음 맞는 라우트 하나만 쓴다. 조건이 없는 규칙(catch-all)이 앞에 있으면 뒤의 규칙은 영원히 쓰이지 않는다.
가중치는 요청마다 주사위를 굴린다. 75 대 25 를 마흔 번 보내 30 대 10 이 딱 나오지 않는 것은 정상이다. 재시도는 원래 요청 하나에 최대 attempts 번이 더 붙어, 업스트림은 1+attempts 번 두드려진다. Istio 는 VirtualService 에 retries 를 적지 않아도 기본 재시도(2회, 연결 실패·거절 등)를 넣고, 같은 호스트를 피하는 previous_hosts 술어도 함께 붙인다.
현장에서 만나는 모습
새 규칙을 목록 끝에 붙였더니 아무 효과가 없다. 끝에 catch-all 이 이미 있었기 때문이다. 리뷰 때 "새 규칙이 catch-all 보다 앞인가" 를 한 줄 확인 항목으로 둔다.
장애 때 업스트림 요청이 네 배로 뛴다. 호출 사슬의 여러 단이 각각 재시도를 걸면 곱으로 늘어난다. 재시도는 사슬의 한 곳에만, 짧은 perTryTimeout 과 함께 건다.
Envoy 를 직접 써 본 사람이 15초 타임아웃을 기대한다. Envoy 라우트의 기본 타임아웃은 15초지만, Istio 는 VirtualService 에 timeout 이 없으면 라우트에 0s(끄기)를 넣는다. 그래서 사이드카 뒤의 느린 호출은 끝없이 기다린다. 기다림의 상한이 필요하면 VirtualService 에 적어야 하고, 적은 값이 라우트의 timeout 으로 그대로 보인다.
카나리 1% 가 안 보인다. 요청 몇십 개로 확인하면 v2 가 한 번도 안 나올 수 있다. 비율은 통계로, 충분한 횟수로 본다.
공식 문서: [VirtualService](https://istio.io/latest/docs/reference/config/networking/virtual-service/) · [Debugging Envoy and Istiod](https://istio.io/latest/docs/ops/diagnostic-tools/proxy-cmd/)
다음 실습에서 할 것
VS·DR 을 써서 분석을 통과시키고, 라우트 표·가상 호스트·도메인 이름을 규칙으로 도출해 Envoy 를 세운다. Host 헤더로 표를 고르는 것, 헤더 매치와 경로 매치가 겹칠 때 먼저 적힌 것이 이기는 것을 보고, catch-all 을 앞에 둔 실수를 analyze 는 못 잡고 Envoy 는 그대로 따른다는 것을 확인한다. 마지막으로 가중치와 재시도를 세어 본다.