Skip to content

필사 모드: Advanced Ingress Protocols — gRPC, WebSocket, HTTP/2, HTTP/3

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Introduction

When you first deploy a service on Kubernetes, it is usually a plain HTTP/1.1 REST API. But as the service grows, things get complicated fast. You switch inter-service communication to gRPC, open a WebSocket for real-time notifications, enable HTTP/2 multiplexing for frontend performance, and start evaluating HTTP/3 (QUIC) to cut latency on mobile networks. All of this traffic must ultimately pass through the cluster's front door: the Ingress controller.

The catch is that the `Ingress` resource itself is fundamentally an abstraction from the HTTP/1.1 era. The `Ingress` API is a simple model that maps hosts and paths to backend Services, and it has no fields that directly express concepts like gRPC streaming, WebSocket upgrades, or QUIC. As a result, these advanced protocols are almost always enabled indirectly through controller-specific annotations or CRDs. The same capability is configured completely differently across ingress-nginx, Traefik, HAProxy, Contour, and Kong.

In this article we walk through the four protocols one by one: what each demands of the Ingress layer, how to configure it per controller, and which pitfalls are easy to fall into, all with hands-on YAML. As of 2026, the `Ingress` API is frozen, meaning no new features are being added, and these advanced protocols are treated as first-class citizens in the successor standard, the Gateway API. So at the end we also look at how the same problems are solved there.

Per-protocol requirements overview

Configuration becomes much clearer once you state exactly what each protocol demands of the Ingress layer.

| Protocol | Core requirement | What the Ingress layer must do |

| --- | --- | --- |

| gRPC | Runs over HTTP/2, bidirectional streaming, trailers | Keep HTTP/2 (h2 or h2c) to the backend, disable buffering |

| WebSocket | HTTP/1.1 Upgrade handshake, long-lived connections | Forward Connection/Upgrade headers, long idle timeouts |

| HTTP/2 | Multiplexing, header compression, TLS ALPN | Client-side h2 negotiation, optionally backend h2 |

| HTTP/3 | QUIC (UDP 443), TLS 1.3 required, Alt-Svc advertise | UDP 443 listener, Alt-Svc header, guaranteed fallback |

Two things stand out. First, with gRPC and HTTP/2 the connection itself must be HTTP/2. Second, WebSocket and HTTP/3 step outside the ordinary request-response model with long-lived connections or a different transport layer. The Ingress controller has to absorb these differences.

[ Client ]

|

TLS 1.3 + ALPN negotiation (h2 / h3 / http/1.1)

|

[ Ingress Controller ]

+-----------+-----------+-----------+

| | | |

gRPC WebSocket HTTP/2 HTTP/3

(h2c?) (Upgrade) (backend) (QUIC->TCP fallback)

| | | |

[ Backend Services ]

gRPC routing

What gRPC demands of Ingress

gRPC runs on top of HTTP/2 framing, so it will not work if the Ingress simply proxies to the backend over HTTP/1.1. The controller must keep the connection to the backend on HTTP/2, disable response buffering, and forward trailers verbatim (gRPC sends `grpc-status` as a trailer). When the backend speaks plaintext HTTP/2 without TLS, that is called h2c (HTTP/2 cleartext).

gRPC on ingress-nginx

ingress-nginx selects the backend protocol with the `nginx.ingress.kubernetes.io/backend-protocol` annotation. For gRPC the value is `GRPC`, or `GRPCS` for a TLS backend.

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

One thing to note: gRPC over HTTP/2 strongly favors TLS in practice. To negotiate `h2`, the client needs ALPN, and ALPN happens during the TLS handshake. So you always include a `tls` block, as in the example above.

Path-based gRPC method routing

gRPC methods map to HTTP/2 paths. For example, the `helloworld.Greeter/SayHello` method arrives on path `/helloworld.Greeter/SayHello`. So to route per service you can use the package and service name as a path 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

gRPC on Traefik

