Skip to content
Published on

Ingress 고급 프로토콜 — gRPC, WebSocket, HTTP/2, HTTP/3

Authors

들어가며

처음 Kubernetes에 서비스를 올릴 때는 대부분 평범한 HTTP/1.1 REST API입니다. 그런데 서비스가 성장하면서 상황은 빠르게 복잡해집니다. 마이크로서비스 사이의 통신을 gRPC로 바꾸고, 실시간 알림을 위해 WebSocket을 열고, 프런트엔드 성능을 위해 HTTP/2 멀티플렉싱을 켜고, 모바일 환경의 지연을 줄이려고 HTTP/3(QUIC)을 검토하게 됩니다. 그리고 이 모든 트래픽은 결국 클러스터의 입구인 Ingress 컨트롤러를 통과해야 합니다.

문제는 Ingress 리소스 자체가 본질적으로 HTTP/1.1 시대의 추상화라는 점입니다. Ingress API는 호스트와 경로를 백엔드 Service에 매핑하는 단순한 모델이고, gRPC 스트리밍이나 WebSocket 업그레이드, QUIC 같은 개념을 직접 표현하는 필드가 없습니다. 그래서 이런 고급 프로토콜은 거의 전부 컨트롤러별 어노테이션이나 CRD를 통해 우회적으로 활성화됩니다. 같은 기능이라도 ingress-nginx, Traefik, HAProxy, Contour, Kong에서 설정 방법이 제각각이라는 뜻입니다.

이 글에서는 네 가지 프로토콜을 하나씩 짚으면서, 각 프로토콜이 Ingress 계층에 무엇을 요구하는지, 컨트롤러별로 어떻게 설정하는지, 그리고 어떤 함정에 빠지기 쉬운지를 실습 YAML과 함께 정리합니다. 2026년 현재 Ingress API는 동결(frozen) 상태로 더 이상 새 기능이 추가되지 않으며, 이런 고급 프로토콜은 후계 표준인 Gateway API에서 훨씬 일급으로 다뤄집니다. 따라서 마지막에는 Gateway API에서 같은 문제를 어떻게 푸는지도 함께 살펴봅니다.

프로토콜별 요구사항 개관

각 프로토콜이 Ingress 계층에 정확히 무엇을 요구하는지부터 정리하면 설정이 한결 명확해집니다.

프로토콜핵심 요구사항Ingress 계층이 해야 할 일
gRPCHTTP/2 위에서 동작, 양방향 스트리밍, trailer 헤더백엔드까지 HTTP/2(h2 또는 h2c) 유지, 버퍼링 금지
WebSocketHTTP/1.1 Upgrade 핸드셰이크, 장시간 연결Connection/Upgrade 헤더 전달, 긴 idle 타임아웃
HTTP/2멀티플렉싱, 헤더 압축, TLS ALPN 협상클라이언트 측 h2 협상, 선택적으로 백엔드 h2
HTTP/3QUIC(UDP 443), TLS 1.3 필수, Alt-Svc 광고UDP 443 리스너, Alt-Svc 헤더, 폴백 보장

핵심은 두 가지입니다. 첫째, gRPC와 HTTP/2는 "연결 자체"가 HTTP/2여야 한다는 점, 둘째 WebSocket과 HTTP/3은 일반적인 요청-응답 모델을 벗어난 장시간 연결 또는 다른 전송 계층을 쓴다는 점입니다. Ingress 컨트롤러는 이 차이를 흡수해 줘야 합니다.

                    [ Client ]
                        |
        TLS 1.3 + ALPN 협상 (h2 / h3 / http/1.1)
                        |
                 [ Ingress Controller ]
            +-----------+-----------+-----------+
            |           |           |           |
          gRPC       WebSocket    HTTP/2      HTTP/3
         (h2c?)      (Upgrade)    (백엔드)    (QUIC->TCP 폴백)
            |           |           |           |
                  [ Backend Services ]

gRPC 라우팅

gRPC가 Ingress에 요구하는 것

