🏠 목록 control plane과 data plane의 설치·수명주기를 분리하면 istiod 업그레이드가 data plane에 투명해진다 📄 MD 원본 🌓 테마
istioinstallupgradehelmrevision

control plane과 data plane의 설치·수명주기를 분리하면 istiod 업그레이드가 data plane에 투명해진다

ℹ 이 문서가 다루는 것

"Istio를 업그레이드한다"가 왜 "mesh 전체를 한 번에 흔드는 일"이 아니라 "proxy를 하나씩 새 control plane으로 옮기는 일"이 될 수 있는지를 다룬다. 결론부터: istiod(control plane)와 Envoy(data plane)는 xDS라는 느슨한 gRPC 계약으로만 묶인 별개 컴포넌트라서, 새 istiod를 옆에 나란히 띄우고(revision canary) workload를 한 namespace씩 옮길 수 있고, 그래서 control plane 교체가 data plane에 투명해진다. 설치 단위(base/istiod/gateway 3 Helm chart)·revision 메커니즘·canary 흐름을 "왜 이렇게 설계됐나"의 원리로 풀고, 운영 detail(canary 명령·합격 기준)은 운영 플레이북 §06에 위임한다.

대상: Istio 1.30 / Helm 설치. 독자: control plane 업그레이드의 안전성 모델을 원리로 이해하려는 DevOps/SRE. 선행: xDS가 무엇인지 대략의 감(xDS API 계층).


01. 배경 — 왜 "분리"가 필요한가: monolithic mesh의 업그레이드 공포

먼저 분리가 없을 때의 세계를 그려야 분리의 가치가 보인다. Istio를 "하나의 덩어리"로 생각하면 — 즉 control plane과 그것이 제어하는 모든 proxy를 한 운명으로 묶으면 — 업그레이드는 다음 딜레마에 빠진다.

이 딜레마의 근원은 결합(coupling)이다. "control plane 버전"과 "내 워크로드가 받는 설정"이 1:1로 묶여 있으면, 버전을 바꾸는 행위가 곧 전체 워크로드의 설정을 바꾸는 행위가 된다. 그래서 부분적으로·되돌릴 수 있게·blast radius를 작게 업그레이드하려면, 먼저 이 둘을 떼어내야 한다. 떼어낼 수 있다는 사실 자체가 Istio 아키텍처의 핵심 자산이고, 이 문서 전체가 그 한 가지 사실의 전개다.

떼어내는 데 필요한 전제 두 가지: ① control plane과 data plane이 느슨하게 연결돼 있어야 하고(그래야 control plane을 통째로 바꾸지 않고 일부만 새것에 붙일 수 있다), ② 새 control plane을 구 것을 죽이지 않고 옆에 띄울 수 있어야 한다. 1번을 §02가, 2번을 §03~04가 책임진다.


02. 핵심 — 두 컴포넌트, 하나의 느슨한 계약 (멘탈모델 ANCHOR)

머릿속에 박을 그림 하나: istiod와 Envoy는 "전선 한 가닥(xDS gRPC 스트림)"으로만 연결된 독립 박스 두 개다. 전선을 뽑아도 Envoy는 마지막에 받은 설정으로 계속 굴러가고, 전선의 반대쪽 끝(어느 istiod)은 갈아 끼울 수 있다. 이 그림에서 분리·canary·투명한 업그레이드가 모두 따라 나온다.

두 박스의 역할부터 명확히 가르자.

둘을 잇는 유일한 연결이 xDS(LDS/RDS/CDS/EDS/SDS) gRPC 스트림이다. "느슨한 계약"이라는 말의 구체적 의미는 두 가지 성질로 드러난다 — 그리고 이 두 성질이 §01의 전제 ①②를 정확히 충족한다.

control plane (istiod) not on data path istiod rev=1-27 istiod rev=1-30 canary data plane (Envoy) serves traffic sidecar A rev=1-27 ingress gw rev=1-27 sidecar B rev=1-30 xDS push xDS push pod-to-pod traffic
그림 1. control/data plane 분리. istiod는 데이터 경로 밖이라 rev별로 공존(1-27 + 1-30 canary)하고, 각 proxy는 자기 rev의 istiod에서만 xDS를 받는다 — rev가 달라도 sidecar A↔B는 pod-to-pod로 그대로 통신한다.

