Envoy Gateway Listener 설계 가이드: 멀티 포트, 멀티 호스트네임, 멀티 인그레스 패턴

8 min read

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 하나"로 보지 마세요. 오히려 이렇게 보는 편이 맞습니다:

  • http listener: 평문 HTTP용
  • https listener: app.example.com
  • grpc listener: 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 하나"라는 재난을 줄여주기 때문입니다.

sectionNameallowedRoutes: 가장 중요한 두 필드

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할 수 있고 누가 무엇을 참조할 수 있을까?

다음 글에서는 네임스페이스 간 패턴과 소유권을 다룹니다: 👉 멀티 네임스페이스와 팀 협업