🏠 목록 Istio 대규모 운영 플레이북 — multi-cluster·config scope·upgrade·LLMOps (출처: ChatGPT "Istio 운영 노하우" 대화 + Istio 1.30 공식 문서) 📄 MD 원본 🌓 테마
istiooperationsmulticlusterconfig-scopeupgradellmopsgitops

Istio 대규모 운영 플레이북 — multi-cluster·config scope·upgrade·LLMOps (출처: ChatGPT "Istio 운영 노하우" 대화 + Istio 1.30 공식 문서)

ℹ 이 문서가 다루는 것

한 그림으로: Istio 운영은 "Istio를 Envoy 설정 컴파일러(K8s 상태 + CRD → istiod → xDS → 모든 proxy)로 보고, 모든 장애를 YAML → istiod → xDS → Envoy config → response flag내려가며 추적하는 것"이다. 기능을 켜는 단계가 아니라 운영하는 단계에서는 성패가 config 전파량(scope)·upgrade blast radius·trust boundary·retry/timeout 정책에서 갈린다. 이 문서는 그 한 멘탈모델을 세운 뒤(§01~§02), 모든 장애를 같은 방향으로 추적하는 도구(§03)와, 규모가 커지면 그 도구만으로는 안 되는 4개 운영 주제(§04 multi-cluster·§05 scope·§06 upgrade·§07 GitOps·§08 LLMOps)를 같은 멘탈모델 위에서 펼치고, 마지막에 학습 루트(§09)로 닫는다.

이 문서는 운영자 레벨의 "큰 그림"과 production 실무에 집중한다. iptables/포트, response flag별 디버깅, cluster naming 같은 깊은 detail은 형제 문서(→ Sidecar 트래픽 캡처, → Cluster 해부, → AuthorizationPolicy 멘탈모델)에 있으므로 여기서는 요약·연결만 한다.


01. 배경 — 왜 "서비스 메시"라는 단어를 버려야 운영이 보이는가

운영자가 가장 먼저 풀어야 할 인식 문제가 하나 있다. "서비스 메시"라는 단어는 기능 목록("mTLS·라우팅·관측성을 자동으로 준다")으로 Istio를 설명한다. 이 시점에 머물면 장애가 났을 때 어디를 봐야 하는지 알 수 없다. 기능 목록에는 "어디서 무엇이 잘못될 수 있는가"가 없기 때문이다.

운영에 쓸 수 있는 모델은 단어를 바꾸는 데서 시작한다.

★ 한 문장 멘탈모델 (이 문서 전체의 anchor)

Istio는 Kubernetes 상태와 Istio CRD를 Envoy 설정으로 컴파일해서 모든 Pod/Gateway proxy에 배포하는 제어 시스템이다. 운영이란 이 컴파일러의 입력(YAML)·중간산물(xDS)·출력(Envoy config)·실행결과(response flag)를 한 방향으로 따라 내려가며 보는 일이다.

이 한 문장이 anchor인 이유는, 이 문장에서 운영의 모든 구조가 따라 나오기 때문이다. 컴파일러로 보면 자연히 4개 질문이 생긴다 — ① 무엇이 입력인가(K8s + CRD), ② 누가 컴파일하나(istiod), ③ 무엇으로 배포되나(xDS), ④ 실제로 도착·실행됐는가(Envoy config·ACK·response flag). 장애 추적(§03), 전파량 관리(§05), 업그레이드(§06), GitOps 검증(§07)은 전부 이 네 질문 중 어디를 손대느냐의 문제일 뿐이다.

sidecar mode에서 data plane은 각 Pod 옆의 Envoy proxy가, control plane은 istiod가 담당한다. istiod는 ① service discovery, ② configuration, ③ certificate management를 맡고, 고수준 routing rule을 Envoy 전용 설정으로 변환해 sidecar에 전파한다. 즉 컴파일러 본체가 istiod, 컴파일 결과를 실행하는 런타임이 Envoy다.

K8s API Service/EndpointSlice/Pod/Secret Istio CRD Gateway/VS/DR/SE/Sidecar... istiod compiler Envoy proxy LDS/RDS/CDS EDS/SDS 실제 트래픽 처리 watch watch xDS push 고수준 rule → Envoy 전용 config
그림 1. 컴파일러 모델. istiod가 K8s 객체와 Istio CRD를 watch/reconcile해 Envoy 전용 설정으로 컴파일하고 xDS로 push — 컴파일러 본체는 istiod, 런타임은 Envoy.

