Skip to content

필사 모드: Ingress で TCP/UDP サービスを公開する — L4 の限界と解法

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

はじめに

Kubernetes で Web アプリケーションを外部に公開することは比較的直感的です。Ingress リソースを作り、ホストとパスを書けば終わりです。ところがある日、運用チームからこんな依頼が来ます。「PostgreSQL を外部の分析ツールから直接つなげるように開けてほしい」「ゲームサーバーの UDP ポートを公開してほしい」「社内 DNS をクラスターから配信してほしい」。この瞬間、私たちは Ingress の本質的な限界に直面します。

`Ingress` API は設計上 L7、正確には HTTP と HTTPS のための抽象です。ホスト名、URL パス、HTTP ヘッダーといった L7 情報でルーティングするのがすべてです。一方、PostgreSQL のワイヤープロトコル、ゲームの UDP パケット、DNS クエリは HTTP ではない純粋な TCP/UDP トラフィックなので、Ingress リソースで表現する方法がそもそもありません。

それでも現実には、同じ Ingress コントローラーで TCP/UDP まで公開したいという要求が多くあります。別の LoadBalancer をたくさん作りたくないからです。幸い主要なコントローラーは、Ingress API の外側、つまり ConfigMap や EntryPoint、CRD といったコントローラー固有のメカニズムで L4 公開をサポートします。本記事ではその方法を整理し、こうした回避策をいつ使い、いつ専用の L4 ソリューション(MetalLB、クラウド NLB など)を使うのが正しいかの判断基準を示します。また 2026 年の後継標準である Gateway API の `TCPRoute`/`UDPRoute` がこの問題をどう一級として扱うのかも見ていきます。

Ingress は本来 L7 である

まずはっきりさせておきましょう。`Ingress` リソースには TCP/UDP のためのフィールドがありません。spec をいくら眺めても `rules` は `http` ブロックしか持たず、ポートとホスト、パスで HTTP トラフィックをルーティングします。したがって「Ingress で TCP を公開する」という言い方は厳密には誤りで、実際には「Ingress コントローラー(その背後の nginx/Traefik/HAProxy プロセス)に L4 プロキシ設定を追加で重ねる」が正確です。

L7 (HTTP) L4 (TCP/UDP)

--------- ------------

Ingress リソースで表現可能 Ingress リソースで表現不可

host/path ルーティング ポート単位のパススルー

TLS termination TLS passthrough(SNI)

| |

+----- 同じコントローラープロセス -+

(ConfigMap / EntryPoint / CRD で L4 を追加)

この区別を明確にしておくと、後で「なぜ host ルーティングが効かないのか」といった混乱を避けられます。L4 公開ではホスト名ルーティングは(TLS SNI を使わない限り)不可能です。

ingress-nginx の TCP/UDP services ConfigMap

ingress-nginx は L4 公開のために二つの別々の ConfigMap を持ちます。`tcp-services` と `udp-services` です。キーは外部に公開するポート番号、値は `namespace/service:port` 形式のバックエンド指定です。

apiVersion: v1

kind: ConfigMap

metadata:

name: tcp-services

namespace: ingress-nginx

data:

"5432": "databases/postgres:5432"

"6379": "cache/redis:6379"

UDP も同じ方式です。

apiVersion: v1

kind: ConfigMap

metadata:

name: udp-services

namespace: ingress-nginx

data:

"53": "dns/coredns:53"

これで終わりではありません。ingress-nginx コントローラー自体がこの ConfigMap を読むように引数を与える必要があり、コントローラー Pod と LoadBalancer Service で該当ポートを実際に開く必要があります。Deployment の args に次を追加します。

args:

- /nginx-ingress-controller

- --tcp-services-configmap=ingress-nginx/tcp-services

- --udp-services-configmap=ingress-nginx/udp-services

そしてコントローラーを公開する Service にポートを追加します。

apiVersion: v1

kind: Service

metadata:

name: ingress-nginx-controller

namespace: ingress-nginx

spec:

type: LoadBalancer

ports:

- name: http

port: 80

targetPort: 80

- name: https

port: 443

targetPort: 443

- name: postgres

port: 5432

targetPort: 5432

- name: dns-udp

port: 53

protocol: UDP

targetPort: 53

Helm でインストールした場合は、values で `tcp` と `udp` のマッピングを宣言すれば、上の三つのステップをチャートが自動で処理します。

tcp:

5432: "databases/postgres:5432"

udp:

53: "dns/coredns:53"

Traefik の EntryPoint と IngressRouteTCP/UDP

