はじめに
こんにちは、モノタロウでコンテナ基盤グループに所属している田中です。
以前の記事では、ArgoCD Pull Request Generator を活用して、PRを作るだけで検証環境 (Preview 環境) が立ち上がる仕組みを紹介しました。
おかげさまでこの Preview 環境は社内で大変好評で、現在ではデファクトスタンダードな検証環境として、多くの開発チームに日常的に利用されています。 しかし、利用が拡大するにつれて新たな課題も生まれてきました。今回は、その課題を AWS VPC Lattice と Kubernetes Gateway API を導入して解決し、さらに開発者体験を向上させた話をご紹介します。
なお、本記事で紹介する構成は AWS のマネージドサービスである VPC Lattice を中核としているため、Amazon EKS (Elastic Kubernetes Service) 環境での動作を前提としています。GKE やオンプレミス等の環境ではそのまま適用できませんが、マルチクラスタにおけるルーティング抽象化のアイデアとして参考にしていただければ幸いです。
背景と課題:マルチクラスタ展開の壁
当初、この Preview 環境の仕組みは 1つの Kubernetes クラスタ(以降、クラスタ)で運用されていました。 しかし、社内での利用が広まるにつれ、「他のサービスが動いているクラスタでも同じ仕組みを使いたい」といった要望が増えてきました。
もちろん、仕組み自体を他のクラスタに展開すること自体は難しくありません。しかし、既存の Ingress NGINX をベースとしたルーティング構成のままマルチクラスタ展開を行うと、「ドメイン名」 に関する問題が発生します。
本記事では、Kubernetes の Ingress コントローラーとして利用しているものを「Ingress NGINX」、今回 VPC Lattice の前段にリバースプロキシとして独自に配置したものを「Nginx」と表記して区別します。
「どのクラスタにいるか」を意識させたくない
Ingress NGINX はクラスタ内部でルーティングを行うため、外部からアクセスする際には「どのクラスタの Ingress コントローラーに到達させるか」を DNS レベルで区別する必要があります。 そのため、サブドメインにクラスタを識別するための情報を含める必要が出てきます。
- hoge クラスタの場合:
*.hoge-cluster-preview.monotaro.com - fuga クラスタの場合:
*.fuga-cluster-preview.monotaro.com
これでは、プラットフォーム利用者は「自分のアプリは今どのクラスタにデプロイされているんだっけ?」と常に意識して URL を使い分ける必要があります。 私たちはプラットフォームエンジニアリングの観点から、「アプリがどこで動いていようと、開発者は意識せずに同じ使い勝手でアクセスできる(抽象化されている)」 状態があるべき姿だと考えました。
Ingress NGINX のプロジェクト終了と Gateway API への移行
また、今回の刷新にはもう一つ、インフラ基盤としての重要な動機がありました。 皆さんもご存知の通り、長年 Kubernetes のスタンダードであった Ingress NGINX プロジェクトが 2026年3月をもって終了する とのアナウンスが出されています(参照)。
これにより、コミュニティの主軸は明確に Gateway API へと移っています。もちろん、プロジェクト終了により直ちにシステムが危険に晒されるわけではありませんが、将来的に新たな脆弱性が発見された際に公式のサポートが受けられなくなるリスクが生じます。 私たちは、単に現在の課題(ドメイン名の抽象化)を解決するだけでなく、将来にわたって持続可能なプラットフォームを維持するためには、今このタイミングで Ingress から Gateway API へ移行すべきだと判断しました。
AWS VPC Lattice は Gateway API をネイティブにサポートしているため、この「モダンな標準への移行」と「マルチクラスタ課題の解決」を同時に実現できる、一石二鳥のサービスだったのです。
解決策のアプローチ:ルーティングの「外出し」
--
※ (2026/04/01 10:00) AWS Load Balancer Controller (LBC) を採用しなかった背景について追記
--
「動的なルーティング設定をクラスタ内(Ingress)で完結させようとするから、クラスタへの依存が生まれる」 そう考えた私たちは、ルーティングの決定権をクラスタの外にある AWS マネージドサービス AWS VPC Lattice へ移譲・集約することにしました。
さらに、設定のインターフェースとして Kubernetes Gateway API を採用しました。これにより、
- 開発者はどのクラスタからでも、標準的な
HTTPRouteリソースを作成するだけ。 - 裏側で AWS Gateway API Controller が VPC Lattice の設定を自動更新。
- すべての設定が VPC Lattice という「クラスタ外の 1つの場所」に集約される。
という構成が実現でき、クラスタを跨いだルーティングの抽象化が可能になります。
なお、Gateway API を利用するにあたり、コントローラーとして AWS Load Balancer Controller (LBC) を指定する(spec.controllerName: gateway.k8s.aws/alb)という、よりスタンダードなアプローチを思い浮かべた方もいらっしゃるかもしれません。(実際にそのようなご意見もいただき、非常に鋭い視点だと感じました。ありがとうございます!)
結論から言うと、マルチクラスタ環境において、アプリケーション開発者にルーティングの権限を移譲したいという今回の要件では、LBC のアプローチは採用を見送りました。
確かに LBC を使えば、TargetGroupBinding などを駆使してクラスタ間で TargetGroup を共有し、1つの ALB から複数クラスタへトラフィックを流すことは可能です。しかし課題となるのは、ルーティングルール(HTTPRoute)の管理場所です。
LBC で ALB を管理する場合、ホストヘッダーなどでルーティングを定義する HTTPRoute を、複数クラスタから分散して 1つの ALB にアタッチ(マージ)させることは困難です。どうしても、どこか1つの「親クラスタ」に HTTPRoute を集約して管理する中央集権的な構成になりがちです。
そうなると、せっかく各クラスタを分割しているのに、「アプリ開発者が自分たちのクラスタ内で HTTPRoute を書き、動的にルーティングを追加・変更する」という自律的な運用ができなくなってしまいます。
私たちが実現したかったのは、「各クラスタの開発者が、他のクラスタや中央管理者を気にすることなく、自分たちの裁量でルーティングをコントロールできる体験」です。これを実現するためには、ルーティングレイヤー自体を各クラスタから切り離し(外出しし)、各クラスタが独立して設定を流し込める現在のアーキテクチャが最適だと判断しました。
では、具体的にこの構成を支える技術要素と、Kubernetes との連携の仕組みについて簡単に解説します。
前提知識:AWS VPC Lattice と Gateway API の関係
本構成の核となる技術、AWS VPC Lattice と Kubernetes Gateway API の関係性について簡単に触れておきます。
AWS VPC Lattice とは
AWS VPC Lattice は、サービス間通信を簡素化・管理するためのフルマネージドなアプリケーションネットワーキングサービスです 。 従来の VPC Peering や Transit Gateway を用いた複雑なルーティング設計を行わなくとも、IP アドレスの重複を気にせず、異なる VPC やアカウントにまたがるサービス間を接続できるのが特徴です 。
Gateway API との連携
EKS 環境において、AWS は AWS Gateway API Controller を通じて VPC Lattice との連携をサポートしています 。 これにより、開発者は AWS コンソールや Terraform を操作することなく、標準的な Kubernetes マニフェスト(Gateway API)を適用するだけで、VPC Lattice のリソースを自動的にプロビジョニングできます 。
具体的には、Kubernetes 上のリソースと AWS VPC Lattice のリソースは以下のようにマッピングされます。
- Gateway: AWS 上の Service Network に対応します。これが論理的なネットワークの境界となります。
- HTTPRoute: AWS 上の Service および Listener Rule に対応します。ここでパスベースのルーティングやホスト名の設定を行います。
- Kubernetes Service: AWS 上の Target Group に対応します。