컴파일러의 출력은 Envoy가 받는 5종 설정이다. 이 5종이 곧 "장애가 날 수 있는 5개 층"이므로 의미를 질문 형태로 박아두면 추적이 쉬워진다.

Listener  : 어디서 받을 것인가
Route     : 어떤 HTTP/gRPC 요청을 어디로 보낼 것인가
Cluster   : upstream service/subset/policy는 무엇인가
Endpoint  : 실제 Pod IP/port는 무엇인가
Secret    : mTLS cert/key/trust bundle은 무엇인가
✓ 핵심: 피상적 이해 vs 내부 이해
  • 피상: "VirtualService가 라우팅한다"에서 멈춤.
  • 내부: VirtualService의 host/path/match가 Envoy RDS route로 바뀌고, route의 destination은 outbound|PORT|SUBSET|FQDN 형태의 cluster를 참조하며, DestinationRule의 subset label과 trafficPolicy가 CDS/EDS/LB/outlier detection에 영향을 준다.

피상에 머물면 "VS를 고쳤는데 왜 안 바뀌지?"에서 막힌다. 내부 모델이 있으면 "VS는 route로 컴파일된다 → 그럼 그 pod의 route를 보면 된다"로 바로 손이 간다. cluster naming(outbound|9080|v1|reviews...)과 subset의 정확한 의미는 → Cluster 해부 참조.


02. 핵심 메커니즘 — 요청 하나가 지나가는 경로 = 컴파일된 설정이 실행되는 경로

§01의 anchor를 "정지한 그림"이라면, 이번 절은 그 그림이 요청 하나가 흐르는 동안 어떻게 실행되는가다. 운영자가 외워야 할 단 하나의 경로이고, §03 이후의 모든 디버깅이 이 경로 위의 한 지점을 찍는 일이다.

sidecar mode 기준으로 요청은 app container에서 출발해 iptables/CNI redirection으로 Envoy에 빼앗기고, outbound listener에서 protocol·route·cluster·endpoint·mTLS origination을 거쳐 network를 건너, 상대 Envoy inbound listener에서 mTLS termination·인증·인가·telemetry를 거쳐 server app에 도달한다. 왜 app이 직접 보내지 않고 Envoy가 가로채는가 — 그래야 app 코드를 한 줄도 안 고치고 routing·mTLS·정책을 설정으로 주입할 수 있기 때문이다. 가로채기(iptables redirect)가 곧 "컴파일된 설정이 끼어들 자리"를 만든다.

Application container
   │ connect("service-b.ns.svc.cluster.local:8080")
   ▼
iptables / Istio CNI redirection
   ▼
Envoy outbound listener
   ├─ protocol 판단: HTTP/gRPC/TCP
   ├─ route match: host, path, header, gateway/source
   ├─ cluster 선택: service, subset, port
   ├─ endpoint 선택: pod IP, locality, health, outlier state
   ├─ mTLS origination: client cert, trust domain, SAN 검증
   ▼
Network
   ▼
Remote Envoy inbound listener
   ├─ mTLS termination
   ├─ PeerAuthentication 검사
   ├─ RequestAuthentication/JWT 검사
   ├─ AuthorizationPolicy 검사
   ├─ telemetry/access log/metrics
   ▼
Server application container

이 경로의 각 단계가 §01의 5종 설정 중 하나와 정확히 대응한다는 점이 핵심이다 — outbound listener=LDS, route match=RDS, cluster/endpoint 선택=CDS/EDS, mTLS=SDS, inbound 정책 검사=RBAC filter. 즉 "요청이 막힌 단계"를 알면 "어느 설정 층을 볼지"가 자동으로 정해진다.

그렇다면 "그 설정이 실제로 그 proxy에 도착했는가?"라는 질문이 남는다. Envoy는 동적 설정 API(xDS: LDS/RDS/CDS/EDS/SDS)로 listener·cluster·route·endpoint·암호자료를 갱신하고, update를 받으면 ACK/NACK로 적용 여부를 control plane에 알린다. 그래서 istioctl proxy-statusSYNCED/STALE/NOT SENT는 단순 상태값이 아니라 "Envoy가 istiod의 설정을 실제로 받아들였는가"를 보는 운영 신호다.

