IT/기타

Istio sidecar에서 ambient로 옮기다 일주일을 날렸다

gfrog 2026. 5. 30. 18:44
반응형

왜 옮겼나

지난주 얘기다. 우리 팀 서비스 메시를 Istio sidecar mode에서 ambient mode로 옮기다가, 새벽 2시에 사내 메신저가 폭주했다. 일주일 정도 마이그레이션 준비를 한 줄 알았는데, 사실은 그때부터가 진짜 시작이었다.

이번 KubeCon Europe 2026(암스테르담)에서 Ambient Multicluster Beta가 발표된 걸 보고 팀에서 좀 들떴다. 우리는 클러스터가 4개라 진작에 ambient를 보고 있었는데, 멀티클러스터까지 시야에 들어오니 "지금 single cluster부터 옮겨두면 나중에 편하겠다"는 분위기였다. 그래서 진행한 건데, 결론부터 말하면 마이그레이션 자체는 책 그대로 됐다. 문제는 책에 안 나온 부분이었다.

직접적인 이유는 메모리였다. 노드당 envoy sidecar가 100개 가까이 떠 있는 클러스터가 두 개 있는데, sidecar 하나당 평균 60~80MB씩 먹으니까 노드 자원의 10% 정도가 그냥 사이드카로 빠진다. ztunnel은 노드당 한 개 DaemonSet이라 이 비용이 사실상 없어진다. 1.22에서 single cluster ambient가 GA로 가고, 최근 4개 릴리즈 동안 ztunnel 성능이 75% 정도 개선됐다는 얘기를 보고 "이제 갈 만하다"고 판단했다.

부수적인 이유는 sidecar lifecycle. Pod 시작 순서, 종료 순서, init container와의 race condition. 다들 한 번씩 본 그 문제들. 그게 진짜 짜증 났다.

1번째 namespace는 너무 잘 됐다

platform-shared namespace부터 시작했다. 사내 공통 라이브러리, 인증 프록시 같은 게 들어가 있는데, L7 정책이 없어서 ambient로 옮기기 가장 쉬운 후보였다.

kubectl label namespace platform-shared istio.io/dataplane-mode=ambient
kubectl rollout restart deployment -n platform-shared

이게 끝이다. mTLS는 ztunnel이 자동으로 처리해주고, 트래픽 흐름도 멀쩡했다. 일주일 정도 모니터링 돌려보고 P99 latency가 오히려 미세하게 (한 1~2ms) 낮아진 걸 보고 "다음 가자" 했다.

2번째 namespace에서 401이 쏟아졌다

order-service namespace로 갔다. 여기는 AuthorizationPolicy로 JWT 검증을 하고 있었다. sidecar mode에서는 envoy 안에 JWT filter가 박혀서 잘 돌고 있었다.

ambient로 옮긴 직후엔 멀쩡해 보였다. 트래픽도 잘 흐르고 mTLS도 붙어 있었다. 그런데 한 30분쯤 지나서 결제 팀에서 "401이 막 뜬다"는 핑이 왔다. 처음엔 캐시 문제인 줄 알았다. 토큰 클럭 스큐 의심도 했고. 한 1시간을 그쪽으로 팠다.

결국 envoy access log를 까보고서야 알았다. JWT 검증이 아예 안 되고 있었다. 모든 요청이 401로 떨어지는 게 아니라, 인증 헤더가 살아 있는 요청만 통과하고, 그 외에는 그냥 200을 던지고 있었다. 더 심각했다.

ztunnel은 L4까지만 본다

여기서 정신이 좀 들었다. ztunnel은 L4 secure overlay라서 mTLS, 단순한 L4 인가 정도만 한다. JWT 검증이나 HTTP path 기반 정책 같은 L7 처리는 별도의 waypoint proxy로 빠진다. 다시 말해, ambient로 옮기는 순간 L7 정책은 자동으로 따라오지 않는다.

sidecar mode에서는 envoy 하나가 L4부터 L7까지 다 봤지만, ambient에서는 정책별로 어디서 평가될지를 명시적으로 결정해줘야 한다. 우리 AuthorizationPolicy는 selector로 Pod에 붙어 있었는데, ambient namespace에서 이건 ztunnel에서 평가되지 않고 그냥 무시된다(정확히는 L4만 적용).

해결책은 waypoint를 띄우는 것이었다.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: order-service-waypoint
  namespace: order-service
  labels:
    istio.io/waypoint-for: service
spec:
  gatewayClassName: istio-waypoint
  listeners:
  - name: mesh
    port: 15008
    protocol: HBONE

그리고 AuthorizationPolicytargetRefs를 Service 단위로 다시 잡아줬다.

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: require-jwt
  namespace: order-service
spec:
  targetRefs:
  - kind: Service
    group: ""
    name: order-api
  action: ALLOW
  rules:
  - from:
    - source:
        requestPrincipals: ["*"]

selector 기반에서 targetRefs 기반으로 옮기는 게 핵심이었다. 옛 문서 보고 그대로 따라하면 selector를 그대로 두는데, 그게 ambient에서는 작동 모델이 다르다.

새벽 2시의 메신저 폭주

위 문제를 잡은 다음 날, 다른 namespace를 옮기다가 진짜로 새벽 2시에 깼다. 이번엔 다른 문제였는데, waypoint를 띄웠더니 일부 트래픽이 waypoint를 우회해서 직접 ztunnel로 가는 케이스가 있었다. 같은 클러스터 안에서 ServiceEntry로 외부 도메인을 가리키는 워크로드들이 그랬다.

이건 ztunnel 1.24 이슈로 보였는데, 정확한 원인은 아직 검증 중이다. 우선 임시로 istio.io/use-waypoint 라벨을 명시적으로 박아서 우회시켰다. 이 부분은 다음 주에 좀 더 파볼 예정이다.

정리하면

마이그레이션 자체는 1.22 이후로 진짜 잘 다듬어져 있다. label 박고 rollout 하면 끝이라는 말은 거짓말이 아니다. 하지만 L7 정책이 있는 namespace는 사전에 waypoint를 띄우고, AuthorizationPolicy를 targetRefs로 다시 잡아둬야 한다. 이 부분만 미리 알았어도 새벽에 안 깼을 것이다.

지금 4개 클러스터 중 2개는 ambient로 옮겼고, 나머지 2개는 multicluster beta가 좀 더 안정화되기를 기다리는 중이다. 같은 작업 하시는 분 있으면, sidecar/ambient 혼용 마이그레이션 가이드를 먼저 정독하시길. 우리 팀은 그걸 안 봤다가 일주일을 날렸다.

혹시 비슷한 경험 있으신 분, ztunnel + ServiceEntry 케이스 어떻게 잡으셨는지 댓글 남겨주시면 정말 감사하겠습니다.

반응형