Traefik は L4 をより一級として扱います。まず静的設定で TCP/UDP 用の EntryPoint を定義します。

entryPoints:

postgres:

address: ":5432"

gamedns:

address: ":53/udp"

次に CRD である `IngressRouteTCP` で TCP ルーティングを宣言します。TLS を終端せずそのまま通すには、SNI マッチングと passthrough を併用します。

apiVersion: traefik.io/v1alpha1

kind: IngressRouteTCP

metadata:

name: postgres-route

namespace: databases

spec:

entryPoints:

- postgres

routes:

- match: HostSNI(`*`)

services:

- name: postgres

port: 5432

UDP は `IngressRouteUDP` で扱います。UDP には SNI やホストの概念がないため、マッチ条件なしで EntryPoint に来たトラフィックをバックエンドに送ります。

apiVersion: traefik.io/v1alpha1

kind: IngressRouteUDP

metadata:

name: dns-route

namespace: dns

spec:

entryPoints:

- gamedns

routes:

- services:

- name: coredns

port: 53

TLS passthrough — SNI ベースのルーティング

L4 でホスト名ルーティングを模倣したいとき、唯一の手がかりは TLS ハンドシェイクの SNI(Server Name Indication)です。SNI は暗号化前の ClientHello に平文で含まれており、プロキシは TLS を終端せずにどのドメインへ向かうのかを知ることができます。

ClientHello (SNI: db1.example.com)

-> プロキシは SNI だけを読みルーティングを決定

-> TLS は終端せずバックエンドまでそのまま転送(passthrough)

-> バックエンドが直接 TLS を終端

passthrough の利点は、エンドツーエンド暗号化がバックエンドまで維持され、プロキシが証明書を保持する必要がない点です。ingress-nginx では `nginx.ingress.kubernetes.io/ssl-passthrough` アノテーションとコントローラーの `--enable-ssl-passthrough` フラグで有効化します。Traefik では上の IngressRouteTCP に `tls.passthrough: true` を置きます。ただし SNI を送らない純粋な TCP プロトコル(例: 平文 PostgreSQL)には適用できないという限界があります。

Gateway API の TCPRoute/UDPRoute

`Ingress` API が凍結された今、L4 公開の正解は Gateway API です。最初から複数プロトコルを念頭に設計され、TCP は `TCPRoute`、UDP は `UDPRoute`、TLS passthrough は `TLSRoute` という一級リソースを提供します。

まず Gateway の Listener に L4 プロトコルを宣言します。

apiVersion: gateway.networking.k8s.io/v1

kind: Gateway

metadata:

name: l4-gateway

namespace: infra

spec:

gatewayClassName: example

listeners:

- name: postgres

protocol: TCP

port: 5432

allowedRoutes:

kinds:

- kind: TCPRoute

次に TCPRoute でバックエンドをつなぎます。

apiVersion: gateway.networking.k8s.io/v1alpha2

kind: TCPRoute

metadata:

name: postgres

namespace: databases

spec:

parentRefs:

- name: l4-gateway

namespace: infra

sectionName: postgres

rules:

- backendRefs:

- name: postgres

port: 5432

UDPRoute も同じ構造で `protocol: UDP` の Listener につなぎます。アノテーションや ConfigMap の回避策なしに、そしてコントローラーに依存せずに表現される点が中核の利点です。ただし TCPRoute/UDPRoute は Gateway API の中で比較的遅く GA に向かう部分なので、採用前に使用する実装(例: Cilium、Istio、Envoy Gateway)の対応水準を確認する必要があります。

専用 L4 ソリューションとの選択

ここで一歩引いて考えるべきです。TCP/UDP をわざわざ Ingress コントローラーに重ねることが常に最善とは限りません。しばしばよりシンプルで堅牢な答えは専用の L4 経路です。

| アプローチ | 適する状況 | 欠点 |

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

| Ingress コントローラー L4(ConfigMap/CRD) | すでにコントローラーを運用中、ポート数個の追加 | コントローラー依存、ホストルーティング制約 |

| Service type LoadBalancer | サービスごとに独立 IP、最もシンプル | クラウド LB コスト、IP 消費 |

| MetalLB(オンプレ) | ベアメタルで LoadBalancer を実装 | L2/BGP ネットワークの理解が必要 |

| クラウド NLB(L4) | 高性能な純粋 L4、大量接続 | L7 機能なし |

| Gateway API TCPRoute | 標準化・移植性、複数プロトコル | 実装の対応成熟度にばらつき |