proxy-status 의미 운영적 해석
SYNCED Envoy가 마지막 push를 ACK한 정상 상태 설정이 도착·적용됨
STALE istiod가 push했으나 ACK가 지연 Envoy 과부하 / 느린 ACK — push 폭주·proxy 리소스 의심
NOT SENT istiod가 아직 보낼 게 없음 debounce 대기 또는 해당 proxy scope에 변경 없음

STALE이 대량이면 §05의 scope 문제(push량 과다)를, NOT SENT인데 변경을 기대했다면 §05의 scope에서 그 proxy가 해당 리소스를 import하지 않는 것을 의심한다(→ 데이터플레인 sync 상태). iptables 15001/15006 capture와 listener handoff의 packet-level detail은 → Sidecar 트래픽 캡처 참조.


03. 적용 — CRD ↔ Envoy 번역표로 장애를 한 방향으로 내려가기

§01~§02가 멘탈모델이라면, 이 절은 그 모델을 손에 쥐는 단 하나의 추적 규율이다. 외울 것은 매핑이 아니라 방향이다.

★ 추적 방향 (모든 장애에 동일)

YAML → istiod → xDS → Envoy config(proxy-config) → access log/response flag. 위에서 의심되는 CRD를 찾고, 아래로 내려가며 "어느 층에서 기대와 실제가 갈라지는가"를 찾는다. 이 매핑을 외우는 게 아니라, 매번 istioctl proxy-config로 확인하는 습관을 들이는 것이 핵심이다.

CRD / YAML VirtualService / DestinationRule / ... istiod compile + push istioctl proxy-config listener/route/cluster/endpoint/secret access log + response flag NR / UF / UO / UH ... 기대≠실제면 위(CRD)가 원인 여기서만 보이면 런타임
그림 2. 추적 방향. 모든 장애는 YAML → istiod → proxy-config → access log 순으로 내려가며 "어느 층에서 기대와 실제가 갈라지는가"를 찾는다 — proxy-config에서 갈라지면 위(CRD), log에서만 보이면 런타임/정책.

각 CRD가 이 경로의 어느 층으로 컴파일되는지, 그래서 장애 시 어느 proxy-config를 보는지를 한 표로 정렬한다.

Istio/K8s 리소스 Envoy 관점 장애 시 보는 곳
Service, EndpointSlice, ServiceEntry service registry / endpoint source proxy-config endpoint, proxy-config cluster
VirtualService HTTP/TCP/TLS route proxy-config route
DestinationRule LB, subset, TLS, connection pool, outlier detection proxy-config cluster
Gateway edge listener + server + TLS proxy-config listener, proxy-config route
PeerAuthentication inbound mTLS requirement proxy-config listener, proxy-config secret
AuthorizationPolicy Envoy RBAC filter proxy-config listener -o json (HTTP는 http_connection_managerenvoy.filters.http.rbac, TCP는 envoy.filters.network.rbac 확인 — 멘탈모델은 → AuthorizationPolicy 멘탈모델)
Sidecar config scope / import boundary proxy-config cluster, proxy-config listener
EnvoyFilter raw Envoy patch proxy-config listener/cluster/route -o json

공식 문서상 VirtualService는 host/source/subset/weighted routing 같은 routing abstraction을, DestinationRule은 routing 이후의 load balancing/connection pool/outlier detection/subset policy를 정의한다. Sidecar는 workload의 inbound/outbound config scope를 조정한다(§05).

워크드 예시: "VS 적용했는데 트래픽이 안 바뀜"

가장 흔한 장애를 추적 방향대로 따라가 보자. 증상은 똑같아도 원인은 컴파일 경로의 서로 다른 층에 있다.

⚠ 함정 — 4대 원인 (각각 갈라지는 층)
  1. hosts mismatchhosts가 실제 요청의 :authority/DNS 이름과 달라 route가 매칭 안 됨 (→ route 층).
  2. gateways 필드 — rule이 mesh sidecar에는 안 붙고 ingress gateway에만 붙음(또는 반대) (→ 어느 proxy의 route인가 층).
  3. port naming — Service port가 http/grpc로 인식되지 않아 L7 route가 아니라 TCP proxy로 처리됨 (→ listener 층, route 자체가 안 생김).
  4. subset label mismatch — DestinationRule subset label이 실제 Pod label과 달라 endpoint가 비어 cluster가 무력 (→ cluster/endpoint 층).

