본문으로 건너뛰기
3-2편 — Partially Enrolled Pod와 Untaint Controller

03-2 · Partially Enrolled Pod와 Untaint Controller — istio-cni보다 먼저 뜬 Pod (2026-07)

참조한 내용정리 · 이 문서는 아래 원문을 읽고 우리 지식베이스 형식으로 재구성한 요약입니다. 원문 자체가 아니며, 정확한 워딩·전체 맥락·그림은 원문에서 확인합니다.

  • 특정 노드에서 새로 뜬 Pod이 간헐적으로 트래픽을 받지 못했습니다. Pod phase는 Running, readiness probe는 통과, Service 엔드포인트에도 들어가 있었는데 메시 데이터플레인 관점에서는 아직 처리 준비가 안 된 partially enrolled 상태였습니다.
  • Ambient에서 “메시에 참여했다"가 성립하려면 두 단계가 모두 끝나야 합니다. ① istio-cni가 Pod network namespace에 리다이렉션 규칙을 심습니다. ② ztunnel이 그 Pod을 workload로 인식하고 프록시를 준비합니다. 하나만 빠져도 쿠버네티스는 정상이라 보고 메시는 처리하지 못합니다.
  • 원인은 스케줄링 경합입니다. kube-scheduler는 DaemonSet의 준비 완료를 일반 워크로드 스케줄링의 선행 조건으로 보장하지 않습니다. 새 노드에서는 istio-cni·ztunnel DaemonSet Pod과 워크로드 Pod의 스케줄링이 같은 시점에 함께 진행될 수 있습니다.
  • 공식 해법은 untaint controller입니다. 새 노드에 cni.istio.io/not-ready startup taint를 붙여 워크로드 스케줄링을 막아두고 istio-cni가 Ready가 되면 istiod 안의 컨트롤러가 그 taint를 뗍니다.
  • 설정은 pilot.taint.enabled=truePILOT_ENABLE_NODE_UNTAINT_CONTROLLERS 두 개가 다 필요했습니다. Istio 1.30부터는 전자를 켜면 후자가 자동 구성됩니다.

3-1편의 503과 half-open connection은 이미 메시 안에 자리 잡은 커넥션이 언제 끊기는지를 다뤘습니다. 이번 편의 대상은 그보다 한 단계 앞입니다 — 애초에 메시에 제대로 들어오지 못한 Pod입니다. 겉으로 드러나는 증상은 똑같이 5xx인데 원인이 놓인 계층이 다릅니다. 앞쪽은 커넥션 타이밍이고 이쪽은 Pod 등록 타이밍입니다.

Sidecar mode라면 이 문제 자체가 성립하기 어렵습니다. Envoy 사이드카가 Pod 안에 함께 있으니 Pod이 준비되면 메시 기능도 같이 준비됩니다. Ambient mode의 데이터플레인은 Pod 밖에 있습니다 — 노드 위의 istio-cni와 ztunnel입니다. 그래서 Pod의 readiness와 노드 데이터플레인의 readiness가 어긋납니다. 그 사이에 벌어진 간격이 장애 구간입니다(1편이 다룬 트레이드오프의 연장선입니다).

1. 증상 — Running이고 Ready인데 트래픽이 실패한다

채널팀이 프로덕션에서 관측한 내용입니다.

관측 지점상태
Pod phaseRunning
readiness probe통과
Kubernetes Service 엔드포인트정상 포함
다른 메시 워크로드 / waypoint에서의 호출실패

클라이언트 쪽 Envoy에는 이런 에러가 남았습니다.

upstream connect error or disconnect/reset before headers

reset reason은 connection failure 또는 connection termination이었습니다.

쿠버네티스가 내주는 신호는 전부 초록불인데 트래픽만 흐르지 않습니다. 상위 챕터 05 간헐적 응답 이상에서 다룬 사이드카 시절의 5xx 추적 순서로는 이런 조합이 잘 잡히지 않습니다. 그 순서는 “프록시가 붙어 있다"는 전제에서 출발해 라우팅·정책·업스트림을 차례로 의심하는데 여기서는 프록시가 지날 경로 자체가 아직 만들어지지 않았습니다.