判断基準はシンプルです。ポートが一つか二つで、すでに ingress-nginx や Traefik を運用しているなら、コントローラー L4 公開が運用コストを節約します。逆に、データベースのようにスループットと安定性が重要、公開する L4 サービスが多い、あるいは L7 機能がまったく不要なら、NLB や type LoadBalancer Service の方が明確でデバッグしやすいです。

実戦事例

データベースの公開

本番データベースを外部に直接開くのはセキュリティ上慎重であるべきです。公開が避けられないなら、TLS passthrough でエンドツーエンド暗号化を維持し、NetworkPolicy で送信元 IP を制限し、可能であれば読み取り専用レプリカのみを公開します。

ゲームサーバー UDP

リアルタイムゲームは UDP を使うことが多く、遅延に敏感です。UDP は SNI ルーティングができないため、ポート単位でのみ分岐します。セッション中に同じバックエンドへ行く必要がある場合、クラウド NLB のセッション維持またはクライアント IP ベースのハッシュを活用します。

DNS の配信

DNS は UDP 53 と TCP 53 の両方を扱う必要があります(大きな応答やゾーン転送は TCP)。したがって同じ 53 ポートを udp-services と tcp-services の両方に登録し、Service にも両プロトコルを宣言する必要があります。

落とし穴とチェックリスト

L4 公開でよくはまる落とし穴です。

- **ホストルーティングの期待**: L4 には host の概念がありません。SNI なしでホストごとの分岐を期待すると失敗します。

- **Service ポートの漏れ**: ConfigMap に登録しても、コントローラー Service にポートを開かないと外部から届きません。

- **UDP ファイアウォール**: クラウドのセキュリティグループやノードのファイアウォールで UDP が既定でブロックされていることが多いです。

- **コントローラーフラグ未設定**: ConfigMap だけ作って `--tcp-services-configmap` 引数を与えないと無視されます。

- **送信元 IP の保持**: L4 プロキシを経由するとクライアント IP が隠れることがあります。`externalTrafficPolicy: Local` または PROXY プロトコルを検討します。

チェックリスト:

[ ] 公開対象は本当に L7 ではない純粋な TCP/UDP か

[ ] ConfigMap/CRD にポートとバックエンドを正確に登録したか

[ ] コントローラー Service(LoadBalancer) に該当ポートを開けたか

[ ] コントローラーに必要なフラグ/EntryPoint を設定したか

[ ] ファイアウォール/セキュリティグループで TCP・UDP ポートを許可したか

[ ] NetworkPolicy で送信元を制限したか(特に DB)

[ ] 専用 L4(NLB/MetalLB) の方がシンプルではないか比較したか

おわりに

Ingress で TCP/UDP を公開することの核心は、Ingress が本来 L7 の抽象であり、L4 トラフィックはコントローラー固有のメカニズムで「おまけ」として重ねられるという事実を理解することです。ingress-nginx の tcp/udp-services ConfigMap、Traefik の EntryPoint と IngressRouteTCP/UDP、TLS passthrough の SNI ルーティングがその道具でした。

しかし本当に綺麗な未来は Gateway API です。TCPRoute、UDPRoute、TLSRoute が L4 を標準リソースとして一級化するからです。同時に、ポート一つか二つのためにコントローラーを複雑にするより、専用の L4 ソリューションの方が良い場合も多いことを忘れてはいけません。公開対象の性質、運用中のインフラ、そして今後の Gateway API 移行計画を併せて選択するのがよいでしょう。

参考資料

- Kubernetes Ingress 公式ドキュメント: https://kubernetes.io/docs/concepts/services-networking/ingress/

- ingress-nginx TCP/UDP 公開ガイド: https://kubernetes.github.io/ingress-nginx/user-guide/exposing-tcp-udp-services/

- ingress-nginx SSL passthrough: https://kubernetes.github.io/ingress-nginx/user-guide/tls/

- Traefik IngressRouteTCP/UDP: https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/

- Gateway API TCPRoute: https://gateway-api.sigs.k8s.io/api-types/tcproute/

- Gateway API UDPRoute: https://gateway-api.sigs.k8s.io/api-types/udproute/

- MetalLB 公式ドキュメント: https://metallb.universe.tf/

- Kubernetes Service 公式ドキュメント: https://kubernetes.io/docs/concepts/services-networking/service/

현재 단락 (1/169)

Kubernetes で Web アプリケーションを外部に公開することは比較的直感的です。Ingress リソースを作り、ホストとパスを書けば終わりです。ところがある日、運用チームからこんな依頼が来ま...

작성 글자: 0원문 글자: 7,299작성 단락: 0/169