1. 서론

 
이전 포스팅을 통해 envoy를 관리하기 위해서 등장한 istio의 pilot 아키텍처와 Service Discovery 전파를 위한 informer 구조에 대해서 살펴봤습니다. 이번 포스팅에서는 pilot 아키텍처 중 pilot-discovery의 내부 컴포넌트 중 XDS Server에 대해서 살펴보면서 외부로 부터 전달된 Service 및 설정 변경이 어떻게 내부 과정을 거쳐 개별 pilot-agent의 envoy에 전파되는지 알아보겠습니다.
 
 
 


2. XDS Server  개요

 

 
 
이전 포스팅에서 pilot-discovery는 Informer로부터 Kubernetes의 Resource 정보를 전달받으면, Controller의 Handler를 통해서 관련 내용이 XDS Server에게 전달된다고 설명했습니다. 그리고 이를 전달받은 XDS Server의 역할은 개별 Service 들에게 xDS API 형태로 제공하여 Service 정보를 갱신하는 역할을 수행합니다. 이를 위해 XDS Server 내부에는 Service들의 Connection Pool을 관리하고 envoy가 이해할 수 있도록 xDS API로 변환하여 전달하는 컴포넌트 등이 존재합니다.
 
그렇다면 XDS Server는 어떻게 구성되어있을까요?
 

 
핵심 컴포넌트에 대해서만 살펴보자면, 위와 같은 속성을 지니고 있습니다. 그중 특히 informer와 연계되어 Controller의 역할을 처리하는 것은 Enviroment에 속한 ServiceDiscovery입니다. 해당 컴포넌트에 대해서 살펴보겠습니다.
 


3. ServiceDiscovery

 

 
 
ServiceDiscovery의 주요 역할은 istio의 주요 관심 대상 Resource에 대해서 변경이 감지되었을 때, 이를 수신 받아 XDS Server로 전달하는 Controller 역할을 수행합니다. 위 그림을 살펴보면 총 21개가 관심 대상이며, 파란색으로 표시한 Resource는 istio에서 제공하는 CRD가 아니라 Kubernetes Gateway API에 해당하는 항목입니다.
 
참고로 Kubernetes Gateway API는 Kubernetes의 Network 서비스에 대한 표준 스펙을 정의하기 위한 Spec으로 자세한 내용은 아래 공식 문서를 참고하시기 바랍니다.
 
https://gateway-api.sigs.k8s.io/

Introduction - Kubernetes Gateway API

Introduction What is the Gateway API? Gateway API is an open source project managed by the SIG-NETWORK community. It is a collection of resources that model service networking in Kubernetes. These resources - GatewayClass,Gateway, HTTPRoute, TCPRoute, Serv

gateway-api.sigs.k8s.io

 
istio에서는 Kubernetes Gateway API Resource가 생성/수정/삭제되면, 해당 내용을 분석하여 xDS API 변환하여 전달하도록 구현되어있습니다. 다만 Kubernetes Gateway API는 Kubernetes 설치 시 기본적으로 등록된 API가 아니기 때문에 별도로 설치해야합니다.
 
 
 

 
istio에서는 Kubernetes Gateway API 지원을 위해서 위 그림과 같이 환경 변수를 통해 pilot-discovery 프로그램을 수행합니다. 이때 PILOT_ENABLE_GATEWAY_API 환경 변수가 true로 지정되어있는지 여부를 확인합니다. 그 결과 값이 true로 지정된 경우에는 위와 같이 istio에서 제공하는 CRD와 더불어 Kuberntes Gateway API의 Resource 정보를 관심 Collection에 추가합니다. 반대로 해당 값이 false인 경우에는 istio에서 제공하는 CRD만 관심 Collection에 추가하도록 구성되어있습니다.
 
위와 같은 과정을 거치게되면, istio에서 주요 관심 대상에 대한 Resource 추출은 끝나게됩니다. 그 다음 수행해야하는 일은 Controller를 구성하고 Informer에 등록하는 작업입니다.
 
 

 
우선 Controller를 구성했다고 가정하고, 이전 과정에서 추출한 관심 대상을 기반으로 Informer와 연결하는 부분부터 살펴보겠습니다. 이전에 추출한 관심 대상 Resource를 등록하는 작업을 수행합니다. 해당 과정은 총 4가지 절차를 통해 수행됩니다.
 
