1. 서론
envoy-internals 시리즈를 통해 envoy 아키텍처에 대해서 자세하게 살펴봤습니다. 이번부터 진행되는 시리즈는 envoy 위에서 동작하는 istio가 어떻게 상호작용하는지 그리고 이를 위해 어떤 내부 구조를 지니고 동작 원리가 어떻게 되는지에 대해서 자세하게 살펴보고자 합니다.
이번 포스팅에서는 envoy proxy를 활용하기 위한 istio의 역할과 아키텍처에 구조에 대해서 일부 다루어보겠습니다.
2. envoy proxy와 istio

이전 envoy 시리즈를 통해 envoy proxy는 service 앞단에 위치하고 요청에 대한 라우팅 정책, Circuit Breaker, mTLS 통신과 같은 인프라단에서 제공해주는 네트워크 기능을 대신 처리함을 확인했습니다. 그에 따라 Client가 Service를 호출할 때 다이렉트로 Service를 호출하는 것이 아니라 envoy proxy를 경유해서 upstream으로 트래픽이 전달됩니다.
위와 같은 구조를 가졌을 때, 애플리케이션 배포할 때는 대개 Service만 배포하지 않고 envoy proxy와 Service간 연결 설정 정보가 포함된 하나의 형태로 패키징하여 배포합니다. 이러한 배포 형태를 사이드카 패턴이라고 부르며, Kubernetes를 통해 배포할 경우 1개의 Pod내에 Service와 Proxy Container를 엮어서 같이 배포하는 것이 일반적입니다.
이때 Service가 단일로 구성되어있다면, envoy 설정 정보가 포함된 1개의 Pod를 잘 만들어서 배포할 경우 서비스 제공에 문제가 없을 것입니다.

그렇다면 MSA 환경에서는 어떨까요?
MSA 환경에서는 위와 같이 시스템을 구축하기 위해 협력해야하는 Service가 다수가 될 것입니다. 그리고 이때 문제가 되는 것은 협력 해야할 Service가 지속적으로 추가되거나 변경되는 일이 자주 이루어진다는 것입니다. 즉 envoy proxy간 통신을 위해서는 서로간의 정보를 추적하고 이를 반영해야 서비스를 안전하게 제공할 수 있음을 의미합니다.
envoy 구조 포스팅에서 살펴봤듯이 envoy 내부 컴포넌트 변경을 위해서는 static과 dynamic 방법이 있음을 설명했습니다. 만약 Service가 지속적으로 추가되거나 변경될 때 static 방식으로 처리해야한다면, 위 예시와 같이 Service A 입장에서 Service B, Service C가 추가될 때마다 static resources 파일을 변경해서 재기동을 수행해야하는 문제점을 야기합니다. 따라서 이 경우에는 dynamic 방식을 활용하여 xDS를 통해 config 파일 변경 없이 기동 시점에 동적으로 Cluster 정보, Endpoint 정보 등을 변경하는 것이 바람직합니다.

그렇다면 envoy proxy에 xDS를 통해 올바르게 정보를 제공하기 위해서는 누군가는 Service의 등록/수정/삭제 등을 지속적으로 추적하고 관리하는 Control plane이 필요할 것입니다. 아마 눈치채셨겠지만, istio가 바로 해당 Control Plane의 역할을 수행합니다.
그렇다면 istio는 어떠한 원리로 Service의 등록/수정/삭제를 추적하고 관리하고, envoy proxy들과 Connection을 맺고 있으며 어떻게 데이터를 전달할까요?
이러한 목표를 달성하기 위해서 istio에서는 pilot 아키텍처를 제공하였습니다.

Pilot은 말 그대로 조종사를 의미합니다. 마치 istio를 통해서 서비스와 endpoint 정보를 제공하여 올바르게 애플리케이션이 동작할 수 있도록 조종하는 역할을 수행합니다. pilot 아키텍처에는 이를 구현하기 위해 두 개의 컨테이너 이미지가 제공됩니다. 하나는 pilot-discovery 나머지는 pilot-agent입니다. 각각 이미지가 무슨 역할을 수행하는지 알아보겠습니다.