단서는 발생이 특정 노드에 몰린다는 점이었습니다. 문제의 축은 Pod이 아니라 노드였습니다.

2. Ambient에서 “메시에 참여한다"의 정확한 의미

레이블은 의도 표명일 뿐이다

네임스페이스에 istio.io/dataplane-mode=ambient 레이블이 붙었다는 건 하나만 뜻합니다 — “이 Pod은 Ambient mesh 대상이어야 한다"는 의도. 레이블이 붙었다고 트래픽이 ztunnel을 지나가지는 않습니다.

실제로 트래픽이 ztunnel을 지나가려면 두 단계를 다 마쳐야 합니다.

메시 참여의 두 조건 — 1단계는 istio-cni node agent가 Pod network namespace에 진입해 트래픽 리다이렉션 규칙을 설정하는 것, 2단계는 istio-cni가 Pod의 workload 정보와 network namespace file descriptor를 ztunnel에 넘겨 ztunnel이 그 Pod의 소켓으로 인바운드·아웃바운드를 처리할 준비를 마치는 것입니다. 레이블은 이 둘 중 어느 것도 대신하지 못합니다.
도식 텍스트
  • ambient 레이블 — 네임스페이스 · 의도 표명
  • istio-cni — 1단계 · netns 리다이렉션 규칙
  • ztunnel — 2단계 · workload · netns fd
  • 메시 트래픽 — inbound · outbound 처리
  • Pod 생성
  • netns fd
  • proxy

두 단계 중 하나라도 빠진 Pod은 쿠버네티스 관점에서는 정상 Pod처럼 보이지만 메시 데이터플레인 쪽에서는 아직 완전하지 않습니다. 이 상태를 partially enrolled라고 부릅니다.

사이드카 모드와의 근본적 차이

구분Sidecar modeAmbient mode
프록시 위치Pod 내부 컨테이너노드 위 DaemonSet(ztunnel)
준비 판정Pod 자체가 준비되면 메시 기능도 준비Pod 준비와 별개로 node-local 컴포넌트가 준비돼야 함
실패 시점Pod 기동 실패로 드러남Pod은 정상, 트래픽만 실패
의존 방향Pod 내부 자기완결node-local 컴포넌트(istio-cni · ztunnel)에 강하게 의존

Ambient에서는 Pod이 뜨는 시점에 이 노드 컴포넌트들이 이미 준비돼 있어야 합니다. 그렇지 않으면 트래픽 손실이 납니다. Ambient의 Envoy 설정 구조 자체는 2편에서 다룹니다.

3. Partially enrolled — 두 가지 실패 시나리오

원문은 partially enrolled를 두 갈래로 분류합니다.

구분시나리오 1시나리오 2
무엇이 안 됐나istio-cni 미호출 또는 ambient 판정 실패규칙은 생성됐으나 ztunnel 연결·ACK 미준비
netns 리다이렉션 규칙없음있음
ambient.istio.io/redirection미적용pending
실제 트래픽메시를 그대로 우회인바운드·아웃바운드가 동작하지 않음
위험mTLS, AuthorizationPolicy, telemetry가 우회될 수 있음커넥션 실패로 드러남
시나리오 1이 더 위험합니다. 트래픽이 막히는 게 아니라 정책을 우회한 채 성공하기 때문입니다. mTLS도, AuthorizationPolicy도, 텔레메트리도 적용되지 않은 평문 트래픽이 오가는데 메트릭에는 아무 이상도 잡히지 않습니다. 시나리오 2는 최소한 5xx로 시끄럽게 실패합니다.

annotation이 상태를 드러낸다

등록 상태는 Istio가 Pod의 ambient.istio.io/redirection annotation으로 관리합니다.

의미
enabled리다이렉션 구성 완료. Pod이 captured 상태
pending리다이렉션이 일부 적용됐으나 ztunnel 등록이 완료되지 않음

원문은 pending을 이렇게 규정합니다 — active ztunnel이 해당 Pod을 프록시하기 전까지는 인바운드·아웃바운드 트래픽이 동작하지 않을 수 있는 상태입니다.

