Istio 서비스 메시 6주 스터디 — 주차별 실습 기록

Kubernetes8분조회

동료들과 Istio 스터디를 하자는 제안이 와서 6주를 반강제로 붙들려 있게 됐는데, 마침 도입 얘기가 돌던 참이라 빠지겠다고 하기도 애매했습니다. 저 역시 이름만 알지 만져본 적은 없던 터라, 문서만 읽고 붙였다가는 장애가 나도 어디를 봐야 할지 모르겠다 싶긴 했습니다.

실습은 KinD(로컬에 쿠버네티스를 띄우는 도구)에 Istio 공식 샘플인 Bookinfo 를 올려놓고, 주차 주제에 맞는 설정을 직접 걸어 보는 식으로 했습니다.

서비스 메시가 처음이라면 각 파드 옆에 프록시(Envoy)를 하나씩 붙여서, 서비스 간 트래픽 제어·보안·관찰성을 앱 코드가 아니라 인프라에서 처리하는 구조라고 보면 됩니다.

1주차 — 왜 Istio인가, 그리고 사이드카

쿠버네티스 Ingress 만으로는 가중치 라우팅(카나리), 서비스 간 mTLS, 세밀한 재시도·서킷브레이킹, 상세한 관찰성을 하기 어렵습니다. 예전엔 이걸 언어·런타임 마다 앱 코드로 각각 구현했는데, Istio 는 그 기능들을 Envoy 사이드카로 내려서 공통으로 처리합니다.

구조는 컨트롤 플레인(istiod)데이터 플레인(각 파드에 붙는 Envoy) 둘로 나뉘는데, istiod 가 설정을 xDS API 로 사이드카에 배포하고, 워크로드 인증서를 발급하고, 사이드카를 자동 주입합니다.

실습은 istioctl install 로 시작해서, 네임스페이스에 istio-injection=enabled 라벨을 붙이면 그 아래 파드가 READY 2/2(앱 + istio-proxy)로 뜹니다.

istio-ingressgatewaytype: LoadBalancer 서비스인데, KinD 에는 여기에 외부 IP 를 할당해 줄 클라우드 컨트롤러가 없어서 EXTERNAL-IP 가 계속 <pending> 입니다. MetalLB 나 cloud-provider-kind 를 따로 붙이면 되지만, 실습에서는 kubectl port-forward 로 게이트웨이 서비스에 직접 붙였습니다. 포워딩 대상이 게이트웨이 서비스라 트래픽은 그대로 ingress gateway 를 거쳐 mesh 로 들어갑니다.

2주차 — 트래픽 라우팅과 카나리

DestinationRule 로 subset(v1/v2/v3)을 정의하고, VirtualService 로 라우팅 규칙을 씁니다. 가중치를 90/10 → 50/50 → 100 순서로 바꾸며 카나리를 진행했습니다.

가중치를 옮기는 것 자체는 금방 됐는데, Istio 를 만지면서 처음으로 제대로 막힌 게 여기였습니다. DestinationRule 없이 VirtualService 의 subset 을 참조하면 kubectl apply 는 아무 불평 없이 통과하는데, 정작 호출하면 503 cluster_not_found 가 떨어집니다. 리소스가 하나 빠졌는데 적용 단계에서는 아무도 안 알려주니 처음엔 라우팅 규칙을 잘못 쓴 줄 알았고, istioctl analyze 를 돌리고 나서야 참조가 깨진 걸 알았습니다.

참조를 고치고 나서 가중치가 제대로 먹었는지 보려고 요청을 수십 건 넣어 비율을 세어 봤는데, 이번엔 설정한 90/10 과 실제 비율이 또 안 맞았습니다. 이건 설정 문제가 아니라 Envoy 가 요청마다 확률로 고르기 때문이고, 표본이 작으면 당연히 치우쳐 나옵니다. 비율을 눈으로 확인하려면 요청을 충분히 보내야 합니다.

Kiali 트래픽 그래프에서 reviews로 가는 요청이 v1과 v3로 갈리는 모습
Kiali 트래픽 그래프 — 진입점은 istio-ingressgateway, reviews 로 가는 요청은 v1 95.4% / v3 4.6%로 갈린다(빨간 박스)

3주차 — 복원력: timeout · retry · fault injection · circuit breaking

장애를 일부러 만들어(fault injection) 지연·에러를 주입하고, timeout·retry· 서킷브레이킹을 걸었습니다. 검증이 까다로운 지점이 세 군데였습니다.

  • timeout 만 걸었는데 실측이 두 배로 나왔습니다. 기본 retry 가 붙어서 2s × 2 가 된 거였습니다. SLO 계산할 때 이걸 모르면 어긋납니다.
  • retry 를 제대로 검증하려면, fault.abort(Envoy 가 앞단에서 끊어 upstream 에 도달을 안 해 retry 카운터가 0)나 replicas=0(즉시 503) 같은 걸로는 안 됩니다. replica 를 늘려 두고 부하 중에 파드를 강제 삭제해서 일시적 connect-failure 를 만들어야 재시도가 실제로 돕니다.
  • 서킷브레이킹connectionPool(동시연결·대기큐)과 outlierDetection (연속 5xx 로 endpoint 축출)로 나뉩니다. 축출을 깔끔히 재현하려면 항상 503 을 주는 replica 를 endpoint 에 끼워 넣어 연속 5xx 를 쌓아야 했습니다.