pilot-agent는 envoy proxy를 wrapping하고 있는 프로그램으로써 pilot-discovery로부터 Service 변경 사항을 통지받으면, 내부의 envoy proxy에게 이를 전파하여 envoy proxy 내부 설정을 변경하는 중계자 역할을 수행합니다.
반면 pilot-discovery는 Control Plane의 역할인 등록/수정/삭제되는 Service를 감지하고 이를 연결된 모든 pilot-agent에게 전달하는 역할을 수행합니다. 다만 여기서 알아야 할 중요 포인트는 pilot-discovery 자체가 Service 라이프 사이클을 직접 추적하지 않는다는 점입니다. 즉 외부에 존재하는 Service Registry로부터 관련 이벤트를 전달받아 이를 전파하는 중계의 역할만을 담당합니다. 따라서 istio에서는 3가지 방법을 통해 이벤트를 전달받을 수 있는 창구를 제공합니다.
2-1 MCP

첫번째 방식은 MCP(Mesh Configuration Protocol)를 활용하는 방법입니다. Service Discovery 기능을 제공하는 Platform은 많습니다. 가령 위와같이 Consul, Eureka 등이 예입니다. 이때 개별 Platform 마다 Service Discovery 제공 format은 상이할 것이므로 만약 istio에서 각각의 Platform format을 지원해야한다면, 시스템 의존성이 강해질 것입니다. 이는 유지보수 측면에서 굉장히 어려움을 겪을 수 있음을 의미합니다.
따라서 외부 의존성을 줄이기 위해서 istio 내부에서 사용할 수 있는 MCP를 만들고 Service Discovery에서는 MCP 포맷으로 데이터를 전달받아 처리하도록 설계되었습니다. 또한 개별 Service Registry에서 바로 MCP 포맷으로 보내줄 수 없는 경우에는 위와 같이 중간에 Adapter 역할을 수행하는 MCP Server를 통해 pilot-discovery에 전달하도록 구성할 수 있습니다.
2-2 File
두번째 방식은 File 방식입니다. 이는 local에 저장된 Configuration file을 주기적으로 읽어들여서 메모리에 캐싱하고 해당 정보를 토대로 Configuration을 수행하는 방식입니다. 해당 방식은 주로 istio를 테스트할 때 사용합니다.
2-3 Kubernetes

세번째 방식은 Kubernetes 기반의 Service Discovery 방식입니다. Kubernetes 플랫폼을 사용한다면, 자체적으로 Service의 등록/삭제/수정과 관련된 이벤트를 제공받을 수 있습니다. 저를 포함하여 istio를 사용하는 대부분의 사용자는 kubernetes위에서 istio를 사용하는 경우가 많을 것이기 때문에 대개 Service Discovery Provider로써 Kubernetes를 사용할 것입니다.
본 포스팅 또한 위 세가지 방법 중 Kubernetes를 통한 Service 전달 방식에 대해서 자세하게 알아보겠습니다.
3. Informer
이전 내용에서 확인했듯이 pilot-discovery는 외부에 존재하는 Service Registry로부터 Service의 변경 사항을 전달받아 이를 pilot-agent에게 전달하는 역할을 수행합니다. 그리고 이를 위한 인터페이스 방법으로 3가지가 있음을 확인했습니다. 그 중 kubernetes가 가장 보편적으로 사용되는데, istio는 어떻게 kubernetes로부터 이벤트를 전달받을 수 있을까요?
kuberetes 내부에는 여러가지 Controller 즉 Scheduler, Service controller 등이 있습니다. 그리고 해당 컴포넌트의 역할은 kube-apiserver로부터 관심 대상 Resource에 대해서 변경사항이 발생했을 때, Watch 메커니즘을 통해 Resource를 전달받고 후속 작업을 처리합니다. istio 또한 kubernetes로부터 Resource 변경에 대해 통지를 받아 후속처리하는 역할로써 kubernetes의 Custom Controller라고 볼 수 있습니다.
kubernetes의 Controller는 내부적으로 client-go sdk를 통해서 Kube API Server와 통신하고 Watch 메커니즘을 제공합니다. 그리고 이를 위해서 내부에는 informer구조를 사용하고 있습니다.

