Envoy
개요
2016년 Lyft에서 개발하고 2017년 CNCF로 기증된 오픈소스 네트워크 프록시.
| The network should be transparent to applications. When network and application problems do occur it should be easy to determine the source of the problem.
어플리케이션의 네트워크를 투명하게, 문제가 발생했을 때 원인을 진단하는 것을 편하게 하는 것을 주목표로 개발됐다.[1]
C++을 이용해 개발되었고, 높은 부하를 견디며 좋은 성능을 가진 서버로서 기능하게 하는 것이 주된 목표이다.
여기에 프록시로서의 기능에 충실하기 위해 엔보이는 다양한 기능을 추가적으로 제공하고 있다.
현재 이스티오의 데이터 플레인으로서 톡톡히 역할을 수행하고 있다.
문서를 조금 보는데, 거의 백서 느낌으로 상세하게 돼있다..
디자인
엔보이를 처음 공부해보면, 생각보다 다양한 기능에 있다는 것에 놀라고, 무엇보다 생각 이상으로 설정 방법이 광대하고 복잡하다는 것에 놀란다.
엔보이를 조금 더 쉽게 이해하기 위해서는 먼저 엔보이가 무엇을 염두하여 디자인됐는지 아는 것이 도움이 된다.
내가 이해한 수준에서 엔보이는 다음의 요소들을 기반으로 디자인됐다.
- 투명한 네트워크
- 네트워크의 영역을 가능한 투명하게 드러내어 가능한 문제가 발생했을 때 이를 쉽게 진단할 수 있는 것을 목표로 한다.
- 그래서 네트워크에서 할 수 있는 다양한 설정들을 엔보이는 전부 소화해낼 수 있다.
- 또한 관련한 모든 설정이 엔보이의 설정 파일에 반영된다.
- 이것 때문에 엔보이 설정 파일은 기본 설정만 해도 몇 만줄 가까이 되는 기염을 토한다.
- 가벼우면서 빠른 프록시
- 엔보이는 C++로 개발된 것 이외에도 프로세스 내에서 가능한 락이 걸리지 않도록 설계됐다.
- 하나의 트래픽을 처리하며 호스트와의 추가 요청을 주고받는 과정에서 생기는 레이턴시, 혹은 여러 트래픽들이 공유하거나 연결돼야 할 때도 효율적으로 컴퓨팅 자원을 운용할 수 있도록 디자인됐다.
- 서비스 메시를 위한 서버
- [[#XDS api]]를 통해 엔보이는 런타임 중에 각종 설정을 동적으로 바꿀 수 있다.
- 근데 방식이 조금 특이한데, 엔보이를 설정할 수 있는 서버로부터 설정을 받아가는 방식이다.
- 이는 엔보이가 서비스 메시 환경에서 여러 서비스 앞단에서 결합하여 설정을 할 수 있도록 설계됐기 때문이다.
- 이러한 방식 덕분에 엔보이를 설정하는 중앙 서버를 마련해두면 여러 환경에 분산된 엔보이들을 편하게 관리할 수 있게 된다.
- 사실 공부해보니 이스티오는 사실 엔보이 메시라 부르는 게 더 맞지 않을까 하는 생각이..
이건 내가 엔보이를 공부하면서 막히거나 신기하게 느꼈던 부분들을 개념화시켜서 정리한 것이다.
다른 사람이 엔보이를 공부하면 이 범주화가 부족하다고 느껴질 수도 있으니 참고하자.
기능
기본적으로 프록시가 그러하듯이 엔보이는 L7 서버이며, 응용 계층의 정보를 이해하고 이에 대한 기능을 수행할 수 있다.
매우 다양한 기능을 수행할 수 있는데, 대략 보자면 다음과 같다.
- 어플리케이션 외부에서 동작
- 프록시가 가지는 특징이긴 한데, 아무튼 프록시로서 앱과 별개로 동작하면서 트래픽 메시지, 정보를 전부 투명하게 공개해준다.
- 이를 기반으로 어플리케이션에서 네트워크 관련 기능을 일체 분리하여 개발할 수 있도록 도와준다.
- L3, L4 레벨 기능
- 코어로만 봤을 때는 L4 영역에서도 동작하며, 그래서 TCP, UDP 레벨 패킷을 필터링하는 것도 가능하다.
- L7 레벨 기능
- 동적 설정
- 엔보이는 동적으로 설정을 할 수 있는 자체 api를 노출하고 있어 이를 기반으로 실행되고 있는 엔보이를 동적으로 설정하는 것이 가능하다.
- 이런 설정을 바탕으로 서비스 디스커버리도 할 수 있다.
- 헬스 체킹
- 엔보이 자체의 헬스체크 말고도, 통신을 해야 하는 대상 서비스의 헬스체킹도 수행한다.
- 관측 가능성
- 이게 엔보이가 투명하게 네트워크를 보여준다고 말할 때 핵심이 되는 기능으로, 네트워크 관련 지표와 로그를 수집하여 노출해준다.
- 설정을 하면 스팬을 달아 관측 가능성#Trace도 가능하여 어플리케이션 설정을 최소화하며 모니터링 경험을 향상시킬 수 있다.
기능들이 굉장히 많은데, 엔보이는 기본적으로 서비스 메시 환경을 기반으로 동작할 수 있도록 설계됐다.
어떤 네트워크 토폴로지 속에서도 동작할 수 있도록 가능한 많은 기능을 수행하면서 유연하게 동작할 수 있도록 개발됐다.
그래서 위의 설명만으로 엔보이를 이해하는 것은 매우 부족하고, 자세한 개념들과 설정법을 아는 것이 중요하다.
용어
엔보이에서 사용하는 용어들을 먼저 간단하게 짚어보자.
- Host
- 네트워크 통신을 하는 모든 주체, 엔티티.
- 여기에서 호스트는 논리적 개념의 대상이라 한 하드웨어에 여러 호스트가 있을 수 있다.
- Downstream
- 엔보이에 요청을 날리는 호스트.
- Upstream
- 엔보이가 요청을 보내는 호스트.
- Cluster
- 엔보이가 연결하는 업스트림 호스트들의 논리적 집합.
- 엔보이는 클러스터의 멤버를 서비스 디스커버리를 통해 찾는다.
구조
컴포넌트 구조
일단 사용자가 엔보이를 설정할 때를 기준으로, 논리적인 엔보이의 전체 구조는 다음과 같다.
간략하게 봤을 때, 엔보이의 동작 흐름은 이렇게 된다.
한 마디로 요약하면, 리스너에서 트래픽을 받아 여러 필터를 거치고 클러스터로 라우팅된다고 정리할 수 있겠다.
사용자가 실질적으로 설정하게 되는 주된 영역은 필터 체인쪽에 있으며, 이쪽에서 다양한 엔보이의 기능을 활용할 수 있다.
유의할 점으로, 흐름을 도식화하고자 리스너와 필터를 전부 분리해 그렸으나, 필터 설정들은 사실 전부 리스너 안 속에 있다.
- 리스너 - 다운스트림으로부터 트래픽을 받는 창구로, 어떤 프로토콜의 어떤 포트로 데이터를 받을지 설정
- 필터 - 들어온 트래픽에 대해 각종 작업을 수행하는 단위로, 각 필터는 전부 여러 개의 체인으로 묶여 있다.
- 크게 보자면 필터는 세 가지 정도로 분류할 수 있다.
- 리스너 필터 체인
- 들어온 데이터의 메타데이터나 전처리를 수행하고, 네트워크 필터로 넘긴다.
- 네트워크 필터 체인
- L4 층에서 데이터를 처리, 필터링, 조작한다.
- 필터 체인 매치 기준을 걸어 어떤 경우에만 어떤 필터를 적용할지도 지정할 수 있다.
- 네트워크 필터 중,HTTP Connection Manager(HCM)라는 필터가 있다.
- HTTP 필터 체인
- HCM 필터 안에 묶인 체인으로, HTTP 관련 무수한 세부 필터를 체인으로 품을 수 있다.
- 매우 다양한 설정을 지원하며 복잡하기에, 하나의 분류로 뺄 수 있을 정도이다.
- 가장 마지막에는 라우터 필터가 들어간다.
- 여기에 추가적으로 클러스터 필터 체인이라 하여 클러스터 관련 필터를 설정할 수도 있다.
- 라우터 필터
- 여러 필터 중 얘만 따로 뺀 이유는 이 녀석이 실제로 라우팅을 하는 필터이기 때문이다.
- 이 필터가 실제 업스트림 클러스터로 트래픽을 보내는 작업을 수행하기에 모든 필터 가장 마지막에 위치한다.
- 클러스터 - 업스트림 서버들을 논리적 집합으로 묶어서 관리
- 엔드포인트 - 개별 업스트림 서버를 나타내는 단위
프로세스 구조
엔보이가 실제 실행되는 프로세스 차원에서 구조도를 보면 이런 식으로 생겼다.
프로세스 상으로 보자면, 엔보이는 싱글 프로세스 멀티 쓰레딩 구조를 가지고 있다.
nginx처럼 마스터-워커 개념이 있는데, 각 워커 스레드가 이벤트 기반으로 이벤트 루프(libevent)를 이용해 작업을 처리한다.
그래서 프라이머리 쓰레드(메인)가 워커 쓰레드를 여럿 만들고 초기 트래픽이 왔을 때 워커 스레드를 초기화하는 등의 작업을 수행한다.
동적 설정을 가할 때 이것을 처리하는 것도 바로 메인 스레드이다.
- 프라이머리 쓰레드
- 리스너 매니저와 클러스터 매니저로 엔보이 설정을 관리
- 들어온 트래픽을 조건에 부합한 리스너 인스턴스를 만들어(소켓까지 연결)하여 워커 스레드에 할당
- 각 워커 쓰레드
- 프라이머리 쓰레드로부터 리스너 인스턴스를 받고 자신의 이벤트루프에 등록하여 처리
재밌는 특징 중 하나는 워커 스레드가 락을 걸지 않는다는 것인데, 이로 인해 엔보이는 자원 경합이나 데이터 정합성을 깨지 않고 효율적으로 작업을 처리한다.
이를 위해 매우 정교한 테크닉이 내장돼있는데, 자세한 내용은 여기를 보면 굉장히 도움될 것이다.[2]
기본적으로 각 워커 쓰레드는 독립적으로 트래픽을 작업하여 포워딩하는 작업을 수행한다.
보통의 트래픽은 stateless해 워커 쓰레드 간 독립성이 크게 문제되지 않으며, 커널 단의 컨트랙만으로 세션 유지등의 작업은 수행되도록 개발됐다.
다만 grpc와 같이 긴 연결이 필요한 요청을 처리하기 위해 커넥션 밸런싱을 통해 각 스레드로 가는 트래픽을 제어하기도 한다.
단일 요청 흐름 예시
그렇다면 조금 더 예시를 들어서 하나의 트래픽이 어떤 식으로 흘러가는지 살펴보자.[3]
다음의 상황을 가정하겠다.
엔보이에서 이걸 설정할 때, 아래와 같이 세팅할 수 있다.
# 엔보이가 처음 가동될 때 설정되는 필드.
static_resources:
# 리스터 지정 필드
listeners:
- name: listener_https
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 443
# tls 통신을 위해 sni를 추출하는 필터
listener_filters:
- name: "envoy.filters.listener.tls_inspector"
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
# 특정 도메인에 적용될 필터 체인 정의
filter_chains:
- filter_chain_match:
# tls 필터를 통해 이 이름이 추출된다.
server_names: ["acme.com"]
# 다운스트림과의 tls 통신을 위한 설정
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificates:
- certificate_chain: {filename: "certs/servercert.pem"}
private_key: {filename: "certs/serverkey.pem"}
filters:
# HTTP connection manager 필터
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
use_remote_address: true
http2_protocol_options:
max_concurrent_streams: 100
# 파일시스템에 로깅
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: "/var/log/envoy/access.log"
# 라우팅할 클러스터 지정
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["acme.com"]
routes:
- match:
path: "/foo"
route:
cluster: some_service
# HCM(http connection manager)일 때 존재하는 필드.
# 이 필터가 라우팅을 수행하기 위한 필터라는 명시하는 원소를 넣었다.
# 여기에 커스텀 필터를 더 넣어서 기능을 추가할 수도 있다.
http_filters:
# - name: some.customer.filter
- name: envoy.filters.http.router
# 이 http 필터는 라우팅을 담당하며, 이걸 아예 라우터 필터라고 부른다.
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
# 클러스터 지정 필드
clusters:
# 트래픽을 받게 될 클러스터
- name: some_service
# 업스트림과의 tls 통신을 위한 설정
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
# 로드밸런서 세팅으로, 클러스터 내 하위 엔드포인트들을 지정한다.
load_assignment:
cluster_name: some_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 10.1.2.10
port_value: 10002
- endpoint:
address:
socket_address:
address: 10.1.2.11
port_value: 10002
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options:
max_concurrent_streams: 100
# stats 통계를 내기 위한 클러스터
- name: some_statsd_sink
# 위 클러스터에 대한 설정이 담긴 필드
stats_sinks:
- name: envoy.stat_sinks.statsd
typed_config:
"@type": type.googleapis.com/envoy.config.metrics.v3.StatsdSink
tcp_cluster_name: some_statsd_sink
딱 봐도 굉장히 복잡하다..
복잡한 만큼 다양한 기능을 넣고 커스텀하는 것이 가능하다.
이제 이 상황에서 요청 트래픽이 어떻게 흐름을 탈지 정리해보자.
- 리스너 필드의 리스너 설정이 작동한다.
- 다운스트림으로부터 TCP 커넥션이 리스너로부터 수락된다.
- 리스너 필터가 실행되어 TLS 요청에 대한 SNI값을 꺼내온다.
- 리스너 필터가 실행된 이후 본격적으로 네트워크 필터 체인이 발동하는데, 위의 필터에서 걸린 SNI 값이 함께 전달된다.
- 이 필터 체인에서 TCP, 혹은 HTTP 레벨의 패킷에 대한 조작을 수행한다.
- 해당 필터 체인은 SNI의 값이 "acme.com"일 때 매칭되며, 이후 TCP 소켓 필드에 의해 TLS의 암호문이 복호화된다.
- 필터 체인 내부에 HTTP Connection Manager 필터(http 처리를 위한 필터로, 하위로 추가 필터를 가진다)가 발동한다.
- 해당 통신이 HTTP/2를 통해 온다는 것을 인식하고, 알아서 멀티플렉싱된 스트림을 모아서 인식하는 것이 가능하다.
- 참고로 어차피 한 요청은 그래도 한 스트림에 담기니 딱히 상관은 없다.
- 각 요청에 대해 설정된 것들이 전부 발동한다.
- 액세스 로깅은 요청이 전부 이뤄진 후에 파일시스템에 남는다(정확한 쓰기 시작 시점은 아직 잘 모르겠다).
- 엔보이 입장에서 다운스트림에서 호출하는 호스트는 가상 호스트로, HTTP의 Host 헤더를 기반으로 매칭한다.
/foo
경로로 시작하는 요청이라면 업스트림 클러스터로some_service
를 지정한다.
- http_filter 필드에 나열된 필터들이 순서대로 동작하며, 라우터 필터가 있으므로 트래픽을 라우팅한다.
- HTTP 커넥션 풀이 이 리스너를 실행하는 워커 쓰레드에 존재하며, 라우팅 필터는 연결 상태를 유지하며 업스트림으로 트래픽을 보낼 수 있다.
- 해당 통신이 HTTP/2를 통해 온다는 것을 인식하고, 알아서 멀티플렉싱된 스트림을 모아서 인식하는 것이 가능하다.
- 클러스터 필드에 클러스터 설정이 작동한다.
- 클러스터 매니저에서 커넥션 풀 관리가 이뤄진다.
- 라우팅될 업스트림 엔드포인트의 커넥션 풀을 확인하고 새로운 스트림이 연결될 수 있는지 결정한다.
- 새로운 스트림이 생길 때마다 업스트림 http 필터가 동작하는데, 위에서는 http2의 동시 스트림 최대 개수 100 설정이 적용된다.
- 기본으로 Codec(압축)필터도 동작한다고 한다.
- 각 업스트림 엔드포인트에 대해 HTTP/2 기반으로 멀티플렉싱과 프레임 분리가 적용된다.
- transport_socket 설정으로 엔드포인트와의 연결에서 TLS 암호화 작업이 수행된다.
- 클러스터 매니저에서 커넥션 풀 관리가 이뤄진다.
- 결과적으로 TLS로 암호화된 HTTP/2 요청은 엔보이 내부에서 복호화되어 각종 필터를 거친 후 업스트림으로 다시 암호화해 전달되며, 응답 역시 똑같이 다운스트림으로 전달된다.
- half-close(http2에서 한쪽에서 통신을 종료하는 방식)가 활성화돼있다면 업스와 다운스 측의 응답과 요청이 명확하게 끝나는 것이 보장돼야 200으로 해당 트래픽을 기록한다.
- 활성화 안 됐다면 응답에서 완료 시그널이 오면 스트림이 종료된다고 한다.
- 요청이 완료된 이후 통계 정보가 업데이트되고, 로깅 작업이 이뤄진다.
내가 제대로 정리한 것이 맞는지 확신이 없는데, 조금 더 개념이 익숙해지면 다시금 정리해야겠다.
전체 흐름을 상세 도식으로 다시금 이해해보자.
리스너의 TCP 수락
일단 리스너 매니저는 리스너 설정을 관리하며, 트래픽이 들어올 때 해당하는 IP, 포트, 연결 프로토콜을 기준으로 리스너 인스턴스를 초기화한다.
각 리스너 인스턴스는 아래의 상태를 가질 수 있다.
- Warning - 리스너가 의존성 있는 설정을 기다리거나 적용하고 있어 아직 TCP 연결을 수행할 수 없음
- Active - 리스너가 활성화됐음
- Draining - 리스너를 제거하고 있으며(동적 설정 등의 이유) 현재 이어진 TCP 연결만 수행하고 추가로 받지 않음
이렇게 초기화된 인스턴스는 각 워커 스레드에 할당되며, 이때부터 본격적으로 설정을 통한 엔보이의 트래픽 작업이 수행된다.
한번 TCP 연결이 수락되면 이후 패킷들에 대해서, 커널 단에서 어떤 워커 스레드가 해당 연결을 받고 있는지, 어떤 리스너 인스턴스에 매핑되는지 추적하여 패킷을 보낸다.
리스너 필터 체인 생성
워커 스레드의 리스너 인스턴스는 리스너 필터 체인을 만든다.
이 필터 체인은 각 필터의 필터 팩토리(그냥 팩토리 패턴 말하는 거 같은데, 지피티왈 이 팩토리 자체는 메인 스레드가 가진다는 듯.)로부터 만들어지며, 이 팩토리는 필터의 설정 정보를 기반으로 각 연결에 대한 필터 인스턴스를 만들어 반환해준다.
이 예시에서는 TLS inspector 필터가 만들어지고, 이 필터가 초기 TLS 핸드쉐이킹에서 SNI값을 꺼낸다.
이제 SNI 값은 이후 필터 체인에서 사용될 수 있게 된다.
리스너 필터 체인은 이후에 네트워크 필터 체인(본격적으로 TCP 설정, HTTP 설정 진행)으로 연결된다.
virtual FilterStatus onAccept(ListenerFilterCallbacks& cb) PURE;
모든 필터들은 인터페이스로 위의 메서드를 구현한다고 한다.
이 메서드는 필터가 작업을 수행하는 중에 호출될 수 있는 콜백 메서드로, 이걸 기반으로 필터 작업을 도중에 멈추거나 재개하는 것이 가능하다.
TLS 복호화 후 네트워크 체인 필터 수행
중간에 TLS tranport socket 층이 생성된 것이 보인다.
이것 자체는 필터는 아닌데, TCP 연결 로직 간에 SslSocket::doRead()
함수로 실행되는 메서드라고 한다.
이 친구는 TLS 핸드쉐이킹을 수행하고, 복호화된 데이터를 이후 필터에 넘기는 역할을 한다.
워커 스레드는 이벤트 기반으로 동작하기 때문에, tls 통신 과정으로 인해 추가 통신이 오고가며 이 필터 파이프라인이 수행되지 못하는 순간에도 다른 커넥션을 처리할 수 있다.
리스너 필터 체인이 그러했듯이, 네트워크 필터 체인도 각각 팩토리로부터 필터 인스턴스가 만들어진다.
네트워크 필터는 커넥션으로부터 읽기, 쓰기 작업이 일어날 때만 호출되는 것도 가능하다.
이 예시에서 마지막 필터는 Http Connection Manager, 줄여서 HCM이다.
이 필터는 먼저 HTTP/2 코덱(http/2에서 헤더를 압축)을 복호화한다.
HTTP/2에서는 하나의 TCP 연결에 여러 스트림이 있을 수 있고, 이 스트림이 각각 하나의 요청과 응답을 나타내기 때문에 이를 분류하는 작업을 먼저 한다.
그리고 HTTP 필터 체인을 만들어 작업을 수행한다.
이때 만들어지는 필터는 다운스트림 HTTP 필터라고 부르는데, HTTP 관련 설정만 진행한다.
가령 위의 네트워크 체인 필터는 데이터 읽기 쓰기 작업에 호출될 수 있었는데, 여기에서는 요청이냐 응답이냐에 따라 호출될 수 있다.
각 독립적인 스트림에 대해 각각 체인이 만들어지고 작업이 진행된다.
HTTP 필터 중 마지막인 라우터 필터가 동작할 때 내부에 decodeHeaders()
가 발동되면 라우팅 결정이 완료되며 클러스터가 선택된다.
HTTP 필터 체인이 초기에 경로 설정에 대한 정보가 있어 이를 기반으로 HCM은 최종 클러스터를 선택하게 된다.
근데 체인을 거치며 헤더 변경이나 각종 작업들이 수행될 수 있기에, HCM은 초기 필터 진행 당시 정보 이외에도 다시 경로 평가를 진행하기도 한다.
아무튼 이때 라우팅은 무조건 업스트림 클러스터 이름으로 설정된다.
여기에서 라우터 필터가 하는 일이 굉장히 많다.
- UpstreamReqeust 인스턴스를 만들고 여태 필터 체인을 거치며 나온 최종 전달물을 커넥션 풀에 담는다.
- 이를 위해 클러스터 매니저로부터 해당 클러스터의 HTTP 커넥션 풀에 대한 정보를 요청한다.
- 업스트림으로 전달된 요청이 커넥션 풀로부터 제거되기까지, 요청의 라이프사이클을 총괄한다.
- 가령 요청 재시작, 타임아웃 등의 작업이 여기에서 이뤄지게 된다.
- 업스트림 필터 체인을 만들고 실행한다.
- 업스트림 필터 체인은 클러스터쪽 설정에 위치한다.
- 라우터 필터에 헤더가 오는 순간부터 업스트림 HTTP 필터가 동작한다고 한다.
라우팅 결정이 완료된 이후 업스트림 필터 체인이 동작하는 것이기에, 이쪽 필터는 라우팅에 변경을 가하는 조작은 불가능하다.
여기에서는 재시도 전략, 혹은 업스트림과의 연결 정보를 꺼내오는 정도의 필터를 넣을 수 있다.
업스트림으로 트래픽이 전달되는 과정
라우터 필터가 커넥션 풀에 정보를 요청할 때를 조금 더 상세하게 들어가보면, 먼저 건강한 상태이며 조건이 맞는 엔드포인트에 대해 로드밸런싱이 수행된다.
이 예시에서는 로드밸런싱 알고리즘을 명시하지 않았는데, 기본값은 라운드 로빈 방식이다.
업스트림과도 HTTP/2 통신을 하므로 들어온 요청에 대해 코덱 디코딩을 했듯이 여기에서는 다시금 코덱 인코딩이 수행된다.
마지막으로 TLS 암호화가 이뤄진다.
요청 작업 수행
이제 위의 과정을 계속 수행하면서 업스트림으로 트래픽을 전달한다.
돌아오는 응답은 여기에 필터 체인 부분에서 코덱 디코딩이나 TLS 암복호화가 역순으로 수행된다고 보면 된다.
HTTP/2에서는 통신을 하는 양측이 연결을 종료하는 게 가능한데, independent half-close 기능이 활성화됐다면 엔보이 내부의 스트림은 양쪽 연결이 완벽하게 종료됐을 때만 삭제된다.
다양한 방식으로 요청은 도중에 종료될 수 있다.
- 리퀘스트 타임아웃
- 업스트림 엔드포인트 스트림이 리셋됨
- HTTP 필터 스트림이 리셋됨
- 서킷 브레이커
- 업스트림 대상이 사라짐
- 건강한 엔드포인트가 없음
- 등등..
아무튼 요청이 완전히 종료된 이후에는 후속 작업이 진행된다.
일단 요청에 대한 통계 정보가 업데이트된다.
몇몇 통계는 요청 수행 중에 업데이트되나, 바로 실제 정보로 반영되지는 않고 주기적으로 메인 쓰레드에서 배치 처리로 동기화를 수행한다.
이후 액세스 로그가 파일시스템에 쓰여진다.
또한 트레이스 스팬이 종료된다.
XDS api
엔보이는 동적으로 서버 설정할 수 있도록 api를 노출하고 있는데, 이들을 통틀어 XDS라고 부른다.
왜 이리 부르는지는 종류를 보면 바로 알 수 있다.
- LDS - Listener Discovery Service
- 리스너를 설정하는 api.
- RDS - Route Discovery Service(이놈 때문에 처음에는 엔보이에 Route라는 하나의 컴포넌트가 있다고 오해했다.)
- HTTP 관련 라우팅 설정을 하는데 쓰이는 api
- VHDS - Virtual Host Discovery Service
- 가상 호스트 기반으로 라우팅 설정을 하는 api
- SRDS - Scoped Route Discovery Service
- 라우팅 테이블을 여러 조각으로 분리하는 api
- EDS - Endpoint Discovery Service
- 업스트림 클러스터의 멤버, 즉 엔드포인트를 찾는데 쓰이는 api.
- CDS - Cluster Discovery Service
- 라우팅 경로를 기반으로 업스트림 클러스터를 찾는데 쓰이는 api
- SDS - Secret Discovery Service
- 암호화 관련 api
- RTDS - RunTime Discovery Service
- 파일시스템 등의 런타임 관련 설정을 하는 api
- ECDS - Extension Config Discovery Service
- 리스너와 별개로 확장된 설정을 할 때 쓰이는 api.
- ADS - Aggregated Discovery Service
- 위의 여러 api들을 통합적으로 묶어서 하나의 트랜잭션마냥 설정하는 api
이래서 xDS라고 부르는 것인데, 그럼 왜 Discovery Service인가?
위 api들은 기본적으로 흔히 api 요청을 날리는 방식으로 동작하는 것이 아니기 때문이다.
기본적으로 엔보이에서는 gRPC를 이용해 api를 정의하는데, 대략적인 동작 흐름은 다음과 같다.
- 엔보이 서버를 기동할 때 동적 설정에 대한 필드를 정의한다.
- 어떤 식으로 통신하여 정보를 받을지에 대한 설정이다.
- 해당 엔보이 서버에 대해 클러스터 이름과 node id를 지정해야 한다.
- 설정을 하는 서버 프로세스를 띄운다.
- 이 서버는 엔보이 서버와 통신할 수 있는 주소를 작성해야 한다.
- 해당 서버가 띄워지면 서버 간 gRPC 스트림이 수립되며, 엔보이는 띄워진 서버로부터 설정값들을 받아가서 자신에게 반영한다..
- 설정하는 서버에서 엔보이를 식별하고 구분하기 위해 엔보이 설정 시 id를 지정해야 하는 것이다.
이 설정들은 기본적으로 단일 요청으로 보내서 설정하는데, 각 api 설정 간 긴밀한 결합도를 가진 경우도 존재한다.
그래서 이럴 때 발생할 수 있는 문제를 막고자 ADS라고 트랜잭션형 api도 개발된 것이다.
또 하나, 이 설정은 비동기로 이뤄져서 설정의 순서가 반드시 보장되지는 않는다.
엔보이는 결국에는 적용된다는 관점, 즉 궁극적 일관성을 추구하는 식으로 설계됐기 때문이다.
엔보이는 애초에 서비스 메시 환경에서 운용할 것을 기본으로 만들어졌기 때문에, 설정할 때 각 엔보이에 통신을 하는 식이 아니라 엔보이가 설정 서버로부터 설정을 긁어오는 식으로 api가 개발됐다.
그래서 X에 대한 설정을 네트워크에서 탐색하여 가져와 적용하겠다, 해서 X Discovery Service인 것이다.
참고로 이 api를 적용하는 방법은 크게 3가지가 있다.[4]
가장 권장되는 방식은 당연히 gRPC를 이용하는 것으로, 각 api에 대한 proto 파일이 문서에 정리돼있다.
그래서 이스티오나, 엔보이에서 개발된 Go ControlPlane[5]과 같은 설정용 grpc api를 이용해주면 된다.
일반 rest endpoint에 대한 래퍼도 제공하긴 한다고 한다.
파일시스템의 변경을 기반으로 설정 파일을 적용하는 방법도 존재한다.
각 설정 방법들은 일단 초기 설정 파일에 명시적으로 설정돼있어야 적용되니 참고하자!
엔보이는 이번에 만들어진 v3 이후로 더 이상 메이저 버전을 업데이트하지 않겠다고 한다..
관련 문서
EXPLAIN - 파생 문서
이름3 | related | 생성 일자 |
---|---|---|
이스티오 가상머신 통합 | 이스티오 확장성 | 2025-06-01 13:32 |
이스티오에서 엔보이 기능 확장하기 | 이스티오 스케일링 | 2025-06-01 14:06 |
이스티오의 데이터 플레인 트래픽 세팅 원리 | E-이스티오 가상머신 통합 | 2025-05-27 21:55 |
기타 문서
Z0-연관 knowledge, Z1-트러블슈팅 Z2-디자인,설계, Z3-임시, Z5-프로젝트,아카이브, Z8,9-미분류,미완이름6 | 코드 | 타입 | 생성 일자 |
---|---|---|---|
T-엔보이 실습 with solo.io | Z3 | topic/temp | 2025-04-14 23:15 |
8W - 엔보이와 iptables 뜯어먹기 | Z8 | published | 2025-06-01 12:14 |
엔보이에 와즘 플러그인 적용해보기 | Z8 | topic | 2025-06-09 02:29 |
6W - 이스티오 설정 트러블슈팅 | Z8 | published | 2025-05-18 01:31 |
7W - 엔보이 필터를 통한 기능 확장 | Z8 | published | 2025-06-09 02:30 |
8W - 가상머신 통합하기 | Z8 | published | 2025-06-01 12:11 |
참고
그나저나 엔보이 api 직접 만지려고 시도하다보니까, 새삼 이스티오가 선녀 같다..
쿠버네티스를 공부한 이래로 어떤 툴을 공부하는데 3일 이상 쓴 건 엔보이가 처음이다..