추적 방향대로 위에서 아래로 한 번씩 찍어 내려간다:

kubectl get virtualservice,destinationrule -n <ns> -o yaml

istioctl proxy-config route <client-pod> -n <client-ns> -o json \
  | jq '.. | objects | select(has("virtualHosts"))?'
istioctl proxy-config cluster <client-pod> -n <client-ns> | grep <service-name>
istioctl proxy-config endpoint <client-pod> -n <client-ns> | grep <service-name>

기대 출력 — 정상이면: route에 host/path/header match가, cluster에 subset별 cluster가, endpoint에 실제 Pod IP가 보인다. 어느 단계에서 이게 비면 그 층이 원인이다(route 비었다→원인 1·2, route는 있는데 cluster가 TCP→원인 3, cluster는 있는데 endpoint 비었다→원인 4). 503 response flag(NR/UF/UO/UH...) 해석과 mTLS·AuthZ 장애 detail은 → AuthorizationPolicy 멘탈모델 / → Cluster 해부 참조. 한 줄 기억: DENY + HTTP attribute + no ports = TCP까지 막힐 수 있음 — DENY는 항상 selectorports를 좁힐 것.


04. multi-cluster — 멘탈모델이 cluster 경계를 넘을 때 무엇이 추가되나

여기서부터(§04~§08)는 §01~§03의 추적 도구만으로는 부족해지는 지점, 즉 규모가 만드는 4개 운영 변수다. 첫 변수는 cluster 경계다. 멀티 클러스터는 fault isolation, failover, location-aware routing, team/project isolation을 주지만 복잡도도 함께 늘린다. "설치 방식"을 먼저 고르면 길을 잃는다 — 공식 deployment model은 이를 4개 독립 축으로 보고, 설치 방식은 이 4축의 조합에서 따라 나오는 결과일 뿐이다.

1. single cluster      vs  multiple clusters
2. single network      vs  multiple networks
3. single control plane vs multiple control planes
4. single mesh         vs  multiple meshes

축으로 보면 멘탈모델이 명확해진다 — 네트워크가 하나면 Pod IP 직통이고, 나뉘면 gateway가 끼고, control plane이 하나면 primary-remote, 여럿이면 multi-primary다. 즉 §02의 요청 경로가 cluster 경계에서 무엇을 추가로 통과하는가의 문제다.

Single network 멀티 클러스터 — 경로에 아무것도 안 더해짐

모든 workload IP가 서로 직접 닿을 수 있으면 cross-cluster gateway 없이 Pod IP로 직통한다. §02의 경로가 그대로 cluster를 넘는다. 단, 여러 클러스터의 endpoint IP/VIP가 겹치면 안 된다.

운영 리스크:
- Pod CIDR overlap
- Service CIDR overlap
- 방화벽 / 라우팅 비대칭
- NetworkPolicy가 cross-cluster source를 차단
- kube-proxy / CNI datapath 차이

Multi-network 멀티 클러스터 — 경로에 east-west gateway가 끼어듦

클러스터 간 workload가 직접 닿지 못하면 east-west gateway가 필요하다. 다른 network의 workload는 Istio gateway를 통해서만 도달하고, cross-network 통신은 Istio proxy가 있는 workload에 대해서만 지원된다. 왜 gateway를 통과시키면서도 거기서 복호화하지 않는가 — TLS passthrough로 최종 workload까지 end-to-end mTLS를 유지해야 하기 때문이다. gateway는 SNI를 보고 라우팅만 하고 평문을 만지지 않는다.

cluster-aworkloadcluster-asidecarcluster-beast-west GWcluster-bworkload sidecarcluster-bappmTLSTLS passthrough
그림 2. cluster-a workload → sidecar → cluster-b east-west gateway(TLS passthrough) → cluster-b sidecar → app. 게이트웨이는 SNI만 보고 라우팅.
여기서 많이 터지는 지점:
- east-west gateway LB address 불안정
- SNI host mismatch
- remote secret 권한 문제
- cluster/network label 불일치
- trust domain / root CA 불일치
- gateway firewall는 열렸지만 health/readiness port는 막힘

primary-remote vs multi-primary vs multi-mesh — control plane 축과 mesh 축의 선택

