LabHub

ICA — 이스티오 인증 어소시에이트 · 트래픽 관리 · 이론

어디로 보낼까와 도착 후 어떻게 다룰까

LabHub 에서 이어서 보기

한 줄 요약

VirtualService 는 어디로 보낼까를, DestinationRule 은 도착한 뒤 어떻게 다룰까를 정합니다. 카나리 배포가 동작하지 않는 사고의 절반은 이 분업을 반쪽만 만들어서 생깁니다.

왜 이게 필요했나

배포와 릴리스를 분리하고 싶다는 요구가 출발점입니다. 새 버전을 클러스터에 올리는 일(배포)과 사용자 트래픽을 그리로 보내는 일(릴리스)을 따로 제어할 수 있으면, 문제가 생겨도 이미지를 되돌리는 대신 가중치만 0으로 내리면 됩니다.

그런데 이 두 가지는 성격이 다릅니다. "10%만 v2 로" 같은 분배는 요청 단위 결정이고, "v2 란 version=v2 라벨을 가진 파드들"이라는 정의는 목적지 단위 사실입니다. Istio 는 이것을 두 리소스로 쪼갰습니다.

여기서 시험에 그대로 나오는 비대칭이 생깁니다. subset 은 DestinationRule 에만 있고, weight 는 VirtualService 에만 있습니다. DestinationRule 없이 VirtualService 에서 subset: v2 를 참조하면 Envoy 에 그런 클러스터가 만들어지지 않아 요청은 503 을 받습니다. 액세스 로그의 응답 플래그는 UH(no healthy upstream)로 찍힙니다.

어떻게 동작하나

라우팅 규칙은 위에서 아래로 평가되고 첫 매치가 이깁니다. 그래서 구체적인 규칙을 위에, 포괄 규칙을 맨 아래에 둡니다. 마지막에 match 없는 규칙(catch-all)이 없으면 아무 규칙에도 걸리지 않은 요청은 NR(no route) 플래그와 함께 404 또는 503 이 됩니다.

매칭 조건의 결합 규칙도 자주 틀립니다.

# AND: 경로가 /admin 이면서 동시에 헤더도 맞아야 매칭- match:    - uri:        prefix: /admin      headers:        x-role:          exact: admin# OR: 경로가 /admin 이거나, 헤더가 맞으면 매칭- match:    - uri:        prefix: /admin    - headers:        x-role:          exact: admin

한 match 블록 의 조건들은 AND, match 배열의 항목 사이는 OR 입니다. 들여쓰기 한 칸 차이로 정책의 의미가 뒤집힙니다.

복원력 설정은 값을 외우는 게 아니라 산정 논리를 외워야 합니다.

perTryTimeout = 대상 서비스 정상 p99 x 1.5~2배attempts      = 멱등(GET) 2, 비멱등(POST) 0~1timeout       >= perTryTimeout x (attempts + 1)

마지막 줄이 핵심입니다. timeout 은 재시도를 포함한 전체 데드라인이므로, perTryTimeout 1s 로 2회 재시도를 걸어 두고 timeout 을 1s 로 잡으면 재시도는 한 번도 실행되지 못한 채 요청이 끝납니다. 설정은 있는데 동작하지 않는 전형적인 형태입니다.

재시도는 체인 전체 관점에서 봐야 합니다. A → B → C 에서 각 단계가 attempts 2 를 가지면 C 가 받는 요청은 최악의 경우 3 x 3 = 9배가 됩니다. 4단이면 27배입니다. 죽어가는 서비스에 재시도가 관에 못을 박는 셈이므로, 재시도는 체인의 한 계층(가급적 가장 바깥)에만 둡니다.

서킷브레이커는 두 리소스가 아니라 DestinationRule 한 곳에 모여 있습니다. connectionPool 은 "얼마나 쌓아 둘 것인가"를, outlierDetection 은 "어떤 인스턴스를 빼낼 것인가"를 정합니다. 인스턴스가 3개뿐인 서비스에서 maxEjectionPercent: 100 을 두면 세 개가 동시에 퇴출되어 서킷브레이커가 곧 전면 장애가 됩니다.

같은 DestinationRule 안에 localityLbSetting 도 있습니다. 같은 존의 엔드포인트를 먼저 쓰고 그 존이 죽으면 다음 존으로 넘기는 설정입니다. 존을 넘는 트래픽에는 요금과 지연이 붙으므로 규모가 커지면 반드시 켜게 됩니다.

여기에 시험에도 현장에도 나오는 함정이 있습니다. 페일오버는 outlierDetection 없이는 일어나지 않습니다. 다음 우선순위로 넘어가는 조건이 "지금 우선순위의 엔드포인트가 불건강하다" 인데, 그 판정을 만드는 장치가 outlierDetection 이기 때문입니다. 없으면 Envoy 는 같은 존의 엔드포인트가 죽어 가는 것을 알 방법이 없어 계속 그리로 보냅니다.

증상이 헷갈리는 이유는 평상시에는 멀쩡히 동작한다는 것입니다. 존 우선 라우팅 자체는 잘 되고 지연도 줄고 요금도 내려갑니다. 그러다 존 하나가 죽는 날에만 넘어가지 않습니다. 라벨(topology.kubernetes.io/zone)이 잘못됐다면 애초에 존 우선 라우팅부터 동작하지 않으므로 증상이 다릅니다 — 평소에 잘 되다가 장애 때만 안 되면 거의 언제나 outlierDetection 이 빠진 것입니다.

현장에서 만나는 모습

저자의 홈랩에서 얻은 교훈 하나를 그대로 옮깁니다. Cilium 으로 세운 그 클러스터에서 같은 IP, 같은 포트의 nginx 에 rules.http: [{method: GET}] 정책을 걸었더니 GET 은 200, POST 는 403 이 됐습니다. L3/L4 계층에서는 메서드가 보이지 않으므로 이 구분은 누군가 HTTP 를 파싱했다는 뜻입니다. Istio 에서 그 누군가가 사이드카 Envoy 이고, 헤더 매칭 라우팅과 메서드 매칭이 가능한 이유도 정확히 같습니다.

흥미로운 것은 그때 Hubble 로그의 모양이었습니다. 요청은 DROPPED, 응답은 FORWARDED 로 찍혔습니다. 프록시가 만들어 낸 403 이 정상 응답으로 흘러 나갔기 때문입니다. Istio 에서도 마찬가지로 연결은 성립했는데 403 이 돌아오는 상황과 연결 자체가 실패하는 상황은 로그 모양이 다릅니다. 전자는 L7 판정, 후자는 L4 문제입니다. 이 구분을 못 하면 엉뚱한 리소스를 몇 시간씩 들여다보게 됩니다.

다음 실습에서 할 것

두 개의 실습이 이어집니다. 첫 번째에서는 네임스페이스와 v1/v2 워크로드를 실제로 배포한 뒤 DestinationRule 과 VirtualService 매니페스트를 파일로 작성해 가중치·헤더 매칭·URI 재작성·규칙 순서를 손에 익힙니다. 두 번째에서는 재시도와 타임아웃의 관계, 헤더로 게이팅한 장애 주입, 서킷브레이커, 미러링을 다룹니다.