(出典: AWS News Blog - Amazon VPC Lattice と AWS Gateway API コントローラーのご紹介:Kubernetes Gateway API の実装)
この仕組みにより、開発者は Kubernetes の世界(HTTPRoute)で設定を完結させつつ、裏側では AWS の強力なマネージドサービス(VPC Lattice)の恩恵を受けることが可能になります。
アーキテクチャ詳細:Nginx を Gateway としたアクセス集約
ここで一つ、ネットワーク構成上の課題がありました。 VPC Lattice は仕様上、サービスネットワークに関連付けされた VPC からしかアクセスできません。しかし、社内には AWS の別 VPC や GCP、オンプレミスなど多数のネットワークが存在し、それら全てを Lattice に関連付けるのは管理コストや運用の複雑さの観点から現実的ではありません。
そこで私たちは、Lattice と関連付けた特定の VPC に 「Nginx(リバースプロキシ)」 を配置し、そこを全ネットワークからの「唯一の入り口(Gateway)」とする構成を考案しました。
構成図

ポイント1:2つの ALB で内外両対応
Nginx の前段には、用途の異なる 2つの ALB を配置し、どちらも同じ Nginx ターゲットグループへ転送するようにしています。
- Internal ALB: 社内ネットワーク(TGW 経由、VPN 経由)からのアクセス用。
- Internet-facing ALB: 社外(インターネット)からのアクセス用。認証が通った端末のみアクセス可能。
これにより、社内・社外問わず、同じ仕組みで Preview 環境へ安全にアクセスできるようになりました。
ポイント2:Nginx によるネットワーク制約の回避
本構成の最大の特徴は、Nginx を利用して VPC Lattice のアクセス制限をクリアしている点 です。
通常、VPC Lattice を利用するにはクライアントの VPC を関連付ける必要がありますが、全社的なネットワーク構成変更は容易ではありません。 そこで、すでに Lattice と関連付いている VPC 上で Nginx を稼働させ、リクエストを リンクローカルアドレス (169.254.171.0/24)へ中継させる構成としました。
結果として、社内ネットワークやインターネットからの通信はすべてこの Nginx が引き受け、裏側で Lattice Service Network へと流すことで、シームレスな接続を実現しています。
具体的な実装:基盤の準備から利用まで
今回の構成を実現するために、まずはプラットフォーム側で「VPC Lattice と Kubernetes を繋ぐための準備」を行う必要があります。 一度基盤側でこのセットアップを済ませてしまえば、開発者は個別のインフラ設定を意識する必要はありません。
Step 1: 基盤側の準備 (Platform Engineering)
1. Controller と CRD のインストール
まずは EKS クラスタ上で Gateway API を利用可能にするため、カスタムリソース定義 (CRD) と、AWS が提供するコントローラー aws-gateway-api-controller をインストールします。 (Preview 環境を使う全てのクラスタに設定する必要があります)
AWS の公式ブログにインストール方法が詳しく紹介されていますので、詰まった場合はそちらを参照ください。
ここでは Helm を使用した例を紹介します。
※事前にコントローラー用の IAM ロールの作成と Pod Identity 設定が必要です。
※以下のコマンド内の <VERSION> は、構築時点の最新安定版のバージョン番号に置き換えて実行してください。
# 1. Gateway API CRD のインストール kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/<VERSION>/standard-install.yaml # 2. AWS Gateway API Controller のインストール aws ecr-public get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin public.ecr.aws helm upgrade gateway-api-controller \ oci://public.ecr.aws/aws-application-networking-k8s/aws-gateway-controller-chart \ --version=<VERSION> \ --install \ --namespace aws-application-networking-system \ --set=defaultServiceNetwork=monotaro
この時点で monotaro という VPC Lattice Service Network が作成され、後ほど作成する Gateway と紐付ける準備が整いました。
2. セキュリティグループの設定
VPC Lattice から EKS 上のアプリケーション(Pod)へリクエストを届けるためには、EKS のワーカーノード(または Security Groups for Pods を使用している場合はその SG)に対して、VPC Lattice からのインバウンド通信 を許可する必要があります。
AWS では VPC Lattice 用の マネージドプレフィックスリスト が提供されているため、これをセキュリティグループの許可ルールに追加します。
- タイプ: HTTP (80)
- ソース: カスタム -> プレフィックスリストを選択 (名前が com.amazonaws.region.vpc-lattice のもの)
これを行わないと、Lattice から Pod へのヘルスチェックが通らず、いつまで経っても通信が成功しないため注意が必要です。
3. GatewayClass と Gateway の作成
次に、VPC Lattice と連携するための GatewayClass と、実際のネットワークの入り口となる Gateway リソースを作成します。(こちらも同じ設定を全てのクラスタにする必要があります)
この Gateway リソースを作成すると、コントローラーによって自動的に AWS 上の先ほど作成された VPC Lattice Service Network (monotaro) に関連付けされます。
この関連付けを 1つの Service Network に対して複数のクラスタから行うことで、ルーティング設定の集約が実現できるのです。
apiVersion: gateway.networking.k8s.io/v1beta1 kind: GatewayClass metadata: name: amazon-vpc-lattice spec: controllerName: application-networking.k8s.aws/gateway-api-controller --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: # Gateway名と同じ名前の Service Network が AWS 上に存在しない場合は新規作成され、存在する場合は自動的に関連付けられます # ここでは先ほど controller インストール時に作成された Service Network に紐付けしています name: monotaro spec: gatewayClassName: amazon-vpc-lattice listeners: - name: http protocol: HTTP port: 80 allowedRoutes: namespaces: from: All
4. Nginx の設定:Lattice へのリバースプロキシ
そして、この構成の要である Nginx を用意します。 ここでのポイントは、「受け取ったリクエストを、Host ヘッダーを維持したまま Lattice のリンクローカルアドレスへ流す」 ことです。
http { server_tokens off; # Nginx のバージョン情報を隠蔽 server { listen 80; server_name *.preview.monotaro.com; # ヘルスチェックエンドポイント location = /healthz { return 200 'OK'; add_header Content-Type text/plain; access_log off; # ヘルスチェックのログは除外 } # 全トラフィックを Lattice へ転送 location / { # VPC Lattice へのプロキシ設定 # ※環境に合わせて Lattice のエンドポイント(リンクローカルアドレス等)を指定 proxy_pass http://169.254.171.0; proxy_http_version 1.1; # 【重要】Host ヘッダーを維持して転送 # Lattice はこのヘッダーを見てルーティング先を決定します proxy_set_header Host $http_host; proxy_set_header Connection ""; # クライアント IP の転送設定 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # ALB 経由の接続元 IP を正しく認識させる設定 # ※VPC CIDR (10.0.0.0/8など) を指定してください set_real_ip_from 10.0.0.0/8; real_ip_recursive on; real_ip_header X-Forwarded-For; # ログ設定 (コンテナ標準出力へ) access_log /dev/stdout combined; error_log /dev/stderr crit; } }
169.254.171.0 は VPC Lattice の IPv4 リンクローカルアドレス範囲 (169.254.171.0/24) の先頭です。Lattice の仕様上、この範囲内の IP であれば接続可能ですが、ここでは範囲全体を指す意図で .0 を使用しています。
これで基盤側の準備は完了です。 Lattice Service Network が立ち上がり、Nginx 経由でそこにアクセスできるルートが開通しました。
Step 2: 開発者の利用手順 (Developer)
基盤側の準備さえ整っていれば、開発者の作業は非常にシンプルです。 いつものように Kubernetes のマニフェストを書くだけで、自動的に Lattice へのルーティング設定が追加されます。
開発者が作成するマニフェスト (HTTPRoute)
以下のような yaml を適用すると、コントローラーがそれを検知し、AWS 上の VPC Lattice 設定(Service、 Listener Rule、 Target Group)を自動的に更新します。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app-preview
namespace: my-app
spec:
parentRefs:
# 先ほど基盤側で作成した Gateway を指定
- name: monotaro
namespace: aws-application-networking-system
sectionName: http
hostnames:
# 統一されたドメイン形式(クラスタ名は不要!)
- my-app-pr123.preview.monotaro.com
rules:
- backendRefs:
- kind: Service
name: my-app-service
port: 8080
hostnames にクラスタ名を含める必要がない点にご注目ください。これが実現したかった「抽象化」です。
得られた効果
この構成に移行したことで、以下の効果が得られました。
- 他クラスタへの Preview 環境の提供が可能に(最大の成果)
- 実はこれまで、他クラスタで稼働するアプリの開発チームから「自分たちも Preview 環境を使いたい」という強い要望をいただいていました。しかし、Ingress NGINX に依存した従来の仕組みではルーティング管理が煩雑になってしまうため、提供を断らざるを得ない状態が続いていました。
- 今回、ルーティングを VPC Lattice に「外出し」したことでこの制約が完全に解消され、ついに全社的な展開が可能になりました。
- URL から「クラスタ名」が消え、統一された開発者体験を実現
- Before:
https://my-app-pr123.hoge-cluster-preview.monotaro.com - After:
https://my-app-pr123.preview.monotaro.com(アプリ名のみ) - 元々利用していたチームの体験を損なうことなく、これから新しく利用を始めるチームも、自分のアプリがどのクラスタにいるかを一切意識せずに、全く同じシンプルな使用感で Preview 環境を利用できるようになりました。
- Before:
- 運用負荷を上げずにマルチクラスタ展開が容易に
- 新しいクラスタを追加したい場合、そのクラスタの VPC を Lattice に関連付けし、Gateway API Controller を入れるだけです。
- 入り口となる Nginx や ALB の設定変更は一切不要なため、プラットフォーム側のインフラ運用負荷を上げることなく、利用対象クラスタを横にスケールできるようになりました。
VPC Lattice 導入における制約と今後の課題
今回の移行で多くのメリットを得られましたが、一方で VPC Lattice ならではの制約や、プラットフォームを運用していく上での課題も見えてきました。
【制約】リージョン跨ぎのルーティング抽象化はできない
私たちの環境では、バックエンドのデータベースが大阪に存在しています。そのため、レイテンシーにシビアなアプリケーション向けには、ap-northeast-3 (大阪) リージョンにも EKS クラスタを構築して運用しています。
アカウントや VPC を跨いだルーティングを容易にする強力な VPC Lattice ですが、現状はリージョン単位(Regional)のサービスです。そのため、「東京リージョンと大阪リージョンのクラスタを区別なく、単一の Service Network で完全に抽象化する」といったリージョンを跨いだユースケースは実現できません。現状はリージョンごとに仕組みを用意する必要があり、グローバルな抽象化には至っていません。
【課題】既存 Ingress NGINX 環境からのスムーズな移行
もう一つの大きな課題は、既存環境のマイグレーションです。社内には、これまで Ingress NGINX を前提に構築され、稼働している環境がまだたくさん存在します。
Gateway API (HTTPRoute) を使った VPC Lattice 環境への移行において、ポイントとなるのは「単なるマニフェストの書き換え」ではありません。重要なのは、「既存の振る舞いを VPC Lattice 上でいかに安全かつ確実に再現するか」という検証プロセスです。
Ingress NGINX は非常に機能が豊富で、アノテーションを利用して NGINX の細かなルール(リクエスト/レスポンスヘッダーの書き換えや独自のルーティング制御など)を各環境で柔軟にカスタマイズできる利点がありました。そのため、これまで Ingress NGINX の独自の機能に依存して実現していた複雑な挙動を、新しい VPC Lattice 環境でもシームレスに動作させることができるか、一つ一つ丁寧に検証しながら移行を進める必要があります。
私たちが大切にしている「開発者体験(DX)」を維持するため、開発チームにこの移行の負担をそのまま渡すことは避けたいと考えています。設定の差異や制約をプラットフォーム側でどう吸収・サポートし、スムーズに移行させていくか。これが、プラットフォームエンジニアとしての次なる腕の見せ所だと捉え、着実に準備を進めています。
まとめ
今回は、VPC Lattice と Gateway API を活用して、マルチクラスタ環境における Preview 環境のルーティング課題を解決した事例を紹介しました。
今回の取り組みの最大のポイントは、ルーティングの解決をクラスタ内(Ingress)からクラスタ外(VPC Lattice)へ「外出し」したことにあります。 これにより、バックエンドのアプリケーションがどのクラスタで稼働しているかに依存せず、ルーティング設定を一つの場所に集約することが可能になりました。「場所を意識させない」という抽象化は、今後さらにクラスタが増えていくマルチクラスタ環境において、開発者体験を損なわないための重要な鍵となります。
また、Ingress NGINX の EOL が迫る中、Gateway API への移行は多くの組織にとって避けて通れない道になりつつあります。 今回の事例が、マルチクラスタ環境でのルーティング設計や、Gateway API 導入を検討されている方の参考になれば幸いです。
プラットフォームエンジニアリングとして、今後も「インフラの複雑さを隠蔽し、開発者が本来の業務に集中できる環境」を追求していきたいと思います。