1. 먼저 kubernetes에 등록된 CRD 정보를 조회합니다.
 
2. 조회한 CRD 정보 중에서 istio의 관심대상 Resource에 부합되는 정보만 Filtering 합니다. 이때 Kubernetes Gateway API는 Kubernetes를 설치했다고 해서 기본적으로 등록되는 Resource 정보가 아닙니다. 따라서 별도로 Gateway API CRD 설치(https://istio.io/latest/docs/tasks/traffic-management/ingress/gateway-api/#setup)를 수행하지 않았을 경우에는 등록된 CRD 정보가 없기 때문에 Filtering 됩니다. 반면 해당 CRD가 설치되어있으면 Filtering 결과에 포함될 것입니다. 참고로 위 예시는 설치가 안되어있다는 가정하에 작성하였습니다.
 
3. 관심 대상 Resource 간에 서로 Group이 다르기 때문에 Group 별로 매칭되는 Informer를 찾습니다.
 

 
4.  Resource Group에 해당하는 Informer에 Resource 생성/수정/삭제가 발생했을 경우 Callback을 위한 Handler를 등록합니다.
 

 
위 코드내용을 살펴보면, 개별 Callback 메소드에서 수행하는 작업은 Controller Queue에 Event 타입과 전달받은 Object 내용을 전달하도록 구성되어있음을 확인할 수 있습니다.
 

 
 
이번에는 ServiceDiscovery 즉 Controller를 구성하고 있는 주요 컴포넌트에 대한 소개와 더불어 해당 컴포넌트와 이전에 설명한 informer 연계 과정이 어떻게 연결되는지를 살펴보겠습니다.
 
ServiceDiscovery에는 3가지 주요 속성이 존재합니다. 하나씩 살펴보면 다음과 같습니다. 먼저 queue는 informer로부터 Event가 발생하였을 때 이를 전달하는 중간 버퍼의 역할을 수행합니다.
 
두번째로 살펴볼 것은 handlers입니다. istio의 관심 대상 Resource Group이 여러개이다 보니 GVK(Group Version Kind)별로 대상 informer가 다르고 이를 처리해야하는 Handler 로직 또한 달리 작성해야할 수 있습니다. 따라서 Service Discovery에서는 GVK 별로 Handler를 매핑하여 Map으로 관리하고 있습니다.
 

 
이때 GVK 별로 생성되는 Handler의 모습은 위와 같으며, 가장 중요한 부분은 informer로 부터 전달받은 설정을 기반으로 PushRequest 요청을 만들고 이를 XDSServer.ConfigUpdate 메소드를 호출함으로써, XDSServer와 연결 되어있는 모든 Client에게 설정 정보를 동기화하도록 요청하는 것입니다.
 

 
세번째로 살펴볼 것은 kinds로 GVK 별로 매핑되는 informer 정보가 다르기 때문에, 해당 정보를 관리하기 위한 용도로 사용됩니다. handlers와 마찬가지로 Map으로 구성되어있으며 GVK 별로 어떤 Resource가 있으며, 어떤 informer와 매칭되어있는지 정보를 구조화하여 담고 있습니다.
 

 
지금까지 설명한 내용을 바탕으로 Service Discovery와 istio Kube Client의 연계 과정을 살펴보겠습니다.
 
1. 관심 대상 Resource를 순회하면서, GVK 별로 대상 informer를 찾습니다.
2. informer를 찾았으면 event 발생할 경우 정보를 전달받기 위한 Handler를 등록합니다. 이때 해당 Handler는 GVK를 Key로 가지고 Callback Function을 Value로 가지는 handlers Map에서 GVK에 매칭되는 Function을 등록합니다. 이때 등록되는 Function에는 XDSServer로 Update하는 코드가 포함되어있습니다.
3. queue에서는 지속적으로 하나씩 processing 하면서 informer로 부터 전달받은 오브젝트 정보를 기반으로 매칭되는 Handler 실행을 통해 XDS Server로 Update를 수행합니다.
 
여기까지가 XDS Server 내에 위치한 ServiceDiscovery의 역할입니다.


4. Event 전파

 
이번에는 이전 내용에서 XDSServer.ConfigUpdate 메소드를 호출했을 때, XDS Server 내부에서 어떻게 처리하는지를 살펴보겠습니다.
 

 
ConfigUpdate 메소드가 호출되면, 가장 먼저 하는 일은 XDSServer에 있는 PushChannel에 요청을 전달합니다. 위 그림에서 확인할 수 있듯이 해당 속성은 PushRequest를 전달받아 다른 루틴으로 전달하는 Channel임을 알 수 있습니다.
 
이후 Channel로 전달된 데이터는 내부적으로는 debounce 메소드가 별도의 go routine으로 동작하여 해당 데이터를 처리합니다. 그렇다면 debounce 작업은 왜 하는 것일까요?
 

4-1 debounce

 

 
debounce는 이벤트가 연이어 발생했을 때, 이를 개별적으로 하나씩 처리하지 않고 그룹핑하여 전달하는 것에 목적이 있습니다. 이에 대한 이해를 돕기위해 최대 5명 운송 가능한 엘리베이터를 이용하는 상황을 가정해봅시다.
 
만약 엘리베이터에 대기하는 사람이 6명이 존재하는 상황에서 최대 5명까지 이용 가능하지만 이용객 한명씩 순차적으로 엘리베이터를 사용할 수 있다면 어떻게 될까요? 당연히 운송 시간은 증가할 것이며, 엘리베이터 가동 비용 측면에서 봤을 때에도 굉장히 비효율입니다. 이때 가장 효율적인 방법은 엘리베이터가 허용 가능한 인원만큼 탑승해서 한번에 운반하는 것입니다. 따라서 위 경우에서는 처음 5명을 태운 승강기를 한번 운행하고 그 다음 한번 더 운행 즉 총 2번의 운행하는 것이 가장 좋습니다.
 

 
이번에는 엘리베이터에 한명이 현재 탑승한 상황이라고 가정해봅시다. 이때 시설 관리자 입장에서는 5명까지 이용한 엘리베이터에서 한명만 운행하는 것은 금전적으로 비효율입니다. 따라서 시설 관리자 입장에서만 생각해본다면, 다섯명이 탑승할 때까지 엘리베이터를 기다리게 하는 것이 가장 좋은 선택입니다. 하지만 이 경우에는 다섯명이 탑승할 때까지 엘리베이터가 작동하지 않으므로 탑승객 입장에서는 엄청난 불편함을 초래할 것입니다.
 

 
따라서 두 이해 당사자간의 입장을 고려하여 설계된 일반적인 엘리베이터의 모습은 엘리베이터에 탑승객이 탑승하고나서 일정시간이 지날 때까지 탑승하는 인원이 없으면 문이 닫히고 동작하도록 되어있습니다. 또한 중간에 엘리베이터에 탑승하려는 승객이 추가로 발생할 경우에는 센서가 감지하여 엘리베이터 문이 닫히지 않도록 하며, 그때부터 다시 일정 시간 동안 대기하도록 되어있습니다.
 
위와 같은 엘리베이터는 탑승객 입장에서는 정원이 다 차지 않아도 일정 시간동안 승객의 유입이 없으면 동작하기 때문에 다소 불편함을 감소할 수 있습니다. 또한 시설 관리자 입장에서도 엘리베이터가 아직 닫히지 않는 이상 추가 이용 승객이 발생하면 탑승 가능하기 때문에 합리적인 방법입니다.
 

 
 
이번에는 100명이 이용할 수 있는 엘리베이터가 있고 최초 승객이 탑승 이후 약 10초 이후에 엘리베이터가 자동으로 닫힌다고 가정해봅시다. 이러한 상황에서 최초 승객이 탑승한 이후에 10초마다 새로운 탑승객이 발생한다면 어떻게 될까요? 처음 탑승한 승객은 이전과 같이 엘리베이터가 수용 가능한 모든 인원이 탑승할때까지 대기하게되는 문제가 발생합니다. 따라서 이러한 경우에 승객의 불편함을 최소화할 수 있는 방법은 처음 엘리베이터에 탑승객이 탑승한 이후로부터 최대 대기시간을 설정하여 최대 대기시간을 넘기게되면 추가 승객이 탑승하더라도 타이머가 더 이상 동작하지 않고 닫히도록 구현하는 것입니다. 이렇게 되면 처음 탑승한 승객 입장에서는 최대 대기시간 만큼만 대기하면 되므로 불편함은 최소화 될 수 있을겁니다.
 
지금까지 엘리베이터 시나리오를 설명했는데, istio에서는 위 시나리오 내용과 같은 이유로 동일하게 debounce를 적용했습니다. istio pilot-discovery에는 모든 pilot-agent와 Connection을 맺고 있는데, 이벤트 하나하나씩 발생할 때마다 broadcast하는 것은 굉장히 통신 비용을 증가하는 요인입니다.
 

 
따라서 이때 debounce를 지정하면, 메시지를 효과적으로 처리할 수 있습니다. 가령 최초 PushReqest 요청을 전달받았을 때로 부터 일정 시간 내에 메시지가 유입되는 경우 해당 메시지와 이전 메시지를 병합합니다. 참고로 이때 병합되는 메시지는 PushRequest내에 있는 Reason Slice에 기존 메시지가 추가됩니다.
 
이후 또 다시 일정 시간 이내에 메시지가 추가 유입되면 병합하고 만약 그 시간동안 메시지가 유입되지 않는다면 지금까지 병합한 메시지를 한번에 전송합니다. 다만 이러한 경우 메시지가 계속 추가로 유입되는 경우 한없이 기다릴 수 있기 때문에 최대 대기 시간을 설정하여 최대 대기 시간이 지나면 메시지 처리를 수행하도록 수행할 수 있습니다.
 
istio에서는 PILOT_ENABLE_EDS_DEBOUNCE 환경 변수가 true로 되어있을 때 debounce가 작동하도록 설계되었습니다. 해당 값은 기본적으로 true이기 때문에 별도의 설정이 없다면 항상 debounce는 작동하고 있습니다.
 
그리고 PILOT_DEBOUNCE_AFTER와 PILOT_DEBOUNCE_MAX 값은 이전에 설명한 일정 대기 시간과 최대 대기 시간을 의미합니다. 기본적으로 일정 대기 시간은 100ms 로써 100ms 동안 Event가 발생하면 해당 Event는 하나의 메시지로 병합됩니다. 또한 최대 대기 시간은 10초로 설정되어있으며, 메시지가 아무리 지속 유입이 된다고 할지라도 10초를 넘어가면 메시지 병합을 멈추고 메시지를 발행하도록 되어있습니다.
 
따라서 만약 istio를 운영하다가 너무 잦은 sync로 인한 overhead가 발생한다면 해당 두 값을 적절하게 튜닝하여 메시지 동기화 속도를 조절할 수 있습니다.
 

 
debouce 작업이 끝나면, 하나의 메시지로 통합되며, 이를 통해 하나의 요청만 전달할 수 있습니다. 그 다음 작업은 해당 메시지를 개별 client로 전달하는 것입니다. 이때 전체 Client의 정보는 AdsClients에 저장되어 있습니다. 따라서 Client 별로 해당 메시지를 매핑하여 Queue에 입력합니다. 여기까지가 debounce 작업과 후속 메시지 처리 작업입니다.
 


4-2 PushQueue

 
이전 내용에서 debounce 이후 PushQueue에 Client 정보와 요청 정보 메시지를 삽입하는 것을 확인했습니다. 이때 Enqueue 메소드 안에서는 여러가지 작업이 이루어지는데, 이와 관련하여 PushQueue의 구조와 수행 동작에 대해서 살펴보겠습니다.
 

 
Push Queue의 주요 속성은 위 3가지입니다. queue에는 Client의 연결을 slice 형태로 저장하고 있고 Client 별로 pending과 processing의 Map이 존재하는 것을 확인할 수 있습니다. 위 세가지 자료 구조는 Push Queue가 동작하는데 있어 주요하게 사용됩니다.
 
먼저 Queue에 삽입할 때 과정을 살펴보겠습니다.
 

 
debounce를 통해 입력되는 쓰레드와 Push Queue 처리 쓰레드는 별개의 go routine으로 동작합니다. 따라서 Push Queue에 빠르게 적재되었을 지라도 Push Queue의 처리 능력에 따라서 아직 처리되지 않고 Queue에 남아있을 수 있습니다.
 
따라서 가장 먼저 수행하는 일은 queue 속성에 Client를 저장하는 작업 이외에 Client를 Key, 요청 Requset를 Value로 사용하는 pending Map에 메시지를 집어 넣습니다. 또한 Client는 queue Slice에 추가합니다.
 
이때 모습은 위 그림과 같은 형태일 것입니다. 만약 이러한 상황에서 debounce 작업이 한차례 더 진행되고 이때 Service A에 대해서 새로운 PushRequest가 발생했는데, 아직 Push Queue에서 기존 데이터가 처리되지 않은 상황이라면 어떻게될까요?
 

 
아직 메시지가 전송된 상태가 아니기 때문에 debounce에서 메시지들을 Merge 했던것 처럼 해당 Client에 전송되는 Message에 대해서 Merge 작업을 수행하도록 진행합니다. 따라서 Pending의 목적은 처리되지 않은 메시지를 저장함과 동시에 추가로 발행되는 메시지 중 Connection 정보가 일치하는 요청에 대해서는 Merge 작업을 수행하여 메시지 처리량을 높이는 효과가 있습니다.
 

 
Push Queue에서 주기적으로 Queue에 데이터가 존재하는지 탐색하고 처리하는 작업은 doSendPushes 메소드에서 수행하는데, 해당 메소드는 별도의 go Routine으로 동작합니다. doSendPushes에서 처리하는 과정은 총 3단계로 이루어져있으며, 이는 다음과 같습니다.
 
1. Dequeue 명령을 통해 Push Queue에 존재하는 queue에서 Client 정보를 추출하고 pending Map에 존재하는 Client 정보를 삭제합니다. 추가로 작업 진행을 관리하기 위해 processing Map에 Client 정보를 추가합니다. 이때 중요한 점은 Map에 데이터를 넣을 때 Key 값 즉 Client 정보만 Map에 입력하고 Value는 nil로 입력한다는 것입니다. 그 이유에 대해서는 추후 살펴보겠습니다.
 
2. Client 구조체에 위치한 PushChannel에 데이터를 삽입합니다. 이후 처리 과정은 Client와 연계되어있는 ADS에서 처리합니다. 이에 대해서는 추후 살펴보겠습니다.
 
3. Client에 데이터 전송이 완료되면, 완료 응답을 수신받습니다. 응답 수신이 완료되면, processing Map에서 해당 정보를 삭제합니다.
 
위와 같은 세 가지 단계를 거치면 데이터가 정상적으로 처리되는 것을 확인할 수 있습니다. 
 

 
그렇다면 현재 데이터를 Service A에 전송하고 있는 과정에서 동일한 Client에게 Push Request가 요청되면 어떻게 될까요? 이때는 processing Map을 통해 메시지를 처리합니다.
 
메시지를 Client에게 전송하면 processing Map에 Connection 정보를 입력하는 것을 이전 내용을 통해 확인했습니다. 따라서 debounce 이후 Queue에 입력하는 과정에서 해당 Map을 확인하면 현재 메시지 전송 상태를 확인할 수 있습니다.
 
이 경우에는 processing Map에 존재하는 Client 데이터에 Push Request를 추가합니다. 만약 그 이후에도 Client의 전송 완료 메시지를 받기 이전에 Push Request를 전달받으면 지속적으로 Push Request를 Merge 하여 하나의 메시지로 만듭니다.
 
이후 Client가 작업을 종료하고 전송 완료 응답을 전달하였을 때, processing Map을 살펴봅니다. 이때 Client Key에 해당하는 Value가 nil일 경우에는 메시지 전송 이후로 해당 Client에 요청되는 추가적인 메시지가 없으므로 작업을 종료합니다. 하지만 Value가 nil이 아닐 경우에는 중간에 입력된 메시지가 존재함을 의미합니다. 따라서 그 다음 doSendPushes 싸이클에서 메시지가 다시 처리될 수 있도록 pending 메시지에 해당 Client 정보와 Push Request를 다시 입력하고 queue에도 Client 정보를 삽입합니다.
 
지금까지 Push Queue에 대해 살펴봤습니다. 이를 통해 메시지를 전달하는 과정에서도 Network overhead를 줄이기 위해 여러가지 최적화 장치가 마련된 모습을 볼 수 있습니다.
 


5. Connection 관리

 
지금까지 Event가 발생했을 때, debounce 과정과 이후 Push Queue에서 데이터를 Client에게 전달하고 후속 작업을 처리하는 과정에 대해서 살펴봤습니다. 이번에는 XDS Server Client Connection 관리 주체와 어떻게 통신을 수행하는지 살펴보겠습니다.
 

 
envoy에서는 위와 같이 ADS Server에 대한 gRPC interface를 제공합니다. 따라서 istio에서는 해당 ADS Spec에 부합하도록 gRPC 코드가 구현되어있습니다.
 

 
istio에서는 사용자 요청을 처리하기 위해서 내부적으로 gRPC Server가 있습니다. pilot-agent는 gRPC에 정의된 interface 호출을 통해 pilot-discovery에 접속을 접속과 더불어 필요한 Resource 정보를 요청합니다.
 
사용자의 요청을 전달받으면 해당 Server는 별개의 go routine을 통해 receive와 processRequest 두 개의 작업을 수행합니다. 여기서 주목할 점은 receive와 processRequest는 별개로 동작하지만 두 routine간의 Request Channel로 연결되어있다는 점입니다. 지금부터는 두 과정에 대해서 살펴보면서 동작 원리를 관찰해보겠습니다.
 
먼저 살펴볼 것은 receive입니다.
 

 
receive의 역할은 연결된 Connection에서 데이터 처리가 필요할 때 해당 Request를 Requet Channel로 연결하기 위함입니다. 위 코드 내용을 통해 이를 살펴보겠습니다.
 
최초에 Client가 gRPC 서버에 접속을 요청하면, firstRequest는 true일 것입니다. 이후 for-loop 구문을 수행하는데, 위 코드를 살펴보면 for-loop 자체에는 별도의 조건이 없기 때문에 계속 반복 수행되는 것을 확인할 수 있습니다.

 
이후 for-loop 구문으로 진입했을 때 눈여겨볼 점은 최초 접속시 initConnection 메소드를 호출한다는 점입니다. 해당 메소드가 수행하는 기능은 사용자의 요청이 적합한지 권한 검사 이후에 XDS Server에 위치하는 AdsClients Slice에 해당 Connection 정보를 추가하는 작업을 진행합니다. 마찬가지로 defer 메소드로 Client가 접속을 종료할 때는 closeConnection 메소드를 호출하여 AdsClients로부터 해당 정보를 삭제하는 과정 또한 진행합니다.
 
따라서 이전에 Service Discovery 로직에서 Event 정보를 모든 Clients에게 전파할 때 해당 자료 구조를 참조했음을 확인할 수 있는데 receive 로직에서 AdsClients를 관리함을 알 수 있습니다.
 
사용자 최초 접속 하여 Connection 할당 정보가 완료된 이후, 수행하는 일은 사용자로부터 전달 받은 요청이 있으면 이를 Request Channel로 전달하는 중간 매개체 역할을 수행합니다.
 
정리하자면 별도의 go routine으로 동작하는 receive 메소드의 역할은 Connection 관리와 더불어 Connection에서 발생한 Request 정보를 Request Channel에 전파하는 것입니다.
 
이번에는 사용자의 요청을 처리하거나 Server의 전달 요청을 처리하는 로직 부분을 살펴보겠습니다.
 

 
해당 로직 또한 마찬가지로 for-loop을 지속적으로 수행하면서 요청 구분에 따라 처리 방법을 달리 수행합니다. 그 중 위 코드가 핵심 로직을 설명합니다.
 
먼저 살펴볼 것은 사용자의 요청에 대한 처리입니다. Receive go routine에서 사용자 요청을 최초 접수받고 이를 Request Channel에 전파한다고 이전에 설명했습니다. 이때 Channel로 전파된 데이터를 수신받고 processRequset 메소드에 전달하는 역할을 수행합니다. 
 

 
이때 processRequest 로직에서는 내부적으로 위와 같은 작업이 수행됩니다.
 
1. Client가 요청한 DiscoveryRequest는 ADS interface에 의해 정의된 Envoy 구조체로써 Discovery를 희망하는 요청 타입(ex Cluster)을 알 수 있습니다. 따라서 먼저 해당 정보를 파싱합니다. 그리고 요청하는 Resource를 지속적으로 추적하기 위해서 Connection 내부에 위치한 WatchedResource map에 해당 Type을 저장합니다.
 
2. istio 내부에서는 Envoy Spec과 달리 Abstract한 Model 구조를 사용합니다. 따라서 istio에서 통용되는 데이터 구조인 PushRequest로변환합니다.
 
3. istio는 내부에서 관리하는 데이터 타입 구조를 envoy가 이해할 수 있는 xDS로 변환하기 위해 내부적으로 Generator를 내장하고 있습니다. 위 그림과 같이 Generator는 Map 형태로 되어있으며, 각기 다른 Generator 중 Watched Resource Slice에 저장된 사용자가 원하는 타입에 부합하는 Generator를 찾아 xDS API 스펙에 맞게 변환하는 작업을 수행합니다.
 
4. 변환 작업이 완료되면, 다시 ADS interface에 의해서 정의된 DiscoveryResponse로 다시 변환합니다. 그리고 해당 정보를 Client에게 반환합니다.
 
정리하자면, 사용자가 원하는 요청 정보를 분석하여 istio가 관리하고 있는 서비스 정보를 Generator를 통해 envoy가 이해할 수 있는 형태로 변환한 다음 ADS interface가 요구하는 형태로 Wrapping 하여 전달하는 것이 사용자 요청 처리의 핵심 흐름이라고 볼 수 있습니다.
 

 
 
이번에는 Service 생성/수정/삭제로 인해 Server에서 Client에게 데이터를 전달해야되는 상황에 대해서 살펴보겠습니다.
 

 
이전 Push Queue 설명을 통해서 최종 통보는 Client Connection에 위치한 Push Channel로 데이터가 전달된다고 설명했습니다. 따라서 위 코드를 살펴보면 PushChannel에 데이터가 들어왔을 때는 빨간색 음영 부분이 감지가 될 것이며, 이를 토대로 pushConnection 메소드를 호출하여 후속 작업을 처리합니다.
 
이때 pushConnection 내부 처리 과정은 다음과 같습니다.
 

 
 
PushRequest는 이미 PushChannel로 부터 받았으므로 envoy의 요청과는 다르게 메시지를 변환할 필요가 없습니다. 이후 Connection에 등록된 Watched Resource 정보를 모두 가져와서 Generator에 매칭되는 정보로 변환하고 ADS interface에 의해서 정의된 DiscoveryResponse로 다시 변환합니다. 그리고 해당 정보를 Client에게 반환합니다.
 
해당 과정을 통해 Server의 변경 내용을 Client에게 전파할 수 있습니다.
 


5. 정리

 
지금까지 XDS Server에 존재하는 주요 컴포넌트에 대해서 살펴봤습니다. 이번에는 지금까지 배운 내용을 요약해서 전체적인 관점에서 흐름을 간략하게 살펴보겠습니다.
 

 
1. 사용자로부터 접속 요청을 전달받으면 Connection을 생성하고 XDS Server 내에 위치한 AdsClients에 저장하여 사용자를 관리합니다.
 
2. informer에 등록한 Handler를 통해서 관심 대상 Resource를 등록하며, Callback으로 ServiceDiscovery내에 있는 queue로 전달됩니다. 해당 queue에서는 kinds에 등록된 GVK(Group Version Kind)에 매칭된 handler를 실행합니다. 대부분의 handler는 XDSServer.ConfigUpdate 메소드를 수행하고 해당 정보는 PushChannel로 전달됩니다.
 
3. 내부에는 독립적으로 수행하는 debounce go routine이 존재하여 일정 시간동안 유입되는 메시지를 병합하는 작업을 수행하고 이후 메시지 전달을 위해 Push Queue에 데이터를 저장합니다. 
 
4. Push Queue를 처리하는 로직은 또 다른 별도 go routine을 동작하는 doSubPushes에서 수행되며, 여기서는 Client Connection 정보를 참조하여 해당 Connection의 Push Channel로 데이터를 다시 전달합니다.
 
5. Push Channel로 전달된 데이터는 내부 Generator를 통해 envoy가 이해할 수 있는 형태로 데이터를 가공한다음 Client로 전달합니다.
 


6. 마치며

 
이번 포스팅에서는 istio pilot-discovery에서 가장 중요한 XDS Server에 대해서 살펴봤습니다. 다음 포스팅에서는 인증서 관리와 SDS Server 구조에 대해서 살펴보겠습니다.
 

+ Recent posts