gRPC는 HTTP/2 프레이밍 위에서 동작하므로, Ingress가 단순히 HTTP/1.1로 백엔드에 프록시하면 동작하지 않습니다. 컨트롤러는 백엔드로 향하는 연결을 HTTP/2로 유지해야 하고, 응답 버퍼링을 끄고 trailer 헤더(gRPC는 grpc-status를 trailer로 보냅니다)를 그대로 전달해야 합니다. 백엔드가 TLS 없이 평문 HTTP/2를 쓴다면 이를 h2c(HTTP/2 cleartext)라고 부릅니다.

ingress-nginx에서의 gRPC

ingress-nginx는 nginx.ingress.kubernetes.io/backend-protocol 어노테이션으로 백엔드 프로토콜을 지정합니다. gRPC라면 값을 GRPC 또는 TLS 백엔드의 경우 GRPCS로 둡니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: grpc-ingress
  namespace: demo
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - grpc.example.com
      secretName: grpc-tls
  rules:
    - host: grpc.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: grpc-server
                port:
                  number: 50051

여기서 주의할 점은 gRPC over HTTP/2는 사실상 TLS를 강하게 권장한다는 것입니다. 클라이언트가 h2를 협상하려면 ALPN이 필요하고, ALPN은 TLS 핸드셰이크에서 이뤄지기 때문입니다. 따라서 위 예시처럼 tls 블록을 반드시 함께 둡니다.

경로 기반 gRPC 메서드 라우팅

gRPC의 메서드는 HTTP/2 경로로 매핑됩니다. 예를 들어 helloworld.Greeter/SayHello 메서드는 경로 /helloworld.Greeter/SayHello로 들어옵니다. 따라서 서비스 단위로 라우팅하려면 패키지·서비스 이름을 경로 prefix로 사용할 수 있습니다.

    - host: grpc.example.com
      http:
        paths:
          - path: /helloworld.Greeter
            pathType: Prefix
            backend:
              service:
                name: greeter-svc
                port:
                  number: 50051
          - path: /inventory.Stock
            pathType: Prefix
            backend:
              service:
                name: stock-svc
                port:
                  number: 50051

Traefik에서의 gRPC

Traefik은 h2c 백엔드를 위해 서비스 단위로 scheme을 지정합니다. Kubernetes에서는 Service나 IngressRoute에 어노테이션 또는 ServersTransport로 처리합니다.

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: grpc-route
  namespace: demo
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`grpc.example.com`)
      kind: Rule
      services:
        - name: grpc-server
          port: 50051
          scheme: h2c
  tls:
    secretName: grpc-tls

gRPC 트러블슈팅 — 504와 UNAVAILABLE

gRPC를 Ingress에 처음 올리면 흔히 두 가지 증상을 만납니다.

  • 504 Gateway Timeout: 백엔드 프로토콜이 HTTP/1.1로 잡혀 있어 컨트롤러가 HTTP/2 프레임을 이해하지 못하는 경우입니다. backend-protocolGRPC인지, 백엔드가 평문이면 h2c로 지정됐는지 확인합니다.
  • 스트리밍 응답이 끊김: 프록시 버퍼링이 켜져 있어 서버 스트리밍이 청크 단위로 흐르지 못하는 경우입니다. ingress-nginx에서는 nginx.ingress.kubernetes.io/proxy-buffering: "off"로 끕니다.
  • grpc-status가 사라짐: trailer 헤더 전달이 막힌 경우입니다. 최신 컨트롤러는 기본 지원하지만, 오래된 프록시 앞단이 있으면 trailer를 떨어뜨릴 수 있습니다.

WebSocket 처리

Upgrade 핸드셰이크

WebSocket은 HTTP/1.1 연결에서 시작해 Upgrade: websocketConnection: Upgrade 헤더로 프로토콜을 전환합니다. Ingress 컨트롤러는 이 헤더들을 백엔드로 그대로 전달해야 하고, 전환된 후의 연결은 요청-응답이 아니라 장시간 유지되는 양방향 스트림이 됩니다.

대부분의 컨트롤러는 WebSocket 업그레이드를 자동으로 감지합니다. ingress-nginx는 별도 어노테이션 없이도 Upgrade 헤더를 보고 자동으로 처리합니다. 다만 진짜 문제는 핸드셰이크가 아니라 타임아웃입니다.

idle 타임아웃 늘리기

