본문으로 건너뛰기
컨트롤 플레인 해부: istiod

02 · 컨트롤 플레인 해부 — istiod는 왜 CPU를 먹는가

  • istiod 부하는 프록시 수 × 변경 빈도 × 설정 범위의 곱입니다. 세 항이 함께 커지면 CPU가 벽을 칩니다.
  • 증설은 감으로 정하지 않습니다. 1순위 지표는 pilot_proxy_convergence_time(수렴 시간), 그다음이 연결 프록시 수·push 폭주·CPU입니다.
  • CPU 증설은 응급 처치일 뿐입니다. Sidecar 리소스로 각 프록시가 보는 설정 범위를 좁혀야 진짜로 풀립니다.
  • 그다음 레버가 디바운스·discoverySelectors 튜닝, 마지막이 수평 스케일입니다(istiod는 stateless).

그때 무슨 일이 있었나. 클러스터 규모가 커지고 배포가 잦아지면서 컨트롤 플레인 istiod의 CPU가 주기적으로 치솟았습니다. 급한 불은 CPU를 증설해서 껐지만 그건 응급 처치였습니다. 같은 맥락에서 “istio node/pod 리소스 최적화” 과제가 이어졌습니다 — 프록시 쪽 자원과 istiod가 다루는 설정 범위를 함께 손봐야 근본이 잡히기 때문입니다. 여기서는 istiod가 CPU를 먹는 메커니즘, 언제 증설해야 하는지 알려주는 지표, 증설 말고 진짜 해법을 정리합니다.

관련 문서: 01 메시 기초 · 03 데이터 플레인과 게이트웨이 · 05 장애 이야기 · 09 istiod 스케일링과 커넥션 재분배 — 아래 “그다음에 스케일한다"의 함정을 따로 다룹니다

istiod가 실제로 하는 일

istiod는 트래픽이 지나가는 곳이 아닙니다. 하는 일은 하나입니다. 클러스터 상태를 감시해서 각 Envoy 프록시가 알아야 할 설정을 계산해 내려보냅니다.

istiod가 Kube API를 watch해 상태를 계산하고 xDS로 각 Envoy에 push
도식 텍스트
  • Kube API — Service · Endpoint · Pod · Istio CRD
  • istiod — 1. 상태 수집 → 2. Envoy 설정 변환 → 3. push
  • Envoy₁
  • Envoy₂
  • Envoy₃
  • Envoyₙ
  • watch
  • xDS (ADS/gRPC)

프록시로 내려가는 설정은 xDS라는 프로토콜 묶음을 타고 갑니다. 종류별로:

xDS이름무엇을 알려주나
CDSCluster Discovery어떤 상위 서비스(cluster)들이 있는가
EDSEndpoint Discovery각 서비스의 실제 엔드포인트(파드 IP) 목록
LDSListener Discovery어떤 포트/프로토콜을 받는가
RDSRoute Discovery요청을 어떤 규칙으로 라우팅하는가
SDSSecret DiscoverymTLS 인증서·키

Istio는 이들을 ADS(Aggregated Discovery Service) — 프록시당 하나의 gRPC 스트림 — 로 묶어 보냅니다.

CPU를 먹는 지점: push

istiod의 부하는 대부분 push 과정에서 나옵니다. 클러스터에 변화가 생기면 istiod는 영향받는 프록시들에 새 설정을 밀어내야 합니다. 무엇이 “변화"이고 그 한 건이 몇 개의 프록시를 건드리는지가 부하를 좌우합니다.

변화를 유발하는 것들:

  • 엔드포인트 변경 — 파드가 뜨고 지는 것. 배포 한 번, HPA 스케일 한 번마다 EDS가 갱신됩니다. 가장 잦은 변화입니다.
  • 설정 변경 — VirtualService·DestinationRule 등 CRD 수정. CDS/LDS/RDS 재계산.
  • 인증서 회전 — SDS.