소스코드에서의 위치

채널팀은 istio 소스를 열어 이 경로를 직접 확인했습니다. 진입점은 cni/pkg/nodeagent/meshdataplane_linux.goAddPodToMesh입니다.

AddPodToMesh의 호출 순서 — netns 규칙까지는 성공해도 ztunnel ACK를 못 받으면 Pod은 pending으로 남습니다. 두 단계 사이의 ACK 왕복이 partially enrolled를 가르는 지점입니다.
도식 텍스트
  • Pod netns
  • istio-cni
  • ztunnel
  • kube-apiserver
  1. 1. Pod UID 스냅샷 등록 · netns 열기
  2. 2. netns 안에 리다이렉션 규칙 생성
  3. 3. AddWorkload — workload 정보 + netns fd
  4. 분기 — ACK 수신
    1. 4. ACK
    2. 5. redirection=enabled 적용
    3. 두 단계가 모두 끝났다 — captured
  5. PodAdded — no ztunnel connection
    1. 에러 반환
    2. redirection=pending 적용
    3. 규칙은 들어갔는데 ztunnel은 이 Pod을 모른다 · = partially enrolled · 트래픽 유실 구간
    4. Pod update 이벤트
    5. PodPartiallyEnrolled → AddPodToMesh 재시도

재시도 로직은 있습니다. 그래도 재시도가 성공하기 전까지는 트래픽이 그대로 유실됩니다.

4. 근본 원인 — 스케줄러는 DaemonSet 준비를 기다리지 않는다

kube-scheduler는 DaemonSet의 준비 완료를 일반 워크로드 스케줄링의 선행 조건으로 보장하지 않습니다. 새 노드가 클러스터에 추가되면 istio-cni DaemonSet Pod, ztunnel DaemonSet Pod, 일반 워크로드 Pod의 스케줄링이 거의 동시에 진행될 수 있습니다.

기대하는 순서와 실제로 벌어지는 일 — 기대하는 쪽은 DaemonSet이 Ready가 된 뒤에야 워크로드가 스케줄되는 것이지만, 실제로는 둘이 거의 동시에 진행됩니다. 그 사이에 뜬 Pod은 리다이렉션 없이 Running·Ready에 도달합니다.
도식 텍스트
  • 새 노드
  • kube-scheduler
  • istio-cni DS
  • 워크로드 Pod
  1. 노드 클러스터 합류 · 스케줄 가능
  2. 분기 — 기대한 순서
    1. 1. DaemonSet Pod 먼저 배치
    2. 2. CNI chain 등록 · Ready
    3. 3. 그 다음 워크로드 Pod 스케줄
    4. ztunnel DaemonSet도 이 사이에 Ready
  3. 실제 — 거의 동시에 진행
    1. istio-cni DaemonSet Pod 스케줄
    2. 워크로드 Pod 스케줄 (같은 시점)
    3. Running · Ready 도달
    4. istio-cni가 아직 CNI chain에 없거나 ambient 판정 실패 · → 리다이렉션 없이 떠버린 partially enrolled Pod

readiness probe로는 잡히지 않는다

원문의 표현대로 “쿠버네티스는 Pod이 준비됐다고 보지만, Ambient mesh는 아직 이 Pod을 처리할 준비가 되지 않은 상태"입니다.

readiness probe는 애플리케이션이 준비됐는지만 봅니다. 컨테이너가 포트를 열었는지, 헬스 엔드포인트가 200을 주는지까지가 확인 범위입니다. 노드의 메시 데이터플레인이 이 Pod을 받을 준비가 됐는지는 검사 대상이 아닙니다. 사이드카 시절에는 프록시가 Pod의 컨테이너였으므로 Ready 판정 안에 프록시 준비가 자연히 포함됐지만 Ambient는 그 포함 관계가 끊깁니다. probe를 아무리 촘촘하게 짜도 이 문제는 걸러지지 않습니다.

5. 해법 — Untaint Controller

Istio 팀의 공식 해결책은 untaint-controller입니다. 이미 떠버린 Pod을 사후에 고치는 대신 node-local 데이터플레인이 준비되기 전에는 워크로드 Pod이 그 노드에 스케줄되지 못하게 막습니다.

