Envoy 내부 구조 · 한 포트가 여러 얼굴을 가질 때 · 이론
체인은 순서가 아니라 구체성으로 이긴다
한 줄 요약
리스너는 '주소와 포트 하나' 이고, 그 안의 필터 체인이 실제로 연결을 받는다. 어느 체인이 받을지는 연결의 성질(SNI · 전송 프로토콜 · ALPN · 출발지)로 정해지며, 그 판정은 여덟 단계의 고정된 순서로 좁혀진다. 설정에 쓴 순서는 아무 상관이 없다.
왜 이게 필요했나
인그레스 게이트웨이 한 대가 도메인 수십 개를 받는다. 도메인마다 인증서가 다르고, 어떤 도메인은 HTTP/2 로 받아야 하고, 내부 헬스 체크는 평문으로 들어온다. 이걸 포트마다 프로세스를 따로 띄워 푸는 방법도 있지만, 그러면 주소를 그만큼 나눠 주어야 하고 설정도 그만큼 흩어진다.
Envoy 는 다른 길을 택했다. 포트는 하나로 두고, 그 포트에 들어온 연결의 성질을 보고 다른 체인으로 보낸다. 그래서 443 하나가 도메인마다 다른 인증서를 내보내고, 같은 포트가 평문 요청도 받을 수 있다.
이 구조를 모르면 두 가지 증상 앞에서 막힌다. 하나는 "인증서를 분명히 넣었는데 이 도메인만 연결이 그냥 끊긴다" 이고, 다른 하나는 "체인을 위로 올렸는데 여전히 아래 것이 받는다" 이다. 둘 다 원인이 같다 — 체인 선택 규칙을 순서로 오해한 것이다.
어떻게 동작하나
연결이 들어오면 리스너 필터가 먼저 돈다. 이 필터들은 아직 바이트를 읽기만 하고 아무것도 바꾸지 않는다. 그중 tls_inspector 가 하는 일이 핵심이다. TLS 핸드셰이크의 첫 메시지(ClientHello)는 평문이라 복호화 없이도 읽을 수 있고, 거기에 SNI(접속하려는 이름)와 ALPN(쓰고 싶은 프로토콜 목록)이 들어 있다. 인스펙터는 그 둘을 꺼내 연결에 붙여 두고, 덤으로 "이 연결은 TLS인가 아닌가" 도 판정해 transport_protocol 을 tls 또는 raw_buffer 로 세운다.
이 필터를 켜지 않으면 server_names 와 application_protocols 조건은 아무 연결에도 맞지 않는다. 설정은 통과하는데 동작만 안 하므로 가장 찾기 어려운 부류의 실수다.
그다음 매칭이 돈다. 공식 문서가 적어 둔 순서는 이렇다.
1. 목적지 포트 2. 목적지 IP3. 서버 이름(SNI) 4. 전송 프로토콜5. 애플리케이션 프로토콜(ALPN)6. 직접 연결된 출발지 IP 7. 출발지 유형8. 출발지 IP 9. 출발지 포트각 단계에서 가장 구체적으로 맞는 체인만 다음 단계로 넘어간다. 조건을 적지 않은 체인은 '아무거나 받겠다' 는 뜻이라 살아남지만, 조건을 적었는데 틀린 체인은 그 자리에서 탈락한다. 모든 단계를 지나면 남는 체인은 최대 하나임이 보장된다.
여기서 나오는 결론 하나가 실무에서 자주 발을 건다. SNI 조건이 ALPN 조건보다 앞이다. 그래서 server_names: ["a.example.com"] 만 적은 체인과 application_protocols: ["h2"] 만 적은 체인이 있을 때, a.example.com 으로 HTTP/2 요청이 오면 SNI 쪽이 받는다. "h2 는 저쪽 체인으로 보내려고 했는데" 라는 말이 나오는 자리가 바로 여기다.
와일드카드도 구체성으로 비교한다. SNI 가 www.example.com 일 때 우선순위는 www.example.com → *.example.com → *.com → 조건 없음 순이다.
현장에서 만나는 모습
"이 도메인만 연결이 끊깁니다." 응답 코드가 없다는 것이 단서다. 404 나 503 은 HTTP 계층까지 갔다는 뜻인데, 맞는 체인이 없으면 그 앞에서 연결이 닫히므로 클라이언트에는 TLS 핸드셰이크 실패로만 보인다. 새 도메인의 인증서를 넣으면서 server_names 에 이름을 추가하는 것을 잊으면 정확히 이 모양이 된다.
"체인 순서를 바꿔도 결과가 같습니다." 당연하다. 순서는 매칭에 쓰이지 않는다. 바꿔야 하는 것은 조건의 구체성이다.
평문과 TLS 를 한 포트로 받는 구성. 쿠버네티스 안에서 헬스 체크는 평문으로, 밖에서 오는 트래픽은 TLS 로 받고 싶을 때 transport_protocol: raw_buffer 체인 하나를 더해 해결한다. 포트를 하나 더 열고 방화벽 규칙을 하나 더 만드는 것보다 훨씬 간단하다.
공식 문서: [Listeners](https://www.envoyproxy.io/docs/envoy/v1.38.3/intro/arch_overview/listeners/listeners) · [FilterChainMatch](https://www.envoyproxy.io/docs/envoy/v1.38.3/api-v3/config/listener/v3/listener_components.proto) · [TLS Inspector](https://www.envoyproxy.io/docs/envoy/v1.38.3/configuration/listeners/listener_filters/tls_inspector)
다음 실습에서 할 것
인증서 두 장을 직접 구워 SNI 로 체인을 가르고, 같은 포트에서 평문과 TLS 를 함께 받고, SNI 와 ALPN 이 동시에 맞을 때 무엇이 이기는지를 직접 확인한다. 마지막에는 어느 체인에도 맞지 않는 요청을 보내 '응답이 아니라 침묵' 을 눈으로 본다.