들어가며
처음 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 계층이 해야 할 일 |
| --- | --- | --- |
| gRPC | HTTP/2 위에서 동작, 양방향 스트리밍, trailer 헤더 | 백엔드까지 HTTP/2(h2 또는 h2c) 유지, 버퍼링 금지 |
| WebSocket | HTTP/1.1 Upgrade 핸드셰이크, 장시간 연결 | Connection/Upgrade 헤더 전달, 긴 idle 타임아웃 |
| HTTP/2 | 멀티플렉싱, 헤더 압축, TLS ALPN 협상 | 클라이언트 측 h2 협상, 선택적으로 백엔드 h2 |
| HTTP/3 | QUIC(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-protocol`이 `GRPC`인지, 백엔드가 평문이면 h2c로 지정됐는지 확인합니다.
- **스트리밍 응답이 끊김**: 프록시 버퍼링이 켜져 있어 서버 스트리밍이 청크 단위로 흐르지 못하는 경우입니다. ingress-nginx에서는 `nginx.ingress.kubernetes.io/proxy-buffering: "off"`로 끕니다.
- **grpc-status가 사라짐**: trailer 헤더 전달이 막힌 경우입니다. 최신 컨트롤러는 기본 지원하지만, 오래된 프록시 앞단이 있으면 trailer를 떨어뜨릴 수 있습니다.
WebSocket 처리
Upgrade 핸드셰이크
WebSocket은 HTTP/1.1 연결에서 시작해 `Upgrade: websocket`과 `Connection: 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-nginx | Traefik | HAProxy | Contour |
| --- | --- | --- | --- | --- |
| gRPC | backend-protocol GRPC | scheme h2c | 지원 | 지원(HTTPProxy) |
| WebSocket | 자동 | 자동 | 자동 | 자동 |
| 백엔드 HTTP/2 | backend-protocol | ServersTransport | 지원 | 지원 |
| 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로 점진적으로 이행하는 로드맵을 그려 두는 것이 좋습니다.
참고 자료
- Kubernetes Ingress 공식 문서: https://kubernetes.io/docs/concepts/services-networking/ingress/
- ingress-nginx gRPC 예제: https://kubernetes.github.io/ingress-nginx/examples/grpc/
- ingress-nginx 어노테이션 레퍼런스: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/
- Traefik Kubernetes IngressRoute: https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/
- HAProxy Kubernetes Ingress Controller: https://www.haproxy.com/documentation/kubernetes-ingress/
- Contour HTTPProxy 문서: https://projectcontour.io/docs/
- Gateway API GRPCRoute: https://gateway-api.sigs.k8s.io/api-types/grpcroute/
- Gateway API HTTPRoute: https://gateway-api.sigs.k8s.io/api-types/httproute/
- cert-manager 문서: https://cert-manager.io/docs/
현재 단락 (1/184)
처음 Kubernetes에 서비스를 올릴 때는 대부분 평범한 HTTP/1.1 REST API입니다. 그런데 서비스가 성장하면서 상황은 빠르게 복잡해집니다. 마이크로서비스 사이의 통...