동작

taint를 붙이는 쪽과 떼는 쪽이 다릅니다 — 부착은 인프라(Karpenter NodePool·노드 그룹 등)가 노드 생성 시점에, 제거는 istiod 안의 untaint-controller가 istio-cni Ready를 보고 합니다. 인프라 쪽이 비어 있으면 컨트롤러는 뗄 taint가 없어 아무것도 하지 않습니다.
도식 텍스트
  • 노드 프로비저너
  • 새 노드
  • istio-cni
  • istiod untaint
  1. 분기 — 인프라가 startup taint를 붙였다
    1. 1. cni.istio.io/not-ready 부착 (NoSchedule)
    2. taint가 붙어 있는 동안 · 일반 워크로드 Pod은 이 노드에 스케줄되지 않는다
    3. 2. istio-cni DaemonSet만 배치 (toleration 보유)
    4. 3. CNI 설정 완료 · Ready
    5. 4. istio-cni Ready 감시
    6. 5. node patch — taint 제거
    7. 6. 이제 워크로드 Pod이 이 노드에 스케줄된다
  2. Istio 쪽만 켜고 taint를 안 붙임
    1. untaint-controller는 taint를 붙이지 않는다 — 뗄 것이 없으면 · 아무 일도 일어나지 않고 4장의 경합이 그대로 남는다

누가 붙이고 누가 떼는가

도입할 때 가장 자주 어긋나는 대목입니다.

동작담당
taint 추가인프라 레벨 — Karpenter NodePool, 노드 그룹, 오토스케일링 그룹 등이 새 노드 생성 시점에 붙인다
taint 제거untaint-controller (istiod 내부)
untaint-controller는 taint를 붙이지 않습니다. 떼기만 합니다. Istio 쪽 설정만 켜고 노드 프로비저닝 쪽에 startup taint를 넣지 않으면 아무 일도 일어나지 않습니다. 경합은 그대로 남습니다. 노드가 어디서 만들어지든 그 경로마다 taint를 붙여야 합니다.

설정

Karpenter를 쓴다면 NodePool에 startup taint를 구성합니다.

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      startupTaints:
        - key: cni.istio.io/not-ready
          value: "true"
          effect: NoSchedule

Istio 쪽은 Helm values로 켭니다.

pilot:
  taint:
    enabled: true
  env:
    PILOT_ENABLE_NODE_UNTAINT_CONTROLLERS: "true"

두 값의 역할이 다릅니다.

설정역할
pilot.taint.enabled=true노드 패치 권한과 CNI 네임스페이스 설정을 활성화한다
PILOT_ENABLE_NODE_UNTAINT_CONTROLLERS=true실제 컨트롤러 실행을 활성화한다
채널팀이 짚어낸 함정이 여기 있습니다. pilot.taint.enabled=true 하나만으로는 컨트롤러가 돌지 않습니다. Istio 1.22 릴리스 노트는 cni.istio.io/not-ready taint를 제거하는 node taint controller가 추가됐다고 짧게 언급해둔 정도라서 Helm 값 하나만 켜고 끝났다고 오해하기 쉽습니다.

Istio 1.30에서 달라진 것

원문이 링크한 Istio 1.30 upgrade note에 따르면 istiod Helm 차트에서 taint.enabled를 설정하면 PILOT_ENABLE_NODE_UNTAINT_CONTROLLERS 환경변수가 자동으로 구성됩니다. istiod 디플로이먼트에서 이 변수를 수동으로 켤 필요가 없어졌습니다.

1.30 이상이면 위 Helm values의 env 블록은 생략해도 됩니다. 그 이전 버전에서는 두 값을 모두 명시해야 합니다.

6. 적용 시 주의점 — 이걸로 전부 막히지는 않는다