Traefik sets the scheme per service for h2c backends. In Kubernetes this is handled on the Service or IngressRoute via an annotation or 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 troubleshooting — 504 and UNAVAILABLE

When you first put gRPC behind Ingress you commonly hit two symptoms.

- **504 Gateway Timeout**: the backend protocol is set to HTTP/1.1, so the controller cannot understand HTTP/2 frames. Verify that `backend-protocol` is `GRPC`, and that a plaintext backend is configured as h2c.

- **Streaming responses cut off**: proxy buffering is on, so server streams cannot flow chunk by chunk. In ingress-nginx, turn it off with `nginx.ingress.kubernetes.io/proxy-buffering: "off"`.

- **grpc-status disappears**: trailer forwarding is blocked. Modern controllers support this by default, but an older proxy in front may drop trailers.

WebSocket handling

The Upgrade handshake

WebSocket begins on an HTTP/1.1 connection and switches protocol with the `Upgrade: websocket` and `Connection: Upgrade` headers. The Ingress controller must forward these headers to the backend, and after the switch the connection becomes a long-lived bidirectional stream rather than a request-response pair.

Most controllers detect WebSocket upgrades automatically. ingress-nginx handles them by observing the Upgrade header, with no special annotation needed. The real problem is not the handshake but the timeout.

Increasing the idle timeout

The default proxy timeout is usually around 60 seconds. A WebSocket connection that goes quiet for a while hits this timeout and gets dropped. You therefore need to raise the idle timeout substantially.

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

In Traefik you adjust timeouts on the EntryPoint or via middleware. WebSocket itself is handled transparently by Traefik, so no separate enabling is required.

WebSocket troubleshooting

- **Connection drops every minute**: the classic symptom of not raising the read/send timeouts shown above.

- **426 Upgrade Required or failed handshake**: an upstream proxy forcing HTTP/2 is ignoring the Upgrade header. WebSocket must flow over the HTTP/1.1 path.

- **Session breaks while bouncing between backends**: without sticky sessions across replicas, the handshake and subsequent frames can land on different Pods. Enable session affinity with `nginx.ingress.kubernetes.io/affinity: "cookie"`.

HTTP/2 backends

HTTP/2 between the client and the Ingress is often negotiated automatically via ALPN once TLS is enabled. The part that needs separate attention is HTTP/2 between the Ingress and the backend. Even without gRPC, if your backend supports HTTP/2 and you want the multiplexing benefit, set the backend protocol to HTTP/2.

In ingress-nginx you use the same `backend-protocol`.

metadata:

annotations:

nginx.ingress.kubernetes.io/backend-protocol: "HTTP"

control client-side HTTP/2 with use-http2 etc. in the global ConfigMap

Client-side HTTP/2 is controlled in the global ingress-nginx ConfigMap. It is usually on by default and negotiates `h2` via ALPN on TLS-enabled hosts.

HTTP/3 and QUIC

Why HTTP/3 is different

HTTP/3 uses QUIC, which runs over UDP rather than TCP, as its transport. That means the Ingress controller must open a UDP 443 listener, TLS 1.3 is mandatory, and to coexist with existing TCP-based HTTP/2 it advertises "this service also speaks HTTP/3" via the `Alt-Svc` header. The client connects over HTTP/2 first, and after seeing Alt-Svc, tries HTTP/3 on subsequent connections.

Per-controller HTTP/3 support

As of 2026, HTTP/3 maturity varies widely by controller.

| Controller | HTTP/3 support | Notes |

| --- | --- | --- |

| ingress-nginx | Limited/experimental | Depends on the QUIC module of nginx, affected by maintenance mode |

| Traefik | Supported | Enabled with an experimental flag, http3 set on the EntryPoint |

| HAProxy | Supported | UDP bind in QUIC builds |

| Contour/Envoy | Dataplane support | Envoy supports HTTP/3, exposure path must be configured |

| Cloud LB based | Generally supported | HTTP/3 toggle on the cloud L7 LB |

Enabling HTTP/3 in Traefik

