Istio 서비스 메시 · 카나리와 트래픽 분할 · 이론
카나리 — 가중치보다 중요한 것은 중단 조건이다
한 줄 요약
카나리는 "새 버전에 트래픽을 조금 준다"가 아니라, "어떤 지표가 어떻게 되면 멈추고 되돌릴지를 먼저 정한 뒤 조금 준다" 이다.
왜 이게 필요했나
롤링 업데이트만으로도 파드는 조금씩 교체된다. 그런데 롤링 업데이트가 보는 것은 파드의 건강 상태뿐이다. 프로세스가 살아 있고 readiness 프로브가 통과하면 계속 굴러간다. 새 버전이 200 을 잘 내면서 응답 내용만 틀렸거나, p99 지연이 세 배가 됐거나, 특정 요청에서만 500 을 내는 경우 롤링 업데이트는 아무것도 눈치채지 못하고 끝까지 간다.
메시의 트래픽 분할은 여기에 레버 하나를 더한다. 파드 개수와 트래픽 비율을 분리하는 것이다. v2 파드를 미리 다 띄워 놓고도 트래픽은 0% 만 줄 수 있고, 문제가 보이면 파드를 건드리지 않고 가중치만 0 으로 되돌릴 수 있다. 되돌리는 데 배포가 필요 없다는 것, 이것이 롤링 업데이트와의 결정적 차이다.
어떻게 동작하나
가중치 분할은 Envoy 의 WeightedCluster 로 번역된다. 요청마다 난수를 뽑아 가중치 구간에 따라 클러스터를 고른다. 그래서 정확히 90:10 이 아니라 확률적으로 90:10 에 가까워진다. 요청이 100건뿐이면 12건이 v2 로 갈 수도 있고, 이 성질 때문에 트래픽이 적은 서비스에서는 카나리 단계마다 충분한 표본이 쌓일 때까지 기다려야 판단이 의미를 갖는다.
가중치의 합은 반드시 100 이어야 한다. 합이 다르면 검증 단계에서 거부되는데, 이 규칙 덕분에 "v1 을 90으로 낮췄지만 v2 를 올리는 것을 잊는" 사고가 구조적으로 막힌다.
분할 방식은 세 가지를 구분해서 써야 한다.
| 방식 | 대상 | 쓰는 때 |
| --- | --- | --- |
| 가중치 | 무작위 일부 사용자 | 일반적인 점진 배포 |
| 헤더 매칭 | 지정한 사람만 | 내부 테스터 선공개, QA |
| 미러링 | 아무도 (사본만) | 사용자 영향 0으로 부하·회귀 검증 |
미러링(섀도잉)은 특히 오해가 많다. 사본 요청의 응답은 완전히 버려진다. 미러 대상이 죽어 있어도 사용자 응답에는 영향이 없다. 대신 미러된 요청도 DB 쓰기나 외부 API 호출 같은 부작용은 진짜로 일으킨다. 쓰기 경로를 미러링하려면 새 버전이 섀도 모드로 동작하도록 애플리케이션 차원의 준비가 있어야 한다. 미러 요청의 Host 헤더에는 -shadow 접미사가 붙으므로 로그에서 구분할 수 있고, 구분해야 한다.
가중치 분할에는 함정이 하나 더 있다. 요청마다 난수를 뽑으므로 같은 사용자가 화면을 넘길 때마다 v1 과 v2 를 왔다 갔다 한다. 세션 상태나 UI 가 버전마다 다르면 이건 그대로 버그로 보인다. 해결은 DestinationRule 의 일관 해시 로드밸런싱이다. 특정 헤더(예: x-user-id)를 해시해 같은 키는 항상 같은 엔드포인트로 보낸다. 해시 링 구조라 엔드포인트가 늘고 줄어도 재매핑이 최소로 일어난다.
마지막으로, 카나리 계획은 문서가 아니라 실행 가능한 정의여야 한다. 단계마다 (1) 가중치, (2) 다음 단계로 넘어갈 판단 기준, (3) 관찰 시간이 있어야 하고, 전체에 롤백 방법이 있어야 한다. Flagger 나 Argo Rollouts 는 이 정의를 CRD 로 받아 자동으로 굴린다. 자동화의 진짜 가치는 속도가 아니라 나쁜 지표를 보고도 롤백을 망설이는 인간적 편향을 없애는 것이다.
현장에서 만나는 모습
첫째, 라벨 불일치로 시작도 못 하는 카나리. subset 이 가리키는 라벨에 파드가 하나도 없으면 그 subset 으로 간 트래픽은 전부 503(UH)이다. 가중치를 10 으로 올리는 순간 요청의 10% 가 죽는다. 배포 파이프라인에서 subset 라벨과 파드 라벨의 일치를 검증하는 단계를 넣는 팀이 많은 이유다.
둘째, 게이트 없는 카나리. 10 → 30 → 50 → 100 을 사람이 눈으로 보며 올리는 방식은 시작점으로 괜찮지만 야간 배포가 어렵고 판단이 흔들린다. 오류율과 지연 임계값을 단계 정의에 못 박아 두면 그 판단이 사람에게서 떠난다.
셋째, 되돌릴 곳이 없는 카나리. v1 을 이미 지워 버렸다면 가중치를 0 으로 돌려도 갈 곳이 없다. 카나리가 끝나 100% 가 될 때까지 이전 버전은 살아 있어야 하고, 그 정리 시점도 계획에 있어야 한다.
다음 실습에서 할 것
100/0 에서 시작해 90/10 으로 옮기고, 합이 100 이 아닌 매니페스트가 어떻게 거부되는지 확인한다. 헤더로 내부 테스터만 새 버전에 보내는 라우트, 응답을 버리는 미러링, 같은 사용자를 한 버전에 고정하는 일관 해시를 차례로 붙인다. 마지막에는 단계·판단 기준·롤백을 담은 카나리 계획 파일을 만들고, 실제 메시를 50/50 중간 단계까지 진행해 둔다.
주의할 점이 하나 있다. 이 실습의 앞쪽 단계들은 클러스터가 아니라 저장된 파일을 채점한다. vs-baseline.yaml, vs-90-10.yaml, vs-header.yaml, vs-mirror.yaml 은 각 단계의 스냅샷으로 따로 남아 있어야 한다. 하나의 파일을 계속 고쳐 쓰면 앞 단계의 근거가 사라진다. 실무에서도 카나리 단계별 매니페스트를 각각 커밋해 두는 것이 정석이다 — 어느 단계에서 무엇이 달라졌는지가 곧 사고 조사 자료가 되기 때문이다.