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 で設定方法がまったく異なるということです。

本記事では 4 つのプロトコルを一つずつ取り上げ、それぞれが Ingress 層に何を要求するのか、コントローラーごとにどう設定するのか、そしてどんな落とし穴にはまりやすいのかを、実践的な YAML とともに整理します。2026 年現在、Ingress API は凍結(frozen)状態で新機能は追加されず、これらの高度なプロトコルは後継標準である Gateway API でより一級の存在として扱われます。そこで最後に、同じ問題を Gateway API でどう解くのかも見ていきます。

プロトコルごとの要件の概観

各プロトコルが Ingress 層に正確に何を要求するのかを整理すると、設定がぐっと明確になります。

プロトコル中核となる要件Ingress 層がすべきこと
gRPCHTTP/2 上で動作、双方向ストリーミング、トレーラーバックエンドまで 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)    (backend)   (QUIC->TCP fallback)
            |           |           |           |
                  [ Backend Services ]

gRPC ルーティング

gRPC が Ingress に要求するもの

gRPC は HTTP/2 のフレーミング上で動作するため、Ingress が単に HTTP/1.1 でバックエンドにプロキシすると動作しません。コントローラーはバックエンドへの接続を HTTP/2 で維持し、レスポンスのバッファリングを無効にし、トレーラー(gRPC は grpc-status をトレーラーで送ります)をそのまま転送する必要があります。バックエンドが 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 が消える: トレーラーの転送が遮られた場合です。最新のコントローラーは既定で対応しますが、古いプロキシが前段にあるとトレーラーを落とすことがあります。

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"
    # クライアント側 HTTP/2 はグローバル ConfigMap の use-http2 などで制御

クライアント側 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対応実験的フラグで有効化、EntryPoint に http3 を設定
HAProxy対応QUIC ビルドで UDP bind に対応
Contour/Envoyデータプレーン対応Envoy は HTTP/3 対応、公開経路の設定が必要
クラウド 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.1h3(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 への段階的な移行のロードマップを描いておくとよいでしょう。

参考資料