이 정책들의 카운터와 로그는 전부 호출자(client-side) 사이드카에 남습니다. 서버 쪽에서 찾으면 안 나옵니다.

4주차 — 보안: mTLS · JWT 인증 · 인가

PeerAuthentication 을 PERMISSIVE 에서 STRICT 로 올려 서비스 간 mTLS 를 강제했습니다. Istio 는 Auto mTLS 라 DestinationRule 없이도 대개 정상인데, 오히려 DR 의 tls.mode 를 잘못(DISABLE/SIMPLE) 주면 깨집니다 — 올바른 값은 ISTIO_MUTUAL 입니다.

인증과 인가가 분리돼 있다는 것도 여기서 확실해졌습니다. RequestAuthentication(JWT 검증)은 “토큰이 있으면 검증” 일 뿐 토큰 없는 요청은 막지 않아서, 막으려면 AuthorizationPolicy 를 따로 걸어야 합니다. 401(인증 실패)과 403(인가 거절)은 발생하는 필터가 다르고, claim(예: groups) 기반으로 인가 범위를 좁힐 수도 있습니다.

정책을 다 맞췄는데 ingress 로 들어오는 요청만 503 이고 mesh 내부 직접 호출은 200 이었습니다. 로그가 아예 안 남는 게 단서였습니다. mTLS 는 TLS 단계에서 끊기면 애플리케이션 로그에 아무것도 안 남습니다. ingress gateway 의 워크로드 인증서가 만료돼 있었고, KinD 를 오래 켜둔 채 노트북이 슬립되면서 인증서 자동 갱신 신호를 놓친 게 원인이었습니다.

5주차 — 관찰성: Prometheus · Grafana · Jaeger · Kiali

fault injection 을 걸어 두고 네 개 도구로 같은 장애를 추적했습니다. 보는 순서가 정해집니다 — Kiali 로 어디가 빨간지 보고, Jaeger 로 어느 span 에서 느려지는지 보고, Prometheus 로 정확한 수치(RPS·에러율·P99)를 재고, 마지막에 Envoy access log 로 요청 한 건의 최종 원인을 봅니다.

지표를 읽을 때도 한 번 헛다리를 짚었는데, abort 는 호출자 사이드카가 자체 응답을 내기 때문에 reporter="destination" 집계에는 안 잡히고 response_flagsDI,FI 로 남습니다. FI 로만 정확히 grep 하면 놓쳐서 정규식으로 잡아야 했습니다.

6주차 — 설정 오류 진단과 컨트롤 플레인

설정 오류 다섯 가지를 일부러 만들어 재현했습니다 — subset 라벨 불일치, STRICT mTLS 에 평문 클라이언트, ALLOW/DENY 정책 충돌, 사이드카 주입 누락, gateway selector 오타. 진단 도구는 각각 다른 곳을 봅니다. istioctl analyze(정적/K8s API), proxy-status(istiod 와의 xDS 동기화), proxy-config(사이드카 Envoy 의 런타임 설정).

마지막으로 “istiod 가 죽으면 서비스도 죽나?” 를 직접 실험했습니다. istiod 를 replicas=0 으로 내린 상태에서 기존 트래픽을 계속 보냈습니다.

istiod가 죽은 상태에서 productpage 요청이 HTTP 200으로 응답하는 터미널
istiod 를 내린 상태에서도 기존 트래픽은 HTTP 200 — Envoy 가 마지막 설정을 메모리에 들고 돈다(fail-static)

기존 요청은 그대로 200 을 유지했는데, Envoy 가 마지막으로 받은 설정을 메모리에 들고 계속 도는 fail-static 구조이기 때문입니다. 대신 새 설정은 반영되지 않고, 새로 뜨는 파드는 사이드카 주입 webhook 이 istiod 에 연결을 못 해 실패했습니다. 즉 컨트롤 플레인 가용성은 기존 트래픽이 아니라 새 변경 반영·새 파드 합류의 문제였습니다.

사이드카 모드에서 반복해서 걸린 세 가지

정책 카운터는 받는 쪽이 아니라 호출자 사이드카에서 봅니다. mTLS·인증서 문제는 TLS 단계에서 끊기기 때문에 애플리케이션 로그에 아무것도 안 남습니다. 컨트롤 플레인이 죽어도 기존 트래픽은 흐르고, 죽는 건 변경 반영과 새 파드 합류 쪽입니다.

  1. 불러오는 중