untaint controller는 경합의 큰 축 하나를 없앱니다. 원문이 정리한 한계입니다.

  • untaint-controller는 istio-cni의 준비까지만 보장합니다. ztunnel의 readiness까지 완전히 보장하지는 않습니다. 앞서 본 시나리오 2(리다이렉션은 됐지만 ztunnel 연결·ACK 미준비)는 taint만으로 닫히지 않습니다.
  • 기존 Pod의 재등록 경로가 남습니다. 이미 노드에 떠 있던 Pod이 다시 등록돼야 하는 상황에는 startup taint가 개입하지 않습니다.
  • 리다이렉션 적용 이후의 ztunnel 단절, 그리고 pending 상태의 짧은 윈도우는 여전히 존재할 수 있습니다.
  • taint는 반드시 인프라 단계에서 설정해야 합니다. 컨트롤러는 제거만 합니다.

원문이 링크한 ambientmesh.io 운영 가이드는 컨트롤러가 실제로 도는지 확인하려면 istiod 로그를 보고 istioctl admin log로 untaint 관련 로그 레벨을 올리라고 안내합니다. 이 문단은 원문 본문이 아니라 원문이 인용한 문서의 내용입니다.

7. 사이드카 시절의 운영 감각이 어긋나는 지점

상위 챕터의 사이드카 계열 문서들과 나란히 놓으면 차이가 드러납니다.

질문Sidecar mode에서의 답Ambient mode에서의 답
“이 Pod은 메시에 들어와 있나?”사이드카 Ready면 그렇다Pod Ready와 무관 — netns 규칙·ztunnel 등록을 따로 확인
“노드를 늘리면?”각 Pod이 사이드카를 각자 띄운다istio-cni·ztunnel이 먼저 떠야 함 — 경합 구간 발생
컨트롤 플레인 부하의 형태프록시 수에 비례한 xDS push (02)노드 수 기준 컴포넌트 + 노드 taint 패치
스케일아웃의 부작용xDS 커넥션이 재분배되지 않음 (09)신규 노드마다 partially enrolled 윈도우 발생

01 서비스 메시와 Istio 기초가 정리한 메시 운영 비용은 Ambient에서도 남습니다. 사이드카에서 그 비용은 Pod당 프록시의 CPU·메모리였고 Ambient에서는 노드 라이프사이클과 메시 데이터플레인 준비를 맞추는 일입니다.

노드 단위로 데이터플레인을 다루는 사고는 03 게이트웨이 노드 분리와 결이 같습니다. 그쪽은 자원 경합을 피하려 노드를 나누고 이쪽은 노드가 준비될 때까지 스케줄을 미룹니다.

이 문서에서 가져갈 것

  • Ambient에서 Pod의 Ready는 메시 준비를 뜻하지 않습니다. istio.io/dataplane-mode=ambient 레이블은 의도일 뿐이고 실제 참여는 istio-cni의 netns 리다이렉션 규칙과 ztunnel의 workload 등록이 둘 다 끝나야 성립합니다.
  • 등록 상태는 ambient.istio.io/redirection annotation으로 판별합니다. enabled면 captured, pending이면 ztunnel이 아직 프록시하지 않는 상태입니다. annotation이 아예 없는 경우가 더 위험합니다 — 트래픽이 실패하지 않고 mTLS·AuthorizationPolicy·telemetry를 우회한 채 성공합니다.
  • kube-scheduler는 DaemonSet 준비를 워크로드 스케줄링의 선행 조건으로 보장하지 않습니다. 노드 프로비저닝이 잦은 환경(오토스케일, 노드 교체)일수록 이 경합을 반복해서 겪습니다.
  • 해법은 사전 차단입니다. cni.istio.io/not-ready startup taint로 스케줄 자체를 막고 istio-cni Ready 이후 untaint-controller가 taint를 뗍니다. 재시도 로직은 있지만 재시도가 성공하기 전까지는 그대로 트래픽 유실입니다.
  • 설정은 pilot.taint.enabled(권한·네임스페이스)와 PILOT_ENABLE_NODE_UNTAINT_CONTROLLERS(컨트롤러 실행) 두 손잡이가 짝이며 Istio 1.30부터는 전자가 후자를 자동 구성합니다. taint를 붙이는 쪽은 인프라(Karpenter NodePool 등) 이고 컨트롤러는 떼기만 합니다.

소스

마지막 수정 일자