push 한 번의 비용은 대략 이렇게 곱해집니다:

push 비용 ≈ (영향받는 프록시 수) × (프록시당 설정 크기) × (직렬화·계산)

여기서 규모의 함정이 드러납니다. 기본 설정에서 각 프록시는 메시 전체를 알 수 있는 설정을 받습니다. 서비스가 500개면 프록시 하나가 500개 서비스 전부의 cluster/endpoint를 들고 있습니다. 그래서:

  • 파드 하나가 뜨고 질 때마다 → 그 변경과 무관한 프록시까지 갱신 대상이 될 수 있고,
  • 프록시 수 N이 커지면 → 변경 1건의 fan-out이 N에 비례해 커지며,
  • 배포가 잦으면(잦은 endpoint churn) → push가 초당 수십·수백 건씩 쏟아집니다.

프록시 수 × 변경 빈도 × 설정 범위 — 이 세 항의 곱이 istiod CPU입니다. 클러스터가 크고 배포가 잦아질수록 세 항이 나란히 커지므로 어느 순간 CPU가 벽을 칩니다. 우리가 겪은 “주기적 CPU 급등"도 여기서 나왔습니다.

istiod는 폭주를 막으려 디바운스(debounce)를 둡니다. 변경이 몰아치면 짧은 시간(PILOT_DEBOUNCE_AFTER) 동안 모아서 한 번에 처리하고 그래도 밀리면 push 큐에 쌓입니다. 큐가 길어지면 설정이 프록시에 반영되기까지 지연이 생깁니다. 다음 절에서 볼 지표가 이 지연입니다.

언제 증설하나 — istiod scaling 지표

증설은 감이 아니라 지표로 판단합니다. istiod가 :15014/metrics로 노출하는 Prometheus 지표 가운데 컨트롤 플레인 건강을 보는 신호는 다음과 같습니다.

지표무엇을 말하나위험 신호
pilot_proxy_convergence_time설정 변경→반영 완료 시간(히스토그램)가장 중요. p99 수 초↑면 컨트롤 플레인 지연
pilot_proxy_queue_time변경이 push 큐에서 대기한 시간상승 = istiod가 처리 속도를 못 따라감
pilot_xds이 istiod에 연결된 프록시(XDS 클라이언트) 수인스턴스당 과다 = 수평 확장(replica↑) 신호
pilot_xds_pushes타입별(cds/eds/lds/rds) push 발생 수급증 = endpoint churn·설정 변경 폭주
pilot_xds_push_timepush 생성·전송에 걸린 시간상승 = 설정이 크거나 프록시가 많음
pilot_xds_push_context_errors/pilot_total_xds_rejectspush 생성 오류·설정 거부0이 아니면 설정 문제 조사
container_cpu_usage (istiod)istiod 파드의 실제 CPUlimit 근처 지속 = 증설/스케일 대상
process_virtual_memory / container_memory_working_setistiod 메모리설정·프록시 수에 비례해 증가

읽는 순서는 이렇습니다. 먼저 pilot_proxy_convergence_time(수렴 시간)을 SLO로 봅니다. 이게 안정적으로 낮으면 CPU가 좀 높아도 컨트롤 플레인은 건강합니다. 수렴 시간이 늘기 시작하면 원인을 짚습니다 — pilot_xds(연결 프록시 수)가 크면 수평 확장(istiod replica 추가) 신호, pilot_xds_pushes가 폭주하면 churn·설정 범위 문제, istiod CPU가 limit에 붙어 있으면 수직 증설이 급한 불입니다.

참고 수치(버전 의존). Istio 공식 Performance & Scalability 문서는 “잦은 변경이 있는 부하에서 istiod가 프록시 1,000개당 약 1 vCPU와 1.5GB 메모리 규모를 씁니다"를 대략적인 기준으로 제시합니다. 버전·설정에 따라 크게 달라집니다. 규모 감각으로만 쓰고 실제 배포 버전의 지표로 검증합니다. → 소스