기본 프록시 타임아웃은 보통 60초 안팎입니다. WebSocket 연결은 메시지가 한동안 오가지 않으면 이 타임아웃에 걸려 끊깁니다. 따라서 idle 타임아웃을 충분히 늘려야 합니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ws-ingress
  namespace: demo
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
  ingressClassName: nginx
  rules:
    - host: ws.example.com
      http:
        paths:
          - path: /ws
            pathType: Prefix
            backend:
              service:
                name: ws-server
                port:
                  number: 8080

Traefik에서는 EntryPoint나 미들웨어에서 타임아웃을 조정합니다. WebSocket 자체는 Traefik이 투명하게 처리하므로 별도 활성화가 필요 없습니다.

WebSocket 트러블슈팅

  • 연결이 1분마다 끊김: 위에서 본 read/send 타임아웃을 늘리지 않은 전형적 증상입니다.
  • 426 Upgrade Required 또는 핸드셰이크 실패: 앞단에 HTTP/2를 강제하는 프록시가 있어 Upgrade 헤더가 무시되는 경우입니다. WebSocket은 HTTP/1.1 경로로 흘러야 합니다.
  • 세션이 백엔드 사이를 오가다 깨짐: 여러 레플리카에 스티키 세션이 없으면 핸드셰이크와 후속 프레임이 다른 Pod로 갈 수 있습니다. nginx.ingress.kubernetes.io/affinity: "cookie"로 세션 어피니티를 켭니다.

HTTP/2 백엔드

클라이언트와 Ingress 사이의 HTTP/2는 TLS만 켜면 ALPN으로 자동 협상되는 경우가 많습니다. 별도로 신경 쓸 부분은 Ingress와 백엔드 사이의 HTTP/2입니다. gRPC가 아니더라도 백엔드가 HTTP/2를 지원하고 멀티플렉싱 이점을 얻고 싶다면 백엔드 프로토콜을 HTTP/2로 지정합니다.

ingress-nginx에서는 같은 backend-protocol을 사용합니다.

metadata:
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
    # 전역 ConfigMap에서 use-http2 등을 켜 클라이언트측 HTTP/2 제어

클라이언트 측 HTTP/2는 ingress-nginx의 전역 ConfigMap에서 제어합니다. 대부분 기본으로 켜져 있으며, TLS가 활성화된 호스트에서 ALPN으로 h2를 협상합니다.

HTTP/3와 QUIC

HTTP/3가 다른 이유

HTTP/3는 TCP가 아니라 UDP 위에서 동작하는 QUIC를 전송 계층으로 씁니다. 즉 Ingress 컨트롤러가 UDP 443 리스너를 열어야 하고, TLS 1.3이 필수이며, 기존 TCP 기반 HTTP/2와 공존하기 위해 Alt-Svc 헤더로 "이 서비스는 HTTP/3도 됩니다"를 광고합니다. 클라이언트는 처음에는 HTTP/2로 붙고, Alt-Svc를 본 뒤 다음 연결부터 HTTP/3를 시도하는 식입니다.

컨트롤러별 HTTP/3 지원 현황

2026년 기준으로 HTTP/3 지원은 컨트롤러마다 성숙도가 크게 다릅니다.

컨트롤러HTTP/3 지원비고
ingress-nginx제한적/실험적기반 nginx의 QUIC 모듈 의존, 유지보수 모드 영향
Traefik지원experimental 플래그로 활성화, EntryPoint에 http3 설정
HAProxy지원QUIC 빌드에서 UDP bind 지원
Contour/Envoy데이터플레인 지원Envoy는 HTTP/3 지원, 노출 경로 설정 필요
Cloud LB 기반대체로 지원클라우드 L7 LB에서 HTTP/3 토글

Traefik HTTP/3 활성화 예시

Traefik은 정적 설정에서 EntryPoint에 HTTP/3를 켭니다. 아래는 값 구조를 보여주는 예시입니다.

entryPoints:
  websecure:
    address: ":443"
    http3:
      advertisedPort: 443

UDP 443이 LoadBalancer Service와 노드 방화벽에서 열려 있어야 한다는 점이 핵심입니다. 이게 막혀 있으면 클라이언트는 조용히 HTTP/2로 폴백하므로, "HTTP/3가 켜졌는데 안 쓰인다"는 흔한 혼란이 생깁니다.