primary cluster는 자체 istiod control plane을 가지고, remote cluster는 control plane 없이 primary의 것을 사용한다. remote를 지원하려면 primary control plane이 안정적 주소로 접근 가능해야 하고, 네트워크가 나뉘면 gateway/internal LB로 control plane 접근 경로를 만들어야 한다. 즉 "remote는 컴파일러(istiod)를 빌려 쓴다"가 핵심이고, 그래서 primary 장애가 remote의 config 갱신을 멈춘다.

모델 장점 단점 추천 상황
Primary-remote control plane 운영 단순 primary 장애/네트워크 장애 영향 큼 작은 remote cluster, edge/isolated workload
Multi-primary HA·cluster 자율성 좋음 설정 중복, rollout 복잡 region/zone 단위 production
Multi-mesh federation blast radius 작음 service discovery/routing 통합 어려움 강한 조직/보안/규제 경계

세 모델 중 무엇을 고를지는 remote의 규모·HA 요구·trust 경계로 결정한다.

multicluster 모델 선택강한 조직/규제/trust 경계 필요?region·zone HA·자율성 필요?multi-meshfederationmulti-primaryprimary-remote작고 isolated remoteyesnoyesno
그림 3. multicluster 토폴로지 선택: trust 경계가 강하면 multi-mesh, HA·자율성이면 multi-primary, 작고 격리된 remote면 primary-remote.
✓ 핵심

대규모에서 "하나의 거대한 mesh"가 항상 답은 아님. 장애 격리, 보안 경계, GitOps ownership, 인증서 trust boundary를 기준으로 mesh를 쪼개야 할 때가 많음.

⚠ 함정: K8s DNS는 multicluster-aware가 아니다

Kubernetes DNS는 멀티 클러스터를 기본적으로 알지 못함. client cluster에도 DNS lookup이 성공할 Service 또는 별도 DNS 구성이 필요함. Istio(컴파일러)는 multicluster-aware라 remote endpoint를 cluster에 컴파일해 넣지만, K8s DNS는 그렇지 않아 :authority 해석 단계에서 먼저 깨진다 — 멀티 클러스터 장애의 흔한 원인임.


05. config scope — 컴파일 결과를 "누구에게" 보낼지가 곧 운영 비용

두 번째 규모 변수는 cluster 경계가 아니라 전파 범위다. 대규모 운영에서 가장 중요한 단 하나를 고르라면 이것이다. Istio는 기본적으로 control plane이 모든 namespace의 설정을 읽고, 각 proxy도 모든 namespace의 설정을 받을 수 있다. out-of-box 동작에는 좋지만 비용이 있다.

위험한 기본 상태:
모든 namespace의 모든 Service/VirtualService/DestinationRule/ServiceEntry가
모든 sidecar에 전파됨

결과:
- istiod CPU 증가
- Envoy 메모리 증가
- xDS push 시간 증가
- proxy warmup 지연
- config change의 blast radius 증가

왜 이게 §02의 STALE과 직결되는가 — 컴파일러가 각 proxy에 보내는 설정량이 N배면, push 시간도·ACK 부담도 N배다. scope를 좁히면 istiod가 각 proxy에 컴파일·push하는 설정량이 줄어 istiod CPU, Envoy 메모리, xDS push 시간, config change의 blast radius가 모두 작아진다. 대규모 mesh에서는 이 한 가지가 push 지연·proxy warmup·rollout 안정성을 좌우한다. 줄이려면 Sidecar + exportTo + discoverySelectors를 조합한다.

apiVersion: networking.istio.io/v1
kind: Sidecar
metadata:
  name: default
  namespace: app-a
spec:
  egress:
  - hosts:
    - "./*"           # 자기 namespace
    - "shared/*"      # 공용 서비스 namespace
    - "istio-system/*"

Sidecar는 app-a의 proxy가 "자기 + shared + istio-system namespace의 설정만" 컴파일받게 한다. 나머지 namespace는 NOT SENT 영역이 되어 그 proxy의 config·메모리에서 사라진다. Sidecar/exportTo/discoverySelectors의 selection 메커니즘과 REGISTRY_ONLY/outboundTrafficPolicy의 동작 detail은 → Sidecar scope에 위임한다.

⚠ 함정: Sidecar는 보안 차단 장치가 아니다