그렇다면 informer는 왜 사용할까요? 만약 Controller 내부에 있는 컴포넌트들이 Resource 정보를 얻기 위해서 개별적으로 kube-apiserver와 통신을 수행한다면, kube-apiserver 입장에서는 부하가 걸릴 수 있습니다.
따라서 kubernetes 개발자들은 이러한 kube-apiserver의 API 호출 부담을 줄여주고자 Controller 내부에 캐싱 기능과 kube-apiserver와의 효율적인 통신을 위한 informer 메커니즘을 설계하였습니다.

해당 구조를 개략적으로 살펴보면 위와 같습니다. 가령 하나의 Program안에 여러개의 Controller가 존재하고 그 안에서 Resource 변화를 감지를 통지받아야한다면, informer를 통해 통지를 전달받고 싶은 대상을 등록합니다. 그리고 informer가 kube-apiserver에 요청을 전달하여 list 목록을 얻어와서 자신의 Local Thread Safe Cache에 저장합니다.
(※ 참고로 위 그림의 informer는 SharedInformer 구조를 의미하며, istio에서는 이를 위해 SharedInformerFactory를 사용합니다. 자세한 내용은 https://rudecamel.tistory.com/35 내용을 참고하시기 바랍니다.)
이후 Watch 메커니즘을 통해서 kube-apiserver로 부터 이벤트를 전달받으면, 해당 이벤트 내역을 개별 Controller들에게 Callback 하는 방식으로 이벤트를 처리합니다.

조금 더 자세히 살펴보면, Controller Application을 살펴보면 위와 같이 Client-go에서 제공하는 informer 영역과 Controller의 비즈니스 로직 두 부분으로 나눌 수 있습니다.
이때 Controller가 변경 통지를 희망하는 GroupVersionResource를 informer에게 등록하고 향후 해당 이벤트가 전달되었을 때 처리를 희망하는 내부의 여러 Process들이 Controller에게 Add/Update/Delete Callback Function을 등록합니다.

이후 informer가 기동되면, 다음과 같은 과정을 거칩니다.
1. Reflector가 kube-apiserver에게 llist API를 통해서 가장 최신의 resourceVersion이 포함된 Resource 정보를 가져옵니다.
2. 가져온 Resource 정보는 DeltaFIFO에 적재합니다. DeltaFIFO는 Queue 형태로 구성되어있으며, add, update, delete, list, pop 과 같은 연산을 제공합니다.
3. Informer는 DeltaFIFO에 적재된 데이터를 가져옵니다.
4. Informer는 Indexer에게 전달된 데이터 내용을 저장하도록 명령합니다.
5. Informer에 등록된 Controller의 ResourceEventHandler Callback function을 호출합니다.
6. ResourceEventHandler Callback function에서 filtering 룰을 등록한게 있으면, 해당 이벤트는 제외하고 난 다음 관심 대상의 Resource 정보를 WorkQueue에 삽입합니다.
7. Controller는 WorkQueue로 부터 Resource 정보를 획득하여 자신이 보유한 Processor들에게 전파합니다.
8. Processor들은 전달받은 정보를 토대로 비즈니스 로직을 수행합니다. 만약 이 과정에서 Resource에 대한 정보가 필요하다면, Lister interface를 통해서 Indexer에게 Resource 정보를 요청할 수 있습니다. 요청받은 Indexer는 Local Cache로 부터 정보를 제공하여 Processor에서 불필요하게 kube-apiserver에 질의하는 것을 차단할 수 있습니다.
9. 초기화 과정이 끝나면 Reflector는 Watch API를 요청하여 kube-apiserver로부터 Resource 정보의 변경이 있을 때마다 내용을 전달받습니다. 그리고 이전의 흐름 그대로 호출이 이어지면서, 후속 작업을 수행합니다. 이때 Watch API는 HTTP의 Chunked Transfer-Encoding 기법을 사용하여 Connection을 유지하도록 합니다. 이에 대한 설명은 아래 블로그를 참고하시기 바랍니다.
https://mutpp.tistory.com/10
HTTP Chunked Message를 알아보자
# if 0 두달간의 노예 일상을 이제야 끝나고,, 너무 오랜만에 들여다본 블로그,, 개발자로 지내고 있지만,, 일하고 싶지는 않은 그런 삶,, 갑작스럽게 HTTP 프로토콜을 구현해야할 일이 생겨서, 규격
mutpp.tistory.com
지금까지 client-go에서 제공하는 informer 로직에 대해서 살펴봤습니다. istio를 학습하는데 있어 굳이 kubernetes의 watch 메커니즘을 알아야하나 싶을 수도 있습니다. 하지만 istio의 Service Discovery 동작에 있어서 client-go에서 제공해주는 컴포넌트와 강하게 결합되어 있습니다. 따라서 해당 구조를 이해하면 istio 컴포넌트의 동작 흐름을 쉽게 이해할 수 있습니다.
4. Istio Service Discovery 과정
지금까지 kubernetes가 제공하는 client-go의 informer 구조에 대해서 살펴봤습니다. 지금부터는 istio가 어떻게 informer와 연결되어 Service Discovery를 수행하는지에 대해서 살펴보겠습니다.

informer 구조에서 살펴본 것과 같이 informer에는 여러개의 Controller가 존재하고 해당 Controller가 요구하는 Resource 정보를 가지고 있다가 변화가 발생하면, Controller에게 전달함을 확인했습니다. 그에따라 istio에서는 위와 같이 여러개의 Controller를 정의하고 해당 event 정보를 수신하여 비즈니스 로직을 처리하도록 구성되어있습니다.

Control Plane에서는 pilot-agent와 통신을 위한 gRPC Server가 존재합니다. 해당 Server의 역할은 pilot-agent의 Connection을 저장하고 데이터 통신 시 해당 Server를 통해 pilot-agent에게 정보를 전달하는 역할을 수행합니다. 그리고 해당 gRPC Server를 담당하는 것은 pilot-discovery에 속한 XDS Server입니다.
따라서 Controller의 Event가 감지하면 1차적으로 XDS Server에게 해당 내용이 전달되고 XDS Server에서는 전달받은 Resource를 envoy가 이해할 수 있는 XDS 형태로 변환하여 이를 ADS에 전달하여 연결된 모든 pilot-agent에게 정보를 전달하는 역할을 수행합니다.
5. 마치며
이번 포스팅에서는 envoy proxy에게 xDS 정보 제공을 위한 istio control-plane의 기능과 Service Discovery 과정에 대해서 개략적으로 살펴봤습니다. pilot-discovery의 주요 컴포넌트에 대해서는 추후 다루어보도록 하고 이번 포스팅에서는
kubernetes의 informer 구조에 대한 이해와 XDS Server를 통해 envoy proxy들에게 Service Discovery 기능을 제공한다는 점을 기억하면 좋을 것 같습니다.
'MSA > Istio' 카테고리의 다른 글
| 11. [istio-internals] Pilot-discovery 사이드카 컨테이너 주입 (0) | 2026.03.03 |
|---|---|
| 10. [istio-internals] Pilot-discovery - XDS Server (0) | 2026.03.03 |
| 8. [envoy-internals] Client 요청 전달 과정 이해하기 - 2 (0) | 2026.03.03 |
| 7. [envoy-internals] Client 요청 전달 과정 이해하기 - 1 (1) | 2023.05.25 |
| 6. [envoy-internals] Http Connection Manager (0) | 2023.05.24 |