HTTP/3 트러블슈팅

  • 계속 HTTP/2로만 붙음: UDP 443이 LB나 방화벽에서 막혔거나 Alt-Svc 헤더가 광고되지 않는 경우입니다.
  • 간헐적 연결 실패: 네트워크 경로 중간에 UDP를 차단하는 미들박스가 있으면 QUIC가 실패합니다. 이때 정상 동작을 위해 TCP 폴백이 반드시 보장돼야 합니다.

TLS ALPN 협상

지금까지 본 프로토콜 대부분은 ALPN(Application-Layer Protocol Negotiation)에 의존합니다. ALPN은 TLS 핸드셰이크 도중 클라이언트와 서버가 "어떤 애플리케이션 프로토콜을 쓸지"를 합의하는 확장입니다. h2(HTTP/2), http/1.1, h3(HTTP/3) 등이 협상 대상입니다.

ClientHello (ALPN: h3, h2, http/1.1)
        ->  ServerHello (ALPN 선택: h2)
        ->  이후 연결은 HTTP/2로 진행

따라서 gRPC, HTTP/2, HTTP/3를 제대로 쓰려면 TLS가 전제 조건입니다. 평문(h2c)으로 백엔드를 둘 수는 있어도, 클라이언트-Ingress 구간에서는 거의 항상 TLS를 켜고 인증서는 cert-manager 등으로 관리하는 것이 표준입니다.

컨트롤러별 종합 지원 테이블

기능ingress-nginxTraefikHAProxyContour
gRPCbackend-protocol GRPCscheme h2c지원지원(HTTPProxy)
WebSocket자동자동자동자동
백엔드 HTTP/2backend-protocolServersTransport지원지원
HTTP/3실험적지원지원Envoy 경유
TLS ALPN지원지원지원지원

Gateway API에서의 처리

Ingress API가 동결된 지금, 위의 어노테이션 난립 문제를 근본적으로 해결하는 것이 Gateway API입니다. Gateway API는 프로토콜을 일급 개념으로 다룹니다. 예를 들어 gRPC는 GRPCRoute라는 전용 리소스로 표현되어, 어노테이션 우회 없이 서비스·메서드 단위 라우팅을 선언할 수 있습니다.

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: greeter
  namespace: demo
spec:
  parentRefs:
    - name: prod-gateway
  hostnames:
    - grpc.example.com
  rules:
    - matches:
        - method:
            service: helloworld.Greeter
            method: SayHello
      backendRefs:
        - name: greeter-svc
          port: 50051

HTTP/2와 WebSocket은 HTTPRoute에서 자연스럽게 흐르고, Listener의 protocol과 TLS 설정이 ALPN 협상을 결정합니다. HTTP/3는 구현체(Envoy, Traefik 등)의 Gateway 구현 수준에 따라 지원됩니다. 핵심은 컨트롤러별 어노테이션 대신 표준 리소스로 표현되어 이식성이 크게 높아진다는 점입니다. 신규 클러스터라면 고급 프로토콜이 필요한 시점에 Gateway API를 우선 검토하는 것을 권합니다.

마치며

Ingress에서 gRPC, WebSocket, HTTP/2, HTTP/3를 다루는 일은 결국 "HTTP/1.1을 전제로 만들어진 추상화에 비-HTTP/1.1 트래픽을 흘려보내는" 작업입니다. gRPC는 백엔드까지 HTTP/2를 유지하고 버퍼링을 끄는 것, WebSocket은 Upgrade 전달과 긴 타임아웃, HTTP/2는 ALPN과 백엔드 프로토콜 지정, HTTP/3는 UDP 443과 Alt-Svc 그리고 폴백 보장이 핵심이었습니다.

같은 기능이라도 컨트롤러마다 설정 방법이 달라 운영 부담이 크지만, Gateway API가 이 파편화를 표준 리소스로 흡수하고 있습니다. 2026년 현재 Ingress로 운영 중인 서비스라도, 고급 프로토콜 요구가 커진다면 GRPCRoute와 HTTPRoute 기반의 Gateway API로 점진적으로 이행하는 로드맵을 그려 두는 것이 좋습니다.

참고 자료