- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- Ingress は本来 L7 である
- ingress-nginx の TCP/UDP services ConfigMap
- Traefik の EntryPoint と IngressRouteTCP/UDP
- TLS passthrough — SNI ベースのルーティング
- Gateway API の TCPRoute/UDPRoute
- 専用 L4 ソリューションとの選択
- 実戦事例
- 落とし穴とチェックリスト
- おわりに
- 参考資料
はじめに
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/