Sidecar scoping은 프록시 설정량을 줄이는 장치일 뿐, scope 밖 목적지 트래픽이 반드시 차단되는 것은 아님. 컴파일러가 그 cluster를 "안 보내는" 것과, 런타임이 그 목적지를 "막는" 것은 다른 층이다. outbound traffic restriction(보안)으로 오해하면 안 됨 — 외부 호출 차단은 REGISTRY_ONLY outbound policy 등 별도 메커니즘으로 한다.


06. upgrade는 revision canary로 — "컴파일러를 교체하는" 작업의 위험 지점

세 번째 규모 변수는 시간 축, 즉 컴파일러 자신(istiod)을 새 버전으로 바꾸는 일이다. in-place upgrade보다 안전한 권장 방식은 새 control plane을 canary로 먼저 배포하고 일부 workload만 새 revision으로 옮긴 뒤 전체 전환하는 것이다.

여기서 진짜 위험은 직관과 어긋난다. 새 revision을 설치해도 기존 sidecar는 자동으로 바뀌지 않으며, namespace의 istio.io/rev label과 pod 재시작을 통해 새 control plane으로 연결된다. 이유가 곧 메커니즘이다 — sidecar injection은 pod가 생성되는 순간 mutating webhook이 istio.io/rev 값을 보고 한 번 주입하는 것이라, 이미 떠 있는 pod의 sidecar는 옛 revision으로 굳어 있다. label을 바꿔도 재주입이 일어나지 않으므로 rollout restart로 pod를 새로 띄워야 비로소 새 istiod에 붙는다. 즉 "어느 컴파일러에 붙느냐"는 pod 생성 시점에 한 번 결정되고, 그 뒤엔 재시작만이 바꾼다.

⚠ IstioOperator 파일 기반 install은 deprecated

1.30 기준 istioctl install -f IstioOperator.yaml 경로(operator/IstioOperator API)는 deprecated이며, 현행 권장은 Helm chart로 revision canary(istio/base + revision별 istio/istiod release를 --set revision=...로 따로 설치)임. install 방식과 control plane/data plane 분리 원리는 → 설치 CP/DP 분리.

istioctl x precheck

# 권장: Helm canary (revision별 istiod release 추가 설치, 기존 control plane 유지)
helm install istiod-1-30-1 istio/istiod -n istio-system \
  --set revision=1-30-1 --wait

kubectl label ns inference-canary istio.io/rev=1-30-1 --overwrite
kubectl rollout restart deployment -n inference-canary

istioctl proxy-status | grep inference-canary

revision 이름은 1-30-1처럼 DNS-1123 label이어야 한다(점→대시). revision이 istio.io/rev namespace/pod label 값과 istiod의 istiod-<rev> Service·injection webhook 이름에 그대로 쓰이는데, K8s label 값과 DNS 이름은 .을 허용하지 않으므로 1.30.1이 아니라 1-30-1을 써야 한다.

검증은 §02의 신호를 그대로 쓴다 — canary namespace의 proxy가 새 컴파일러에 정상으로 붙었는지를 본다.

✓ 핵심: 전환 검증 기준
  • proxy-status 전부 SYNCED
  • 503/504 증가 없음
  • response_flags 증가 없음
  • p95/p99 latency 변화 없음
  • istiod push error 없음
  • Envoy memory/CPU 급증 없음

이 기준이 모두 만족되면 나머지 namespace의 istio.io/rev label을 옮기고, 구 revision control plane을 제거한다.


07. GitOps admission — 잘못된 컴파일 입력을 cluster 밖에서 거르기

네 번째 규모 변수는 누가 컴파일러 입력을 바꾸는가다. 규모가 커지면 사람이 손으로 검증할 수 없으므로, 잘못된 CRD가 istiod에 닿기 전에 CI에서 막는다. Istio 리소스는 cluster에 들어가기 전에 사전 검증한다. live cluster(-A)와 local YAML 양쪽 모두 가능하다.

istioctl analyze -A

istioctl analyze base.yaml virtualservice.yaml destinationrule.yaml

CI admission에서 반드시 잡아야 하는 것은 §03의 "4대 원인"을 input 단계로 끌어올린 목록이다 — 즉 컴파일 후 깨질 것을 컴파일 전에 잡는다.

- host 없는 DestinationRule
- Gateway selector mismatch
- VirtualService host/gateway mismatch
- subset label 불일치
- namespace injection label 누락
- EnvoyFilter 사용 여부
- AuthorizationPolicy DENY에 port scope 없음
⚠ EnvoyFilter는 최후의 수단

