본문 바로가기
CNCF Projects/Envoy

Envoy architecture - Listener + Listener/Network Filter

by cscscs 2023. 11. 22.

Envoy architecture - Listener + Listener/Network Filter

Introduction

Thread model에 이어서 Listener에 대해 알아보도록 하겠습니다. 이번 글에서도 공식문서를 번역하고, 실 구현체를 이해해보도록 하겠습니다.

 

Listener

Envoy 설정에서 single process에서 임의의 수의 리스너를 구성하는 것을 지원합니다. 일반적으로 설정된 listener의 수와 상관없이 single Envoy를 머신별로 실행하는 것을 추천합니다. 이는 더 쉬운 연산과 통계의 단일 원천이 가능하게 합니다.

Envoy는 TCP와 UDP 리스너 둘 다 지원합니다.

 


 

TCP

각 리스너는 독립적으로 filter_chain과 함께 구성됩니다. 각 filter chacin은 filter_chain_match 범주에 기반해 선택됩니다.

각 filter_chain은 하나 혹은 여러 네트워크 수준 (L3/L4) 필터로 구성됩니다.

listener가 새로운 connection을 받으면, 적절한 filter_chain은 선택되고, 설정된  connection-local filter stack이 인스턴트화됩니다. 그리고 연속된 이벤트들이 처리되기 시작합니다.

generic listener architecture은 Envoy가 사용하는 거의 대부분의 proxy 작업들을 수행하곤 합니다. (rate limiting, TLS client authN, HTTP connection management, MongoDB sniffing, raw TCP proxy 등)

리스너는 선택적으로 몇 listener filter로 구성됩니다. 이 필터들은 보통 커넥션이 이후 필터나 클러스터로부터 어떻게 처리됐는지 전파하기 위해서 네트워크 레벨 필터 전에 처리되고, connection 메타데이터를 생성할 기회를 얻습니다.

리스너는 또한 listener discovery service (LDS)로부터 패치될 수 있습니다.

 


 

UDP

Envoy는 UDP listener와 UDP listener filter들을 지원합니다.

UDP listener filter들은 각 워커들에 대해 한번 인스턴스화되고, 해당 워커에 전역 상태입니다.

각 리스너 필터는 워커가 포트로 부터 listen 함으로써 받은 각 UDP 데이터그램을 처리합니다.

실제로 UDP 리스너는 SO_REUSEPORT 커널 옵션과 함께 설정됩니다. SO_REUSEPORT 은 커널이 각 UDP 4- 튜플을 일관되게 동일한 worker에 해시되도록 합니다. 이러한 처리는 원한다면 UDP listener 필터가 "session" 지향적이 가능하도록 합니다.  이 기능에 대한 built-in example은 UDP proxy listener filter입니다.

 


 

Listener filter

Envoy의 listener filter은 아마 connection metadata를 생성하기 위해 사용됩니다.

listener filter의 주된 목적은 Envoy core 함수들에 변화 없이 이후 시스템 통합 함수들을 더 쉽게 만들고, 여러 해당 feature들 사이 상호작용을 명시적으로 하기 위함입니다.

listener filter을 위한 API는 궁극적으로 해당 필터들이 새롭게 채택된 소켓상에서 동작하기 때문에 상대적으로 단순합니다.

chain에서의 필터들은 멈출 수 있고, 이후 필터들과 연속적으로 상호작용을 이어갈 수 있습니다. 이는 rate limiting service등과 같은 더 복잡한 시나리오가 커버되게 해 줍니다.

엔보이는 architecture overview뿐 아니라 configuration reference에 있는 몇 listener filter들도 포함합니다.

 


 

Filter chain

Filter chain match

네트워크 필터는 Filter Chain의 정렬된 리스트로 체인 됩니다.

각 리스너는 여러 FilterChain을 갖고, 선택적인 default_filter_chain을 갖습니다.

요청을 받으면, 가장 구체적인 match criteria를 포함한 FilterChain이 선택됩니다.

매칭되는 FilterChain이 없으면, 해당 요청을 서빙하는데 default filter chain이 설정되어 있으면 사용됩니다. 만약 default filter chain이 설정되어 있지 않다면, connection은 종료됩니다.

Filter chain only update

Filter chain은 독립적으로 업데이트될 수 있습니다.

리스너가 config를 업데이트하면, 만약 listener manager이 filter chain만 업데이트하려 하는 상황에서, 리스너는 해당 업데이트를 filter chain을 조작(add/update/remove)함으로써 수행할 수 있습니다.

이러한 filter chain들을 파괴함으로써 소유된 connection들은 이후 다룰 Draining을 통해 드레인 됩니다.

만약 새로운 filter chain과 이전의 filter chain이 동일한 protobuf message인 경우, 해당 필터 체인 런타임 정보가 유지됩니다. 유지된 filter chain들로부터 소유된 connection들은 열린 상태로 유지됩니다.

모든 listener config update들이 filter chain update로부터 실행될 수 있지는 않습니다. 예를 들어, 리스너의 메타데이터가 새로운 listener config에서 업데이트된 경우, 새로운 메타데이터는 새로운 필터 체인으로부터 수용되어야 합니다. 따라서 해당 경우, 전체 리스너는 드레인 되고 업데이트됩니다.

 


 

Network (L3/L4) filter

네트워크 레벨 (L3/L4) 필터는 Envoy connection handling의 코어 로직을 형성합니다. filter API는 필터의 다른 집합들이 섞이고, 매치되고, 주어진 리스너로 붙을 수 있도록 합니다.

