istiod 는 왜 하나로 합쳐졌나
한 줄 요약
Istio 는 "설정을 만드는 쪽(istiod)"과 "패킷을 만지는 쪽(Envoy)"을 완전히 분리했습니다. 이 분리 덕분에 컨트롤 플레인이 죽어도 트래픽은 흐르고, 반대로 컨트롤 플레인만 고쳐도 전체 메시의 행동이 바뀝니다.
왜 이게 필요했나
Istio 1.5 이전에는 Pilot(트래픽), Citadel(인증서), Galley(설정 검증), Mixer(정책·텔레메트리)가 각각 별도 파드로 떠 있었습니다. 문제는 이 넷이 서로를 호출하면서 생기는 운영 비용이었습니다. 구성요소 하나가 죽으면 어느 기능이 멈추는지 파악하기 어려웠고, 특히 Mixer 는 요청마다 컨트롤 플레인을 호출하는 구조라 데이터 경로의 지연과 장애가 컨트롤 플레인에 직결됐습니다.
그래서 1.5 에서 Pilot·Citadel·Galley 가 istiod 라는 단일 바이너리로 합쳐졌고, Mixer 는 1.8 에서 아예 제거됐습니다. 텔레메트리는 Envoy 안의 필터가 직접 생성하는 방식으로 바뀌었습니다. 요점은 "컴포넌트를 줄였다"가 아니라 데이터 경로에서 컨트롤 플레인 호출을 완전히 걷어냈다는 것입니다.
본문의 역사 설명이다. 1.5 이전과 이후를 데이터 경로의 관점에서 견준다. 버전 번호는 본문의 서술을 그대로 옮겼다.
- 1.5 이전: 구성요소 넷이 따로 떠 있었다Pilot, Citadel, Galley, Mixer 가 각각 별도 파드였고 서로를 호출하는 운영 비용이 컸다. 하나가 죽으면 어느 기능이 멈추는지 파악하기 어려웠다. 특히 Mixer 는 요청마다 컨트롤 플레인을 호출해서 데이터 경로의 지연과 장애가 컨트롤 플레인에 직결됐다.
- 이후: istiod 하나와 Envoy 안의 텔레메트리1.5 에서 Pilot, Citadel, Galley 가 istiod 라는 단일 바이너리로 합쳐졌고 Mixer 는 1.8 에서 제거됐다. 텔레메트리는 Envoy 안의 필터가 직접 만든다.
여기서 구분할 것 요점은 컴포넌트를 줄였다는 것이 아니라 데이터 경로에서 컨트롤 플레인 호출을 완전히 걷어냈다는 것이다.
잠깐, 예측해 보세요 istiod 로 합친 이득을 파드 개수가 줄어든 것뿐이라고 정리한 동료가 있다. 무엇을 덧붙여야 할까?
설명 확인 · 채점 없는 자가 점검
요청마다 컨트롤 플레인을 부르던 Mixer 식 호출을 데이터 경로에서 없앴다는 점이다. 그래서 설정을 만드는 쪽과 패킷을 만지는 쪽이 분리되고, 컨트롤 플레인이 멈춰도 트래픽은 흐른다.
어떻게 동작하나
istiod 는 쿠버네티스 API 를 감시해 Service·Endpoint·Pod 와 Istio CRD 의 변화를 읽고, 그것을 Envoy 가 이해하는 설정으로 번역해 gRPC 스트림으로 밀어 넣습니다. 이 프로토콜이 xDS 입니다.
| API | 무엇을 내려보내나 |
|---|---|
| LDS | 리스너 — 어떤 포트를 어떤 프로토콜로 듣는가 |
| RDS | 라우트 — 어떤 요청을 어떤 클러스터로 보내는가 |
| CDS | 클러스터 — 목적지 그룹의 정의(LB, 서킷브레이커, TLS) |
| EDS | 엔드포인트 — 그 클러스터에 실제로 들어 있는 파드 IP |
| SDS | 인증서와 키 |
Istio 는 이 다섯을 ADS(Aggregated Discovery Service) 라는 하나의 gRPC 스트림으로 묶어 보냅니다. 이유는 대역폭이 아니라 순서입니다. 클러스터(CDS)가 정의되기 전에 엔드포인트(EDS)가 도착하면 Envoy 는 갈 곳 없는 주소를 받게 되고, 라우트(RDS)가 리스너(LDS)보다 먼저 오면 붙일 곳이 없습니다. 스트림이 하나면 순서가 보장되고, 설정이 원자적으로 갈아 끼워집니다.
Envoy 안에서 목적지는 이런 이름으로 존재합니다.
outbound|9080|v2|reviews.default.svc.cluster.local
방향 포트 subset FQDN
istioctl proxy-config cluster 에서 이 문자열을 읽을 줄 알면 절반은 끝난 것입니다. subset 자리가 비어 있으면 DestinationRule 이 없다는 뜻이고, 이름이 아예 없으면 그 서비스가 이 프록시의 시야 밖이라는 뜻입니다.
파드 안에서는 istio-init 컨테이너가 iptables 규칙을 심어 트래픽을 프록시로 꺾습니다. 인바운드는 15006, 아웃바운드는 15001 로 갑니다. 이때 UID 1337 의 트래픽만은 리다이렉트에서 제외되는데, 그 UID 가 바로 istio-proxy 자신이기 때문입니다. 제외하지 않으면 프록시가 내보낸 패킷이 다시 프록시로 돌아와 무한 루프가 됩니다. 그 밖에 15008(HBONE 터널), 15020(에이전트 통합 헬스·메트릭), 15021(헬스 체크), 15090(프로메테우스 메트릭)을 외워 두면 시험에서 포트 문제를 놓치지 않습니다.
마지막으로 중요한 성질 하나. istiod 가 전부 죽어도 이미 떠 있는 Envoy 는 마지막으로 받은 설정으로 계속 트래픽을 처리합니다. 멈추는 것은 새 설정 전파와 인증서 갱신이지 데이터 경로가 아닙니다. 그래서 istiod 장애는 "즉시 전면 장애"가 아니라 "시한부 상태"이고, 인증서 수명(기본 24시간)이 실질적인 타이머가 됩니다.
현장에서 만나는 모습
저자의 홈랩은 7노드(cp-1/2/3 + gpu-a/b/c/d), 쿠버네티스 v1.34.10, containerd 1.7.27, 커널 6.14 위에 Cilium 1.20.1 로 서 있습니다. 사이드카 메시는 올라가 있지 않은데, 이 대비가 오히려 Istio 를 이해하는 데 도움이 됩니다.
이 클러스터는 kubeadm init --skip-phases=addon/kube-proxy 로 kube-proxy 를 처음부터 설치하지 않고 세웠습니다. 한 번이라도 돈 kube-proxy 는 노드에 KUBE-SERVICES / KUBE-SVC-* / KUBE-SEP-* 체인을 새겨 놓고, DaemonSet 을 지워도 그 규칙은 남아 eBPF 데이터패스와 충돌합니다. 실측 결과 kube-proxy 파드 0개, iptables KUBE- 체인 0개였습니다.
여기서 배울 점은 Istio 에도 똑같이 적용됩니다. 사이드카 주입은 파드 네트워크 네임스페이스 안에 iptables 규칙을 새기는 일이고, 주입 라벨을 나중에 지워도 이미 뜬 파드의 규칙은 파드를 재시작하기 전까지 그대로 남습니다. "라벨은 지웠는데 왜 아직 프록시를 타지?"라는 질문의 답이 이것입니다. 설정은 선언적이지만 노드와 파드에 남는 흔적은 명령적입니다.
본문이 컨트롤 플레인과 데이터 경로의 분리가 만드는 성질로 꼽은 대목이다.
- 멈추는 것새 설정 전파와 인증서 갱신이 멈춘다.
- 계속되는 것이미 떠 있는 Envoy 는 마지막으로 받은 설정으로 계속 트래픽을 처리한다. 데이터 경로는 멈추지 않는다.
여기서 구분할 것 이 장애는 즉시 전면 장애가 아니라 시한부 상태이다. 본문은 인증서 수명(기본 24 시간)이 실질적인 타이머가 된다고 설명한다.
잠깐, 예측해 보세요 새벽에 istiod 가 죽었는데 서비스 호출은 계속 성공한다. 아침까지 그대로 두어도 괜찮을까?
설명 확인 · 채점 없는 자가 점검
기한이 있다. 호출이 성공하는 것은 Envoy 가 마지막 설정으로 일하기 때문이고, 새 설정 전파와 인증서 갱신은 멈춰 있다. 본문 기준으로 인증서 수명(기본 24 시간)이 지나기 전에 복구해야 한다.
다음 퀴즈에서 확인할 것
이 모듈은 개념만 다룹니다. 퀴즈로 아키텍처 감각을 점검한 뒤, 다음 모듈에서 VirtualService 와 DestinationRule 을 직접 써서 위에서 본 outbound|포트|subset|FQDN 이 어떻게 만들어지는지 손으로 확인합니다.