HTTP 커넥션 매니저 — HTTP 요청을 다루는 핵심 필터

HTTP 커넥션 매니저 — HTTP 요청을 다루는 핵심 필터

HTTP는 현대 서비스 지향 아키텍처의 핵심 구성 요소라서, Envoy는 HTTP 전용 기능을 아주 많이 구현해두고 있어요. 그 중심에 있는 게 네트워크 레벨 필터인 HTTP 커넥션 매니저(HTTP connection manager, http_conn_man)예요. 이 필터가 원시 바이트를 HTTP 레벨 메시지와 이벤트(헤더 수신, 바디 데이터 수신, 트레일러 수신 등)로 변환해요. 또 접근 로깅, 요청 ID 생성과 트레이싱, 요청/응답 헤더 조작, 라우트 테이블 관리, 통계 같은 모든 HTTP 연결·요청에 공통인 기능도 함께 처리해요.

출처: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_connection_management

지원 프로토콜

HTTP 커넥션 매니저는 HTTP/1.1, HTTP/2, HTTP/3를 네이티브로 지원하고 WebSockets도 지원해요. Envoy의 HTTP 지원은 무엇보다 HTTP/2 멀티플렉싱 프록시가 되도록 설계됐어요. 내부적으로는 HTTP/2 용어로 구성 요소를 표현해서, 예를 들어 HTTP 요청과 응답이 "스트림(stream)" 위에서 이뤄진다고 말해요.

코덱(codec) API가 서로 다른 와이어 프로토콜을 프로토콜에 무관한 형태의 스트림·요청·응답으로 변환해요. HTTP/1.1의 경우 코덱이 직렬/파이프라이닝 특성을 상위 계층에서 HTTP/2처럼 보이게 변환해줘서, 대부분의 코드는 스트림이 HTTP/1.1·HTTP/2·HTTP/3 중 어느 연결에서 왔는지 알 필요가 없어요.

HTTP 생명주기

프록싱은 다운스트림 HTTP 코덱이 요청 헤더 맵을 성공적으로 디코드했을 때 시작돼요. 프록싱이 끝나고 스트림이 파괴되는 시점은 업스트림 프로토콜과 인디펜던트 하프-클로즈(independent half close) 활성 여부에 따라 달라져요.

  • 인디펜던트 하프-클로즈가 활성이고 업스트림이 HTTP/2·HTTP/3이면, 요청과 응답이 모두 끝(end-of-stream)에 도달했을 때 스트림이 파괴돼요.
  • HTTP/1.1 업스트림이거나 하프-클로즈가 비활성이면, 응답이 완료되어 end-of-stream에 도달했을 때 스트림이 파괴돼요. 요청이 아직 끝나지 않았다면 스트림은 리셋돼요.
  • 오류·타임아웃이 나거나 상대가 HTTP/2·HTTP/3 스트림을 리셋하면 프록싱이 일찍 멈출 수 있어요.

헤더 새니타이징

HTTP 커넥션 매니저는 보안을 위해 다양한 헤더 새니타이징(header sanitizing) 동작을 수행해요.

라우트 테이블 설정

각 HTTP 커넥션 매니저 필터는 라우트 테이블(route table)을 가지며, 다음 두 가지 방식으로 지정할 수 있어요.

  • 정적으로 설정
  • RDS API를 통해 동적으로 설정

재시도 플러그인 설정

보통 재시도 시 호스트 선택은 원래 요청과 같은 과정을 따르는데, 재시도 플러그인(retry plugin)으로 이 동작을 바꿀 수 있어요. 두 종류가 있어요.

  • 호스트 프레디킷(Host Predicates): 호스트를 "거부"해서 호스트 선택을 다시 시도하게 해요. 여러 개를 지정할 수 있고, 어느 하나라도 거부하면 그 호스트는 거부돼요. 내장으로 PreviousHostsPredicate, OmitCanaryHostsPredicate, OmitHostMetadataConfig가 있어요.
  • 프라이어리티 프레디킷(Priority Predicates): 재시도 시 프라이어리티 로드를 조정해요. 하나만 지정할 수 있고, PreviousPrioritiesConfig가 내장돼 있어요.

호스트 선택은 프레디킷이 호스트를 받아들이거나 설정된 host_selection_retry_max_attempts(최대 시도 횟수)에 도달할 때까지 계속돼요. 이 플러그인들은 조합해 쓸 수 있고, 커스텀 필터처럼 Envoy에 커스텀 재시도 플러그인을 추가할 수도 있어요.

예를 들어 아직 시도하지 않은 호스트를 우선하는 PreviousHostsPredicate 설정은 다음과 같아요.

routes:
- match:
    prefix: "/"
  route:
    cluster: cluster_0
    retry_policy:
      retry_host_predicate:
      - name: envoy.retry_host_predicates.previous_hosts
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
      host_selection_retry_max_attempts: 3

이렇게 하면 이전에 시도한 호스트를 거부하고, 호스트 선택을 최대 3번까지 재시도해요. 수용 가능한 호스트를 찾을 수 없거나 찾기 어려운 상황에 대비해 시도 횟수 상한이 꼭 필요해요.

내부 리다이렉트와 헤더 맵

내부 리다이렉트(302 같은 리디렉션 응답이 다른 업스트림으로 재요청되는 흐름)를 허용할 때 Envoy는 업스트림으로 보냈던 요청 헤더를 수정해요. 원래 요청 URL 전체를 x-envoy-original-url 헤더에 넣고, Authority/Host, Scheme, Path 헤더를 Location 헤더 값으로 바꾼 다음, response_headers_to_copy에 나열된 헤더를 새 요청에 복사해 다시 라우트를 선택해요.

HTTP 헤더 맵은 헤더 수가 적을 때 아주 빠른 연결 리스트(linked list) 자료 구조로 헤더(와 :로 시작하는 슈도 헤더)의 삽입 순서를 유지해요.

더 알아보기