세 종류의 네트워크 필터가 존재합니다.

  • Read
    - Read filter들은 Envoy가 downstraem connection으로부터 데이터를 받았을 때 호출됩니다.
  • Write
    - Write filter은 Envoy가 downstream connection으로 데이터를 쓰려고 할 때 호출됩니다.
  • Read/Write
    - Read/Write filter은 위 두 경우 둘 다 호출됩니다.

본질적으로 필터들은 raw byte들과 작은 수의 connection event위에서 연산되기 때문에, 네트워크 레벨 필터 API는 상대적으로 단순합니다.  (예를들어 TLS handshake 완료, connection이 locally or remotely 끊김 등)

chain에서의 필터는 멈추거나 이어서 이후 필터로 반복을 계속할 수 있습니다. 이는 rate limiting service 등의 복잡한 시나리오를 커버할 수 있도록 합니다. 

네트워크 레벨 필터는 상태 (static, dynamic)를 공유할 수 있습니다. 그들 사이에서, 단일 downstraem connection하의 context에서. filter 간 data sharing 문서가 있습니다.

TCP proxy filter

TCP proxy filter은 downstream client와 upstream cluster 간에 기본적인 1:1 네트워크 연결 프록시를 수행합니다. 

이는 stunnel 대체로써 단독으로 사용될 수 있으며, MongoDB 필터나 rate limiting 필터와 같은 다름 필터들과 함께 사용될 수 있습니다.

TCP 프록시 필터는 각 upstream cluster의 글로벌 리소스 매니저에 의해 설정된 connection limits를 준수합니다. TCP 프록시 필터는 해당 클러스터의 최대 연결 수를 초과하지 않고 연결을 생성할 수 있는지 여부를 upstream cluster의 resource manager에 확인합니다. 만약 생성할 수 없다면, TCP proxy는 연결을 시도하지 않습니다.

UDP proxy filter

Envoy는 UDP proxy listner filter를 통한 UDP proxy를 제공합니다.

DNS filter

Envoy는 UDP listener DNS filter를 configuring 함으로써 DNS 요청에 대응하는 것을 지원합니다.

DNS filter는 `A`와 `AAAA` 레코드를 위한 forward query들에 대응하는 것을 지원합니다.

해결 방법은 정적으로 설정된 resource, cluster, 외부 DNS server로 부터 찾는 것입니다.

filter은 DNS 응답을 최대 512 바이트로 반환합니다. 만약 도메인이 여러 주소나 여러 엔드포인트의 클러스터로 구성되어 있다면, 엔보이는 최대 앞에서 언급한 크기 limit까지 각 discover 된 주소들을 반환합니다. 

Connection limiting filter

Envoy는 Connection limit filter과 Runtime listener connection limit를 통한 runtime connection limiting을 통해 L4 레이어의 local (non-distributed) connection limiting을 지원합니다.

 


How is it Listener implemented?

Socket Handling

Envoy는 socket들을 만들고, socket들을 worker에 할당하는데 다음과 같은 절차를 따릅니다.

  1. 리스너가 생성되었을 때, 소캣은 메인스레드의 모든 worker에 사전에 생성됩니다 이는 대부분의 에러들이 listener 생성 과정에서 잡히게 해줍니다. (ex. 잘못된 socket 옵션, bind가 불가능한 상황 등)
    • reuse_port를 사용중이라면, 모든 워커들에게 유일한 socket이 생성됩니다.
    • reuse_port를 사용중이지 않다면, 유일한 socket은 worker 0에 생성됩니다. 그리고 socket은 모든 다른 worker들에게 복제됩니다.
  2. 주소와 소켓이 공유되어야 하는 리스너 업데이트의 경우, 'reuse_port'의  on/off 여부와 관계없이 모든 소켓들은 복제됩니다. 이는 설정이 변경되었을 때, drain되고있는 리스너와  같은 주소를 수신하는 명명된 리스너는 소켓을 공유하는 경우입니다. 'reuse_port' 사용 여부와 관계없이 모든 소켓을 복제하는 것은 로직을 동일하게 유지하고, 'reuse_port'를 사용할 때 어떤 TCP 커넥션도 drain process동안 drop되지 않게 합니다. 여기서 drain process는 큐에 할당하고, 이전 리스너의 소켓 수신을 닫는 사이 경쟁 상태입니다.
  3. hot restart의 경우, 모든 소켓들은 worker index로 요청됩니다. 이전 process가 매치되었다면, 소켓은 복제되고, 새로운 프로세스로 보내집니다. 따라서 hot restart와 리스너 업데이트는 효과적으로 같은 update process를 공유합니다. 이는 reuse_port를 사용할 때도 어떤 connection도 drop되지 않게 해줍니다.
  4. 모든 리스너가 전용 소켓을 가지고 있는 결과로서 (복제여부에 상관없이), 리스너는 제거될 때 다른 리스너를 신경쓰지 않고 자신의 소켓을 close할 수 있습니다.

https://github.com/envoyproxy/envoy/blob/v1.28.0/source/extensions/listener_managers/listener_manager/listener_impl.cc#L75-L120 에서 확인할 수 있습니다. (*좀 더 자세한 구현체는 이후 글 리팩토링시 추가로 설명하겠습니다.)

'CNCF Projects > Envoy' 카테고리의 다른 글

Envoy architecture - Introduction + Threading model  (0) 2023.11.20
Envoy proxy란?  (0) 2023.11.16