증설은 응급 처치다 — 진짜 해법

CPU 증설(수직)이든 replica 추가(수평)든 부하를 감당할 뿐, 부하 자체를 줄이지는 않습니다. 세 항의 곱을 기억하면 진짜 레버가 보입니다.

1) 설정 범위를 좁힌다 — Sidecar 리소스 (가장 큰 레버)

기본값에선 프록시가 메시 전체 설정을 받습니다. 대부분의 서비스는 소수의 상대와만 통신하는데도 전부를 들고 있으니 istiod는 쓸데없이 큰 설정을 계산·push하고 프록시는 그만큼 메모리를 낭비합니다. Sidecar 리소스로 각 워크로드가 보는 네임스페이스·서비스를 명시적으로 제한하면:

apiVersion: networking.istio.io/v1
kind: Sidecar
metadata:
  name: default
  namespace: team-a
spec:
  egress:
  - hosts:
    - "team-a/*"        # 같은 네임스페이스
    - "istio-system/*"  # 컨트롤 플레인·게이트웨이
    - "shared/*"        # 실제로 부르는 공용 서비스만

이 제한은 곱셈의 세 항 중 설정 범위를 직접 줄입니다 → istiod의 push 계산량↓, 프록시 메모리↓, 무관한 변경의 fan-out↓. “istio node/pod 리소스 최적화” 과제의 본질도 여기 있습니다. CPU를 키운 게 아니라, 프록시가 보는 세상을 좁혀 컨트롤 플레인과 데이터 플레인의 부하를 나란히 낮췄습니다.

2) 변경 빈도·범위를 다스린다 — 디바운스와 discoverySelector

  • 디바운스 튜닝 — PILOT_DEBOUNCE_AFTER, PILOT_DEBOUNCE_MAX로 변경을 더 모아 처리하면 push 횟수가 줍니다. 수렴이 늦어지는 트레이드오프가 있으니 수렴 시간 지표를 보며 조정합니다.
  • discoverySelectors — istiod가 감시할 네임스페이스를 아예 제한해서 메시 밖 네임스페이스의 변화가 push를 유발하지 않게 합니다.
  • 네임스페이스 격리 — 팀·도메인 단위로 설정 경계를 나눠 fan-out 범위를 좁힙니다.

3) 그다음에 스케일한다

범위를 좁힌 뒤에도 부하가 크면 그때 스케일합니다. istiod는 stateless라 수평 확장 자체는 쉽습니다. HPA를 CPU 또는 pilot_xds(연결 수) 기준으로 걸어 배포·트래픽 피크에 대응합니다. 파드 스펙의 CPU/메모리 request·limit은 앞의 참고 수치와 실측으로 잡습니다.

“replica를 늘리면 연결이 분산된다"는 절반만 맞습니다. xDS는 장수 gRPC 스트림이라 이미 맺어진 커넥션은 새 파드로 옮겨가지 않습니다. 새로 뜬 파드는 새 커넥션만 받으므로 스케일아웃 직후에는 기존 파드가 부하를 그대로 떠안는 구간이 생깁니다. 이벤트 규모에서는 이 공백이 OOM으로 이어질 수 있습니다 — 자세한 건 09 istiod 스케일링과 커넥션 재분배.

이 문서에서 가져갈 것

  • istiod의 부하는 push에서 나오고 그 비용은 프록시 수 × 변경 빈도 × 설정 범위의 곱입니다.
  • 증설 판단의 근거는 지표입니다. 1순위는 pilot_proxy_convergence_time(수렴 시간), 그다음 연결 프록시 수·push 폭주·istiod CPU 순으로 원인을 짚습니다.
  • CPU 증설은 응급 처치입니다. Sidecar 리소스로 설정 범위를 좁혀 곱의 한 항을 직접 줄여야 진짜로 풀립니다. 그다음이 디바운스 튜닝, 마지막이 스케일입니다.

소스

마지막 수정 일자