Envoy Gateway Listener 설계 가이드: 멀티 포트, 멀티 호스트네임, 멀티 인그레스 패턴
Gateway API를 배우는 많은 사람들은 Listener를 "아, 그냥 port: 80 필드 아닌가요" 정도로 생각합니다.
하지만 실제 배포 환경에서 Listener는 결코 조연이 아닙니다. 오히려 ingress 계층 전체의 트래픽 관제 센터에 가깝습니다.
listener는 단순히 포트를 여는 것이 아닙니다. 다음을 정의합니다:
- 어떤 프로토콜을 받을지
- 어떤 hostname을 받을지
- TLS를 수행할지 여부
- 어떤 route 타입이 attach 가능한지
즉 listener는 ingress 계약입니다. 계약을 깔끔하게 쓰면, 그 아래의 route, 팀 소유권, 디버깅 복잡도도 함께 정리됩니다.
올바른 사고 모델 세우기: Listener 하나 = Ingress 슬롯 하나
다음 Gateway를 보겠습니다:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
- name: https
protocol: HTTPS
port: 443
hostname: app.example.com
tls:
mode: Terminate
certificateRefs:
- name: app-cert
- name: grpc
protocol: HTTPS
port: 8443
hostname: grpc.example.com
tls:
mode: Terminate
certificateRefs:
- name: grpc-cert이걸 단순히 "구멍 세 개 뚫린 Gateway 하나"로 보지 마세요. 오히려 이렇게 보는 편이 맞습니다:
httplistener: 평문 HTTP용httpslistener:app.example.com용grpclistener: gRPC 서비스를 위한 전용 ingress 슬롯
이렇게 생각하기 시작하면 많은 설계 판단이 바로 명확해집니다. 여러분은 YAML을 조립하는 것이 아니라, ingress 경계를 설계하고 있는 것입니다.
자주 쓰는 세 가지 분리 패턴
패턴 1: 프로토콜 기준 분리
가장 직관적인 접근입니다:
- HTTP는 하나의 listener
- HTTPS는 하나의 listener
- gRPC는 하나의 listener
- TCP/TLS passthrough는 별도로 분리
이 방식은 책임이 명확합니다. gRPC와 일반 웹 API가 둘 다 HTTP/2 위에서 돌아간다 해도 서비스 모델은 다릅니다. 분리해 두면 route끼리 서로 간섭하는 일을 줄일 수 있습니다.
패턴 2: Hostname 기준 분리
도메인이 여러 개라면, 대표 hostname마다 listener 하나씩 두는 패턴이 흔합니다:
listeners:
- name: app
hostname: app.example.com
protocol: HTTPS
port: 443
- name: admin
hostname: admin.example.com
protocol: HTTPS
port: 443이 방식은 다음에 잘 맞습니다:
- public과 admin 거버넌스를 분리할 때
- 서로 다른 hostname에 서로 다른 인증서가 필요할 때
- route attach 경계를 깔끔하게 유지하고 싶을 때
패턴 3: 팀 또는 환경 책임 기준 분리
여러 팀이 함께 쓰는 환경이라면, listener는 거버넌스 경계 역할을 할 수 있습니다:
- 외부 제품 트래픽용
public-api - 내부 시스템용
internal-api - 파트너 연동용
partner-api
여기서 listener의 가치는 단순 기술 설정을 넘어, 권한과 책임의 경계가 됩니다. 이 패턴은 중대형 팀에서 특히 차이를 만듭니다. "누구나 아무 route나 붙일 수 있는 ingress 하나"라는 재난을 줄여주기 때문입니다.
sectionName과 allowedRoutes: 가장 중요한 두 필드
route는 parentRefs.sectionName을 통해 특정 listener에 attach합니다:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-route
spec:
parentRefs:
- name: eg
sectionName: https
hostnames:
- "app.example.com"
rules:
- backendRefs:
- name: app-service
port: 8080이 route는 https listener만 찾습니다.
sectionName을 지정하지 않아도 시스템은 attach 가능한 listener를 찾는 규칙을 적용하지만, 멀티 listener 환경에서는 명시적으로 써두는 편이 거의 항상 더 안전합니다.
또 하나의 핵심 필드는 allowedRoutes입니다:
listeners:
- name: public
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: Same이 뜻은 Gateway와 같은 네임스페이스에 있는 route만 이 listener에 attach할 수 있다는 것입니다.
흔한 값은 다음과 같습니다:
Same: 같은 네임스페이스만All: 어떤 네임스페이스든 허용Selector: label selector에 맞는 네임스페이스만 허용
플랫폼 경계를 의도적으로 만들고 싶다면 이 필드는 매우 강력합니다. 리스너 수준에서 "누가 이 ingress에 붙을 수 있는지"를 결정하니까요.
실전 조언: 후회 없는 Listener 구조 짜기
아주 유용한 원칙 하나:
각 listener는 하나의 명확한 책임만 가져야 합니다.
예를 들면:
- 하나의 listener는 하나의 주요 hostname 그룹을 담당
- 하나의 listener는 하나의 주요 프로토콜 타입을 담당
- 하나의 listener는 하나의 route 타입 또는 고정된 네임스페이스 집합만 허용
모든 트래픽을 만능 listener 하나에 욱여넣고, downstream route가 알아서 정리해 주길 기대하지 마세요. 처음에는 효율적으로 보여도, 세 달 뒤에는 집집마다 하나씩 생기는 미스터리 케이블 서랍처럼 됩니다. 다 들어 있는데 아무것도 찾을 수 없죠.
구체적인 제안:
- Listener 이름은 의미 있게 짓기:
https-public,grpc-internal - 멀티 listener 환경에서는 항상
sectionName과 짝지어 쓰기 - 중요한 hostname을 하나의 listener에 마구 섞지 않기
allowedRoutes는 먼저 제한적으로 시작하기. 처음부터from: All로 열지 않기
한 줄 요약
Listener를 단순한 포트 설정이 아니라 ingress 계약으로 다루세요.
listener 경계가 깔끔하면 route는 정확히 attach되고, 팀은 자연스럽게 각자 영역을 소유하며, 디버깅도 미로 속 고양이 쫓기처럼 느껴지지 않습니다.
다음 단계
listener 설계가 정리됐다면, 다음으로 흔한 질문은 이것입니다: 서로 다른 네임스페이스와 팀 사이에서, 누가 route를 attach할 수 있고 누가 무엇을 참조할 수 있을까?
다음 글에서는 네임스페이스 간 패턴과 소유권을 다룹니다: 👉 멀티 네임스페이스와 팀 협업