이 그림이 문서의 결론이다. 같은 mesh 안에서 rev=1-27과 rev=1-30 istiod가 공존하고, 각 proxy는 자기 istio.io/rev가 가리키는 istiod 하나에만 붙는다. 아래쪽 점선(pod-to-pod traffic)이 중요하다 — rev이 다른 sidecar끼리도 서로 트래픽을 주고받는다. data plane은 통일된 하나의 mesh이고, 그 위에서 control plane만 두 버전이 굴러간다. 그러니 "업그레이드"란 control plane을 한 번에 교체하는 게 아니라, proxy를 어느 istiod에 붙일지 하나씩 바꾸는 일이다.

★ 한 문장

설치를 별 chart로 쪼개는 것은 수단이고, 수명주기 분리(istiod를 다른 컴포넌트와 무관하게 추가/교체)가 목적이다. xDS가 느슨한 계약이라서 이 분리가 물리적으로 가능하다.


03. 그 분리가 설치 형태로 굳은 모습 — base / istiod / gateway 3 Helm chart

§02의 "수명주기가 다르면 따로 다뤄야 한다"는 원리를 설치 단계에서 그대로 구현한 것이 3개의 독립 Helm release다. 현행(1.30) 권장 설치는 deprecated된 istioctl install -f IstioOperator.yaml이 아니라 이 3-chart다. 각 chart의 책임(=변경 주기, =blast radius)이 다르고, 다르기 때문에 따로 설치·업그레이드된다.

chart 설치 대상 수명주기(변경 주기) blast radius
istio/base CRD + cluster-wide RBAC/webhook 골격 mesh당 1번, 거의 안 바뀜 cluster 전역 (모든 revision 공유)
istio/istiod control plane Deployment(istiod) revision마다 별도 release 그 revision에 붙은 proxy만
istio/gateway data plane gateway Deployment+Service gateway마다 별도, app처럼 자주 그 gateway가 처리하는 edge 트래픽
# 설치 순서 = 의존 순서 (base의 CRD가 있어야 istiod가 뜨고, istiod가 있어야 gateway가 주입됨)
helm install istio-base istio/base -n istio-system --create-namespace
helm install istiod istio/istiod -n istio-system --wait
helm install istio-ingress istio/gateway -n istio-ingress --create-namespace

이 표를 읽는 핵심은 세로 줄(수명주기)의 비대칭이다.

⚠ IstioOperator / in-cluster operator는 deprecated

1.30 기준 IstioOperator API와 in-cluster operator(operator pattern으로 IstioOperator CR을 reconcile하던 방식)는 deprecated다. 과거 "empty 프로파일로 gateway만 켜기" 같은 IstioOperator 2개 분리 패턴도, 이제는 위처럼 istiod chart와 gateway chart 분리로 자연히 달성된다. 기존 operator-owned 설치가 있으면 Helm으로 재정렬할 때 ownership(label/annotation) 충돌을 먼저 확인할 것.


04. 분리를 "동시 존재"로 구현하는 구체 장치 — revision

§02~03이 "왜·무엇을"이라면, revision은 그것을 실제 클러스터에 어떻게 박는가다. revision 문자열(예: 1-30-1)은 다음 모든 곳에 동일하게 박혀, "이 proxy는 어느 istiod의 것인가"라는 단 하나의 질문에 답한다. 이 문자열이 매개라서, 같은 클러스터에 두 revision이 충돌 없이 공존한다.

revision = "1-30-1" 이 박히는 곳 (모두 같은 문자열이라 서로를 가리킨다)
  - istiod Deployment/Service 이름     : istiod-1-30-1
  - injection MutatingWebhookConfig    : istio-revision-tag / istio-sidecar-injector-1-30-1
  - namespace label                    : istio.io/rev=1-30-1
  - 주입된 sidecar의 xDS 연결 대상      : istiod-1-30-1.istio-system.svc

마지막 줄이 메커니즘의 심장이다. 주입 webhook은 namespace의 istio.io/rev label을 보고, 새로 뜨는 Pod에 "네 두뇌는 istiod-1-30-1.istio-system.svc다"라는 bootstrap 설정을 박는다. 즉 proxy가 어느 istiod에 붙을지는 "주입되는 순간" 고정된다. 여기서 가장 비직관적이지만 가장 중요한 결론이 나온다.

label을 바꾼다고 기존 Pod가 옮겨가지 않는다. 이미 주입이 끝난 sidecar는 옛 bootstrap을 들고 옛 istiod에 그대로 붙어 있다. label은 "앞으로 뜰 Pod의 두뇌"만 정한다.

그래서 revision 전환은 반드시 두 단계다 — label로 미래를 정하고, restart로 현재를 미래로 끌어온다.

# 1) namespace를 새 revision으로 표시 → 이후 주입되는 Pod의 bootstrap이 바뀐다
kubectl label ns app-a istio.io/rev=1-30-1 --overwrite