istiod가 생성한 Envoy 설정을 직접 patch하므로(컴파일러 출력을 손으로 덮어씀), 잘못된 설정은 mesh 전체를 불안정하게 만들 수 있고 Istio/Envoy minor upgrade 시 내부 xDS 구조 변화에 취약함. 충돌하는 EnvoyFilter의 동작은 undefined. 운영 기준: platform team만 merge, root namespace의 workloadSelector 없는 EnvoyFilter는 금지/강한 승인, 적용 전후 config_dump diff 필수, canary namespace 선검증, minor upgrade마다 재검증. 우선순위는 VirtualService → DestinationRule → Telemetry → WasmPlugin → (마지막) EnvoyFilter.


08. LLMOps/MLOps — inference traffic이 기본 멘탈모델을 깨는 지점

다섯 번째 변수는 트래픽의 성질이다. §02의 요청 경로 모델은 짧고 idempotent한 microservice 요청을 암묵 가정한다. LLM inference traffic은 그 가정을 정면으로 깬다.

- 요청 시간이 길다
- streaming 응답이 많다
- payload가 크다
- GPU queueing 때문에 tail latency가 크다
- retry 비용이 매우 비싸다
- model version / adapter / tenant / region별 routing 요구가 생긴다

timeout과 retry는 보수적으로 — resiliency 기본값이 장애 증폭기가 되는 이유

LLM generation은 이미 GPU에서 실행 중인 작업을 retry로 중복 생성할 수 있다. streaming 응답, tool call, fine-tuning trigger, batch inference는 idempotent하지 않을 수 있다. blanket retry는 GPU queue를 더 밀어넣고 tail latency를 악화시킨다 — 일반 REST에서 "안전한 기본값"인 resiliency 설정이 여기선 장애 증폭기가 된다. 메커니즘은 단순하다: 느린 요청 → timeout → retry → 같은 GPU에 작업 하나 더 → 더 느려짐 → 더 많은 timeout (양의 피드백).

- inference POST에는 기본 retry 금지 또는 매우 제한
- GET/health/readiness에는 짧은 timeout
- streaming route에는 긴 timeout 또는 별도 route
- model server 내부 queue timeout과 Envoy route timeout을 맞춤
- upstream 5xx 전체 retry보다 특정 reset/connection failure만 제한적으로
✓ timeout 방향: Envoy route timeout ≥ 서버 내부 처리 상한

Envoy route timeout이 서버 내부 처리 상한(model server queue/inference deadline)보다 커야 서버가 먼저 응답을 끝내거나 Envoy가 504로 깔끔히 끊고, "서버는 아직 도는데 Envoy가 먼저 끊어 무의미한 retry를 부르는" 상황을 막음. streaming 응답은 route timeout을 0(무한) 또는 충분히 긴 값으로 둘 것. non-idempotent inference(generation/tool call)의 retry는 이미 GPU에서 도는 작업을 중복 실행시킬 risk가 있으므로 특히 보수적으로.

워크드 예시: model version canary — label + DestinationRule subset + VirtualService weight

§03의 추적 모델이 그대로 적용되는 구체 예다. model version을 Pod label로 표현하고, DestinationRule subset이 그 label을 cluster로, VirtualService가 weight/header를 route로 컴파일한다 — header 일치(internal-test)는 100% v2로, 나머지는 95/5로 흘린다.

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: llm-server
  namespace: inference
spec:
  host: llm-server.inference.svc.cluster.local
  subsets:
  - name: llama-3-70b-v1
    labels:
      model: llama-3-70b
      version: v1
  - name: llama-3-70b-v2
    labels:
      model: llama-3-70b
      version: v2
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: llm-server
  namespace: inference
spec:
  hosts:
  - llm-server.inference.svc.cluster.local
  http:
  - match:
    - headers:
        x-tenant:
          exact: internal-test
    route:
    - destination:
        host: llm-server.inference.svc.cluster.local
        subset: llama-3-70b-v2
  - route:
    - destination:
        host: llm-server.inference.svc.cluster.local
        subset: llama-3-70b-v1
      weight: 95
    - destination:
        host: llm-server.inference.svc.cluster.local
        subset: llama-3-70b-v2
      weight: 5

검증 — 추적 방향대로 route(가중치)와 endpoint(실제 Pod)를 본다:

istioctl proxy-config route <client-pod> -n <client-ns> -o json \
  | jq '.. | objects | select(.weightedClusters? != null)'
