Istio 3-3편: Ambient mode 안전하게 업그레이드하기
Istio Ambient mode 버전 업그레이드 런북
Jetty (정재홍) • DevOps Engineer
- DevOps
안녕하세요, 채널코퍼레이션 DevOps팀의 딜런, 재티입니다.
이 글은 Istio Ambient mode 도입기 시리즈의 3-3편입니다. 3-1편에서는 waypoint와 ztunnel 사이의 stale connection 문제를, 3-2편에서는 istio-cni와 ztunnel이 준비되기 전에 Pod이 스케줄되었을 때 발생하는 partially enrolled 문제를 다뤘습니다.
3편: 프로덕션에서 만난 당황스러운 이슈들과 트러블슈팅
3-3편: Ambient mode 안전하게 업그레이드하기 (현재 글)
3-4편: 부록 — 507 status code와 istiod disconnected 탐지
이번 글에서는 Istio Ambient mode의 버전 업그레이드 런북을 작성해보려 합니다. Ambient mode에는 sidecar가 없어서 애플리케이션 Pod을 직접 재시작하지 않아도 되는 장점이 있지만, 대신 istio-cni와 ztunnel 같은 node-local 컴포넌트를 더 신중하게 봐야 합니다.
Istio Slack의 #ambient 채널에서도 버전 업그레이드 관련하여 질문이 종종 올라옵니다.
관련 Slack thread: Istio #ambient — Ambient upgrade procedure
위 thread에서도 비슷한 고민이 공유되었습니다. istio-cni는 여러 release를 동시에 설치하기 어렵고, ztunnel은 resourceName으로 여러 DaemonSet을 만들 수 있어 보이지만 istiod의 설정값인 trustedZtunnelName과 연결되어 있어 기존 istiod의 revision canary처럼 다루기 어렵습니다. 그래서 저희는 ztunnel 자체를 workload 단위가 아니라 node pool 단위로 blue-green 배포하는 방향을 선택했습니다.
저희가 정리한 결론은 이렇습니다.
Ambient mode 업그레이드는
istiod → istio-cni → ztunnel순서로 진행하되, ztunnel은 일반적인 rolling update보다 blue-green node pool 방식으로 접근하는 편이 안전합니다.
업그레이드 대상은 세 가지
Ambient mode에서의 버전 업그레이드는 크게 세 컴포넌트를 대상으로 진행됩니다.
Control Plane: istiod
CNI Node Agent: istio-cni
Data Plane: ztunnel
각 컴포넌트는 역할과 영향 범위가 다릅니다.
컴포넌트 | 역할 | 업그레이드 특성 |
| Gateway controller, Envoy config 전파 | - |
| Pod 감지, iptables 설정, ztunnel proxy port 구성 | in-place 업그레이드. 실행 중인 Pod 네트워크에는 보통 영향 없음 |
| node 단위 L4 data plane | 교체 시 해당 node의 long-lived connection이 reset될 수 있음 |
istiod나 istio-cni도 중요하지만, 실제 운영에서 가장 조심해야 하는 컴포넌트는 ztunnel입니다. ztunnel은 노드마다 하나씩 떠 있고, 그 노드에 올라간 Ambient workload의 트래픽을 직접 처리하기 때문입니다.
Step 1: istiod는 in-place로 업그레이드
먼저 control plane인 istiod를 업그레이드합니다. Ambient 환경에서 istiod는 config 전파뿐 아니라 Gateway controller 역할도 수행합니다. Istio Gateway controller가 관리하는 Envoy 워크로드는 istiod revision/버전과 맞물려 있어, istiod를 업그레이드하면 istio-gateway도 새 Envoy 이미지/설정으로 자동 rollout됩니다. 따라서 istiod 업그레이드는 control plane만 바꾸는 작업이 아니라 gateway Envoy까지 함께 업데이트되는 작업으로 보고, 업그레이드 후 gateway 쪽도 같이 확인해야 합니다.
기본적으로 Istio는 graceful shutdown을 지원하므로, control plane과 gateway rollout이 곧바로 서비스 중단으로 이어지지는 않아야 합니다. 다만 업그레이드 중에 다음과 같은 지표들을 확인합니다.
새 istiod revision이 정상적으로 뜨는가?
gateway/waypoint가 의도한 revision을 보고 있는가?
xDS sync가 깨지지 않았는가?
control plane 에러 로그가 증가하지 않는가?
Step 2: istio-cni는 in-place로 업그레이드
Ambient mode에서 istio-cni는 노드 안의 Pod을 감지하고, Pod network namespace에 iptables rule을 설정해 ztunnel과 통신할 수 있게 만듭니다.
istio-cni 업그레이드는 쿠버네티스 DaemonSet rollout으로 이루어집니다. 이 작업을 in-place로 볼 수 있는 이유는, 이미 실행 중인 Pod의 경우 Pod network namespace가 생성될 때 redirection rule이 이미 들어가 있고 ztunnel과 통신하기 위한 socket 연결도 준비되어 있기 때문입니다. 즉 istio-cni Pod이 재시작되는 순간에도 기존 Pod의 network namespace가 다시 만들어지는 것은 아니어서, 기존 Pod 트래픽이 곧바로 끊기는 형태의 data plane 교체는 아닙니다.
새로 생성되는 Pod 기준에서도 어느 정도 안전을 보장할 수 있습니다. istio-cni가 없는 아주 짧은 타이밍에 새 Pod이 생성되면 sandbox 생성 단계에서 CNI 설정을 완료하지 못해 FailedCreatePodSandBox가 발생할 수 있습니다. 하지만 이 Pod이 잘못된 네트워크 상태로 뜨는 것이 아니라, sandbox 생성 자체가 실패했다가 istio-cni가 다시 준비되면 Kubernetes가 재시도하면서 정상적으로 생성됩니다.
즉 istio-cni DaemonSet rollout 중에도 1) 이미 running 중인 Pod은 기존 network namespace와 ztunnel 연결을 그대로 사용하고, 2) 새롭게 뜨는 Pod은 istio-cni가 준비된 뒤 정상적으로 sandbox를 만들며 올라옵니다. 그래서 istio-cni는 DaemonSet rollout이 정상 완료되는지만 확인하면서 in-place로 업그레이드할 수 있다고 봤습니다.
Istio에서는 공식적으로 CNI 버전 호환성은 다음과 같이 제공하고 있습니다.
v1.x istio-cni는 v1.x 및 v1.x+1 control plane과 호환된다. (ztunnel도 마찬가지)
따라서 일반적인 순서는 control plane을 먼저 올리고, 그 다음 CNI를 올리는 것입니다.
다만 3-2편에서 다룬 것처럼 istio-cni가 준비되지 않은 상태에서 workload가 먼저 뜨는 race condition은 관측될 수 있습니다. 이때의 FailedCreatePodSandBox는 영구 장애라기보다 istio-cni가 준비될 때까지 Pod 생성이 재시도되는 현상으로 봅니다. 그래서 CNI 업그레이드에서는 기존 Pod의 연결 상태보다 DaemonSet rollout이 정상적으로 끝나는지, pending redirection이 남지 않는지를 확인합니다.
Step 2: istio-cni는 in-place로 업그레이드
Ambient mode에서 istio-cni는 노드 안의 Pod를 감지하고, Pod network namespace 안에 redirection rule을 설정해 트래픽이 ztunnel을 거치도록 만듭니다. 이 과정에서 ztunnel은 해당 Pod network namespace 안에 redirection socket을 준비하고, workload Pod의inbound/outbound 트래픽은 이 경로를 통해 처리됩니다.
istio-cni 업그레이드는 Kubernetes DaemonSet rollout으로 이루어집니다. 이 작업을 in-place로 볼 수 있는 이유는, 이미 실행 중인 Pod의 경우 Pod network namespace가 생성될 때 redirection rule과 ztunnel로 향하는 경로가 이미 준비되어 있기 때문입니다. 즉 istio-cni Pod이 재시작되는 순간에도 기존 Pod의 network namespace가 다시 만들어지는 것은 아니므로, 기존 Pod 트래픽이 곧바로 끊기는 형태의 data plane 교체는 아닙니다.
다만 여기서 조금 더 주의깊게 봐야할 부분은 기존 Running Pod입니다. istio-cni가 완전히 준비되지 않은 아주 짧은 타이밍에 새 Pod이 생성되면, sandbox 생성 단계에서 CNI 설정을 완료하지 못해 FailedCreatePodSandBox가 발생할 수 있습니다. 하지만 이 경우 Pod이 잘못된 네트워크 상태로 Running 되는 것이 아니라, sandbox 생성 자체가 실패하고 Kubernetes가 다시 시도합니다. 이후 istio-cni가 준비되면 Pod은 정상적으로 생성됩니다.
따라서 istio-cni DaemonSet rollout 중에는 두 가지를 나누어 봐야 합니다. 이미 running 중인 Pod은 기존 network namespace와 ztunnel 경로를 그대로 사용합니다. 반면 새롭게 생성되는 Pod은 istio-cni가 준비된 뒤 sandbox 생성과 redirection 설정을 완료해야 합니다. 이 관점에서 istio-cni는 DaemonSet rollout이 정상 완료되는지 확인하면서 in-place로 업그레이드할 수 있다고 봤습니다.
Istio에서도 CNI 버전 호환성은 다음과 같이 제공하고 있습니다.
v1.x istio-cni는 v1.x 및 v1.x+1 control plane과 호환된다. (ztunnel도 마찬가지)
따라서 일반적인 업그레이드 순서는 control plane을 먼저 올리고, 그 다음 CNI를 올리는 것입니다.
다만 3-2편에서 다룬 것처럼, istio-cni 및 ztunnel이 준비되지 않은 상태에서 workload Pod이 먼저 뜨는 race condition은 여전히 관측될 수 있습니다. 이때의 FailedCreatePodSandBox는 영구 장애라기보다 istio-cni가 준비될 때까지 Pod 생성이 재시도되는 현상으로 보고 있습니다. 그래서 CNI 업그레이드에서는 기존 Pod의 연결 상태보다 istio-cni DaemonSet rollout이 정상적으로 끝나는지, 새로 생성된 Pod 중 redirection이 누락되거나 pending 상태로 남는 Pod이 없는지를 확인합니다.
Step 3: ztunnel은 blue-green node pool로 업그레이드
가장 중요한 부분은 ztunnel입니다.
ztunnel은 Ambient mode의 data plane입니다. v1.x ztunnel은 v1.x 및 v1.x+1 control plane과 호환되므로, control plane을 먼저 업그레이드한 뒤 ztunnel을 옮길 수 있습니다.
문제는 업그레이드 방식입니다. ztunnel을 일반 DaemonSet처럼 rolling update하면, 같은 노드에서 old ztunnel과 new ztunnel이 교체되는 순간이 생깁니다. ztunnel은 graceful shutdown을 지원하지만, 이 시간을 길게 잡는 것은 권장하지 않습니다. 기본값도 30초 수준이고, 그 시간 안에 끝나지 않은 connection은 결국 ztunnel이 종료되는 순간 끊길 수 있습니다. 애플리케이션이나 client 쪽에서 별도 retry가 없다면 요청 실패로 이어질 수 있습니다.
즉 ztunnel upgrade는 “무조건 무중단”이라고 보기 어렵습니다. graceful shutdown이 있더라도 in-place DaemonSet rollout만으로 안전하다고 보기 어렵고, Istio의 Ztunnel Safe Upgrade 이슈에서도 in-place hitless upgrade나 같은 노드에서 여러 ztunnel revision을 활성화하는 것은 범위 밖으로 두고, node drain 기반 절차를 문서화하는 방향으로 논의되고 있습니다. 그리고, 저희도 같은 이유로 ztunnel upgrade를 node pool 단위의 blue-green 방식으로 진행하고 있습니다.
3.1 Green ztunnel 리소스 생성
먼저 기존 ztunnel과 새 ztunnel을 서로 다른 node label에 매핑합니다.
예를 들어 기존 버전은 ztunnel-a, 새 버전은 ztunnel-b라고 둡니다.
핵심은 ztunnel 버전별로 nodeSelector를 다르게 주는 것입니다. 그러면 기존 노드에서는 기존 ztunnel이 계속 돌고, 새 노드에서는 새 ztunnel만 뜨게 만들 수 있습니다.
다만 ztunnel의 resource 이름을 바꾸거나 여러 ztunnel DaemonSet을 동시에 운영하려면 istiod 설정도 같이 맞춰야 합니다. istiod Helm chart는 기본적으로 ztunnel의 이름을 ztunnel로 가정합니다. 따라서 위처럼 ztunnel-a, ztunnel-b를 쓰려면 istiod가 신뢰할 ztunnel service account 목록에 두 이름을 모두 추가해야 합니다.
이 설정이 빠지면 새 resourceName으로 뜬 ztunnel이 istiod와 정상적으로 신뢰 관계를 맺지 못할 수 있습니다. 즉 green ztunnel 리소스를 만들 때는 ztunnel DaemonSet만 추가하는 것이 아니라, istiod의 trusted node account 설정까지 함께 변경하는 작업으로 봐야 합니다.
3.2 Green NodePool 구성
다음으로 새 ztunnel이 뜰 green node pool을 만듭니다. Karpenter를 사용한다면 NodePool template에 새 istio version label을 붙입니다.
이제 기존 노드에는 node.channel.io/istio-version: 1.25.0 label이 남아 있고, 신규 노드에는 node.channel.io/istio-version: 1.26.2 label이 붙습니다. 결과적으로 기존 노드는 blue ztunnel을 계속 사용하고, 신규 노드는 green ztunnel을 사용합니다.
3.3 Blue node를 점진적으로 drain한다
green node pool과 green ztunnel이 준비되면, 기존 blue node를 점진적으로 비웁니다.
이때 stateless workload부터 옮기는 것이 안전합니다. Stateful workload나 long-running job은 PDB, local storage, connection 특성 때문에 오래 남을 수 있습니다. 그래서 ztunnel-a를 완전히 제거하는 시점은 생각보다 늦어질 수 있습니다.
운영 절차는 다음처럼 잡을 수 있습니다.
green node pool 생성
green node에서 ztunnel-b Ready 확인
blue node를 조금씩 cordon/drain
workload가 green node로 이동하는지 확인
5xx, TCP reset, latency 지표 확인
ztunnel-a가 올라간 node가 더 이상 없으면 ztunnel-a 제거
Step 4: NodeGroup 정리
모든 workload가 green node pool로 이동했고, 더 이상 blue ztunnel이 필요한 노드가 없다면 기존 NodeGroup을 정리합니다.
결론
Ambient mode 업그레이드는 “Istio 버전 하나 올리기”가 아닌, 실제로는 세 가지 다른 성격의 컴포넌트를 순서대로 다루는 작업입니다.
istiod는 먼저 업그레이드하고 gateway/waypoint 상태를 확인합니다.istio-cni는 in-place로 업그레이드하되, untaint-controller 및ambient.istio.io/redirection: enabledannotation들을 확인합니다.ztunnel은 가장 조심해야 하며, 가능하면 blue-green node pool 방식으로 node 단위 migration을 수행합니다.
특히 ztunnel은 노드 단위 data plane이기 때문에, upgrade의 안전한 단위도 결국 node입니다. 기존 노드에는 old ztunnel을 유지하고, 신규 노드에는 new ztunnel을 띄운 뒤 workload를 천천히 옮기는 방식이 가장 직관적이고, 안전했습니다.