# 2) Pod를 재시작 → 재주입이 일어나 새 sidecar가 새 istiod에 붙는다
kubectl rollout restart deployment -n app-a
⚠ 진짜 함정은 label이 아니라 재시작 누락

label만 바꾸고 rollout restart를 빼먹으면, 기존 sidecar가 구 istiod에 계속 붙어 있어 "업그레이드한 줄 알았는데 안 된" 상태가 된다. 메커니즘상 당연한 결과지만(주입은 Pod 생성 시 1회), 증상은 "label은 새건데 동작은 옛것"이라 헷갈린다. 전환 검증은 label이 아니라 항상 istioctl proxy-status실제 연결 대상을 확인한다.

revision 문자열이 1.30.1이 아니라 1-30-1인 이유: 이 값이 K8s label 값이자 DNS-1123 label(Service 이름 istiod-1-30-1)로 동시에 쓰이는데, 둘 다 .을 허용하지 않기 때문이다. 그래서 점을 대시로 바꾼다 — 사소해 보이지만 §04 표의 "같은 문자열이 여러 곳에 박힌다"는 제약이 만든 필연이다.


05. 예시 — canary 한 번 돌리고 "정말 옮겨졌나" 눈으로 확인하기

세 가지(xDS 느슨한 계약 §02 + chart 분리 §03 + revision §04)가 합쳐지면 in-place 교체가 아닌 나란히 띄우고 옮기기가 된다. 한 namespace를 옮기는 전체 흐름과, 각 단계에서 무엇이 보여야 성공인지를 같이 본다.

operatoristiod rev=1-30namespace app-asidecar(app-a)helm install (canary)구 1-27 그대로 가동label rev=1-30rollout restart새 sidecar xDS 연결proxy-status / 503·latency 검증나머지 ns 동일 전환구 rev=1-27 제거
그림 2. canary revision 업그레이드: 신 istiod 병렬 설치 → ns 라벨 → rollout → 검증 → 단계적 전환 → 구 control plane 제거.

검증의 핵심 명령은 istioctl proxy-status다. 이 명령은 각 proxy가 실제로 어느 istiod에 붙어 있는지와 그 동기화 상태를 보여준다 — label이 아니라 진실을 본다. 전환 전후 출력 대조가 "정말 옮겨졌나"의 유일한 신뢰 가능한 근거다.

# 전환 전: app-a의 sidecar가 구 istiod(1-27)에 붙어 있음
$ istioctl proxy-status
NAME                          CLUSTER     ...  ISTIOD                       VERSION
app-a-7c9...istio-proxy       Kubernetes  ...  istiod-1-27-3-xxxx           1.27.3
ingressgateway-...            Kubernetes  ...  istiod-1-27-3-xxxx           1.27.3

# label + rollout restart 후: app-a만 새 istiod(1-30)로 이동, gateway는 아직 구버전
$ istioctl proxy-status
NAME                          CLUSTER     ...  ISTIOD                       VERSION
app-a-9f2...istio-proxy       Kubernetes  ...  istiod-1-30-1-yyyy           1.30.1
ingressgateway-...            Kubernetes  ...  istiod-1-27-3-xxxx           1.27.3

읽는 법: ISTIOD 열이 istiod-1-30-1-...로 바뀌고 동기화 상태(CDS/LDS/EDS/RDS)가 모두 SYNCED면 그 proxy는 정상적으로 새 control plane으로 옮겨진 것이다. 위 출력은 §02 anchor 그림을 그대로 증명한다 — app-a sidecar(rev=1-30)와 ingressgateway(rev=1-27)가 서로 다른 istiod에 붙어 공존하고, 그래도 mesh는 한 덩어리로 트래픽을 흘린다.

이 흐름에서 data plane이 받는 충격은 "Pod 한 번 재시작"뿐이고, 그것도 namespace 단위로 점진 적용되며, 언제든 label을 되돌리고 다시 restart하면 즉시 rollback된다(구 istiod를 아직 안 지웠으므로 붙을 두뇌가 살아 있다). control plane 버전 교체 자체는 트래픽 경로에 끼어들지 않는다 — 이것이 "투명하다"의 정확한 의미다.

구체적 canary 설치 명령, DNS-1123 규칙, 전환 합격 기준(proxy-status SYNCED / 503·504 증가 없음 / p95·p99 변화 없음 / istiod push error 없음)은 중복하지 않고 운영 플레이북 §06 — revision canary upgrade에 정리돼 있다. 업그레이드가 push 부하·warmup에 미치는 영향과 그 진단은 istiod 성능 4요인을 참조.


핵심 정리

What you might be missing