istioctl proxy-config endpoint <client-pod> -n <client-ns> | grep llm-server

기대 출력 — route에 v1:95/v2:5 weighted cluster가 잡히고, endpoint에 두 subset의 Pod IP가 보이면 컴파일이 의도대로 된 것이다.

⚠ access log에 prompt/Authorization/PII를 넣지 말 것

Envoy access log는 기본적으로 method/path/response code/response flags/duration/authority/upstream host를 기록하고 body는 기록하지 않음. 그러나 custom access log나 WASM/Lua/ext_authz로 request metadata를 추가할 때 prompt, Authorization header, tenant 식별자, PII가 새기 쉬움. 운영 표준에서 허용 필드를 명확히 제한할 것.


09. 추천 학습 루트 — "YAML이 아니라 Envoy 설정으로 사고"하는 연습

★ 한 문장 멘탈모델

Bookinfo만 돌리지 말고, 직접 만든 서비스에서 YAML이 아니라 Envoy 설정으로 사고하는 연습을 단계적으로 한다. 즉 §01~§03의 추적 방향을 손에 붙이는 훈련이다.

1단계 — 단일 클러스터 request path 해부. client → api → model-server 3서비스를 직접 만들고: VirtualService 1개, DestinationRule subset 2개, 90/10 canary, header 기반 라우팅, timeout/retry 적용 후 access log와 proxy-config route/cluster/endpoint로 내부 설정을 확인. (=§02 경로를 눈으로 보기.)

2단계 — mTLS와 AuthorizationPolicy만 따로 파기. namespace PeerAuthentication STRICT, service account별 AuthorizationPolicy, JWT RequestAuthentication + AuthorizationPolicy 조합. 일부 TCP port에 ports 없는 DENY를 일부러 걸어 장애를 재현하고 proxy-config secret/listener + access log로 원인 확인. (=§03 함정을 직접 만들어 보기.)

3단계 — xDS diff 직접 읽기. 리소스 적용 전후의 proxy-config를 diff로 본다 — 컴파일러가 무엇을 바꿨는지 출력 레벨에서 확인하는 가장 빠른 길.

istioctl proxy-config route   <pod> -n <ns> -o json > before-route.json
istioctl proxy-config cluster <pod> -n <ns> -o json > before-cluster.json

kubectl apply -f virtualservice.yaml
kubectl apply -f destinationrule.yaml

istioctl proxy-config route   <pod> -n <ns> -o json > after-route.json
istioctl proxy-config cluster <pod> -n <ns> -o json > after-cluster.json

diff -u before-route.json   after-route.json
diff -u before-cluster.json after-cluster.json

4단계 — 멀티 클러스터(순서를 지킬 것). §04의 4축을 한 번에 하나씩 켜며 변수를 분리한다.

1. multi-primary, same network
2. primary-remote, same network
3. multi-primary, multi-network, east-west gateway
4. trust domain / CA 분리
5. cluster-local service와 cross-cluster failover 분리

검증 포인트: 각 cluster의 Service/Endpoint view, remote secret 정상 여부, east-west gateway address, SNI/TLS passthrough, mTLS cert SAN, locality 기반 endpoint 선택, DNS lookup 성공 여부.

매일 쓰는 디버깅 명령(요약, §03 추적 방향순): proxy-statusx describe podproxy-config listener/route/cluster/endpoint/secretproxy-config bootstrapanalyze -A. 각 명령의 정확한 출력 해석은 → Cluster 해부 참조.


핵심 정리

1. Istio는 Envoy 설정 컴파일러 + 인증서/정책 control plane이다.
   K8s 상태 + Istio CRD → istiod → xDS(LDS/RDS/CDS/EDS/SDS) → 모든 proxy.

2. 장애 분석은 항상 같은 방향으로 내려간다:
   YAML → istiod → xDS → Envoy config(proxy-config) → access log/response flag.
   어느 층에서 기대≠실제인가가 곧 원인 층이다.

3. 규모가 만드는 5개 운영 변수가 성패를 가른다 — "기능"이 아니다:
   cluster 경계(multi-cluster topology) · 전파량(scope) ·
   시간축(revision canary upgrade) · 입력 게이트(GitOps admission) ·
   트래픽 성질(LLMOps retry/timeout). 전부 컴파일러 모델 위의 변주다.

What you might be missing