Traefik enables HTTP/3 on the EntryPoint in static config. The example below shows the value structure.

entryPoints:

websecure:

address: ":443"

http3:

advertisedPort: 443

The key point is that UDP 443 must be open on the LoadBalancer Service and on node firewalls. If it is blocked, the client silently falls back to HTTP/2, which produces the common confusion of "HTTP/3 is enabled but never used."

HTTP/3 troubleshooting

- **Always connects over HTTP/2 only**: UDP 443 is blocked on the LB or firewall, or the Alt-Svc header is not advertised.

- **Intermittent connection failures**: a middlebox somewhere on the path blocks UDP, so QUIC fails. TCP fallback must be guaranteed for normal operation in this case.

TLS ALPN negotiation

Most of the protocols above rely on ALPN (Application-Layer Protocol Negotiation). ALPN is a TLS extension where the client and server agree on which application protocol to use during the handshake. Candidates include `h2` (HTTP/2), `http/1.1`, and `h3` (HTTP/3).

ClientHello (ALPN: h3, h2, http/1.1)

-> ServerHello (ALPN selected: h2)

-> connection proceeds over HTTP/2

So to use gRPC, HTTP/2, and HTTP/3 properly, TLS is a prerequisite. While you can leave the backend in plaintext (h2c), the standard is to almost always enable TLS on the client-to-Ingress leg and manage certificates with something like cert-manager.

Consolidated per-controller support table

| Feature | ingress-nginx | Traefik | HAProxy | Contour |

| --- | --- | --- | --- | --- |

| gRPC | backend-protocol GRPC | scheme h2c | Supported | Supported (HTTPProxy) |

| WebSocket | Automatic | Automatic | Automatic | Automatic |

| Backend HTTP/2 | backend-protocol | ServersTransport | Supported | Supported |

| HTTP/3 | Experimental | Supported | Supported | Via Envoy |

| TLS ALPN | Supported | Supported | Supported | Supported |

Handling in the Gateway API

With the `Ingress` API now frozen, the Gateway API is what fundamentally solves the annotation sprawl above. The Gateway API treats protocols as first-class concepts. For instance, gRPC is expressed with a dedicated `GRPCRoute` resource, letting you declare service- and method-level routing without annotation workarounds.

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 and WebSocket flow naturally through `HTTPRoute`, and the Listener's `protocol` and TLS settings determine ALPN negotiation. HTTP/3 support depends on the maturity of each implementation's Gateway support (Envoy, Traefik, and so on). The key benefit is far greater portability, since these are expressed as standard resources instead of per-controller annotations. For a new cluster, when advanced protocols become a requirement, consider the Gateway API first.

Closing thoughts

Handling gRPC, WebSocket, HTTP/2, and HTTP/3 in Ingress ultimately comes down to "pushing non-HTTP/1.1 traffic through an abstraction built around HTTP/1.1." gRPC means keeping HTTP/2 to the backend and disabling buffering; WebSocket means forwarding the Upgrade and using long timeouts; HTTP/2 means ALPN and the backend protocol setting; HTTP/3 means UDP 443, Alt-Svc, and guaranteed fallback.

The same capability is configured differently per controller, which is an operational burden, but the Gateway API is absorbing that fragmentation into standard resources. Even if you run on `Ingress` today, if demand for advanced protocols grows, it is wise to map out a roadmap for a gradual migration to the Gateway API based on GRPCRoute and HTTPRoute.

References

- Kubernetes Ingress official docs: https://kubernetes.io/docs/concepts/services-networking/ingress/

- ingress-nginx gRPC example: https://kubernetes.github.io/ingress-nginx/examples/grpc/

- ingress-nginx annotation reference: 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 docs: 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 docs: https://cert-manager.io/docs/

현재 단락 (1/184)

When you first deploy a service on Kubernetes, it is usually a plain HTTP/1.1 REST API. But as the s...

작성 글자: 0원문 글자: 11,733작성 단락: 0/184