みてね Tech Blog
🚚

Ingress NGINXのretirementに伴うGateway API + Envoy Gatewayへの移行

に公開

こんにちは、『家族アルバム みてね』(以下、みてね)でSREを担当している @kohbis です。

皆さんご存知のとおり、2026年3月にKubernetes Ingress Controllerのkubernetes/ingress-nginxはretirementとなり、すでにリポジトリもパブリックアーカイブされています。

https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/

みてねでも、ingress-nginxを活用していたため、retirementに伴う移行を検討する必要がありました。
ただし、ユーザー向けのルーティングにはIngressは含まれておらず、利用範囲は内部向けに限定されていたこともあり、移行先の選定や切り替え戦略を比較的安全に検討できる状況でした。[1]

このポストでは、そのような前提もありつつ

  • IngressからGateway APIへの移行
  • Gateway API実装としてEnvoy Gatewayの選定

の理由や、移行にあたって意識したポイントをまとめています。

Ingress NGINXのretirementにはすでに対応された方も多いかと思いますが、これから対応される方やGateway APIへの移行を検討されている方の参考になれば幸いです。


既存要件の整理

移行先を検討するにあたり、まずingress-nginxにどのような機能を依存しているかを整理しました。

ingress-nginxに依存する機能は、基本的にannotation(nginx.ingress.kubernetes.io/*)として宣言されているため、既存のIngressマニフェストから機械的に抽出できます。
この特性を活かし、利用中のannotationを一覧化することで、移行先に求める要件を網羅的かつ効率的に洗い出すことができました。
ここが曖昧なまま比較を始めると、移行先の評価が感覚的になりやすいため、最初にこの棚卸しを行ったことは判断の精度を上げるうえで効果的でした。

たとえばみてねでは、oauth2-proxyと連携した外部認証(auth-url/auth-signin)などがannotationとして設定されており、こうした機能が移行先でどう実現できるかが検討のポイントになります。

一方、ホストやパスによるルーティングルールはIngressの標準仕様であり、別のIngress実装であれば当然そのまま利用できます。
Gateway APIでもHTTPRouteで同等のルーティングがサポートされているため、この段階では基本的に考慮の対象外としました。

annotationの棚卸しコマンド例

実際には、各クラスタに存在するIngressからnginx.ingress.kubernetes.io/*を抽出して一覧化しました。
たとえば次のようなコマンドで、利用中のannotationをまとめて確認できます。

kubectl get ingress -A -o yaml | grep "nginx.ingress.kubernetes.io"

このように先にannotationを棚卸ししておくと、単に「何が設定されているか」だけでなく、どの機能にどれくらい依存しているかを把握しやすくなります。


検討した選択肢

既存要件を踏まえたうえで、移行先として次の2つの選択肢を比較しました。

  1. 別のIngress実装に置き換える
  2. Gateway APIへ移行する

Ingress実装の差し替えは、ingress-nginxの既存のannotationベース運用をある程度引き継げるのであれば、短期的な移行コストを抑えやすい選択肢です。
そのため、最初の移行候補としては妥当ではありましたが、実際に調査すると、annotationの互換性だけで既存要件をそのまま低コストで移行できると判断できるIngress実装は見つかりませんでした。[2]
一部機能は追加実装や運用変更が必要になり、結果として移行コストが想定より大きくなることが見えてきました。

一定の移行コストが発生するのであれば、短期的な互換性維持を優先してIngress実装を差し替えるより、将来の標準仕様であるGateway APIに寄せたほうが合理的です。
特に重視したのは次の点です。

  • Ingressの実装差し替えでも、将来的にさらにGateway APIへの移行が必要になる可能性がある
  • Gateway層の共通責務(認証、TLS終端など)とアプリケーション固有のルーティングを分けて設計しやすい
  • 認証やポリシーを含む構成を、より明示的に管理しやすい

短期的な差分の小ささだけを見ると、別Ingress実装への移行は魅力的に見えます。
しかし、中長期の構成管理まで考えると、Gateway APIに移行するほうが判断として一貫していました。

最終的に、みてねでは「Ingress実装の差し替えではなくGateway APIへの移行」を選択しました。
なお、最終的にGateway APIを選ぶ場合でも、別Ingress実装への移行を一度比較しておくことで判断理由を明確にしやすくなります。


Envoy Gatewayを選定した理由

Gateway APIを採用すると決めたあと、次に必要になるのが実装の選定です。
みてねでは最終的にEnvoy Gatewayを採用しました。

https://gateway.envoyproxy.io/

比較対象としてはNGINX Gateway FabricTraefikも含めて検討しました。
もちろん、Envoy Gatewayがすべての軸で一律に優れている、という判断ではありません。
みてねにおける要件との適合度を評価した結果、Envoy Gatewayが最も適していると判断しました。
なお、選定時にはドキュメント上で実現可能かだけでなく、設定の見通し、障害時の切り分け、変更差分の追いやすさといった運用容易性まで含めて評価することを意識しました。

差が出た軸を整理すると、主に次のような比較でした。

  • Gateway APIへの素直さ
    • Envoy Gatewayは、GatewayHTTPRouteを中心にしながら、不足する機能をPolicy系CRDで補う形が取りやすく、標準APIとの関係が比較的わかりやすい実装でした。
    • NGINX Gateway FabricやTraefikでも同等機能を実現できる場面はありますが、みてねの観点では、どこまでが標準でどこからが独自拡張かを整理しやすかったのはEnvoy Gatewayでした。
  • 認証・セキュリティ機能の扱いやすさ
    • 今回は認証要件が重要だったため、この軸の重みが大きくなりました。
    • Envoy GatewayはOIDCやJWT、外部認可の構成を比較的素直に組み立てやすく、oauth2-proxy前提の構成を見直す余地がありました。
    • 一方でTraefikやNGINX Gateway Fabricでは、同様のことが可能でも、追加の設計判断や構成上の工夫がより必要になる印象でした。
  • L7制御の表現力と運用しやすさ
    • タイムアウト、リトライ、レートリミット、認証、トラフィック制御のようなL7機能を、どの程度構造化された形で扱えるかを比較しました。
    • Envoy Gatewayは、単純なルーティングを超える制御をGateway側に寄せやすく、今後の拡張も見据えた運用に向いていると判断しました。
    • 反対に、シンプルなNorth-South trafficのエンドポイントだけが必要なのであれば、この差は小さくなると考えています。
  • 将来的な拡張性と責務分離
    • Gateway層の共通責務(認証、TLS終端など)とアプリケーション側の責務(ルーティング、バックエンド指定)を分けて管理しやすいかも重視しました。
    • Envoy Gatewayは、Gateway / HTTPRoute / Policyの役割分担を比較的明確にしやすく、中長期で構成を育てやすい印象でした。
  • 実装の成熟度と調査しやすさ
    • 最終的には、実現可能性だけでなく、ドキュメントの追いやすさ、障害時の切り分け、構成変更時の見通しも重要でした。
    • Envoy Gatewayは設定例やユースケースごとのガイドが揃っており、調査・検証を進めやすい点でも優位でした。

つまり、今回の比較で決定打になったのは、単一の機能差というよりも、Gateway APIに沿った拡張モデル、認証との相性、L7制御の表現力、そして中長期での運用しやすさを合わせて評価したときのバランスでした。

一方で、多機能なEnvoy Gatewayを採用することはToo Muchでないかという懸念もありましたが、認証要件や将来的な拡張性を考えると、必要な機能を必要なときに使える構成のほうが、結果的に運用コストを下げられると判断しました。

認証要件との相性

今回の移行では、認証付きのエンドポイントをどのように扱うかが重要な論点でした。
ルーティングだけ移せても、認証構成が複雑になるのであれば、運用負荷は下がりません。

Envoy GatewayはOIDC連携を前提とした構成を取りやすく、みてねで利用しているAmazon Cognitoとの組み合わせも検討しやすい実装でした。
その結果、従来oauth2-proxyを中継していたコンポーネントの役割を見直し、構成を単純化できる可能性がありました。

認証はルーティングよりも移行難易度が高くなりやすいため、OIDC、Cookie、リダイレクト、ヘッダー受け渡しといった観点は、実装方式が固まりきる前でも早い段階で検証しておくことが重要です。

https://gateway.envoyproxy.io/docs/tasks/security/oidc/

将来的な拡張性

認証そのものに加えて、将来的にJWTクレームを見てバックエンドにヘッダーを渡す、といった要件が発生する可能性もあります。
そうした拡張に対応しやすい構成を取れることも、選定理由のひとつでした。

https://gateway.envoyproxy.io/docs/tasks/security/jwt-claim-authorization/

IngressからHTTPRouteへの変換例

ルーティングの移行そのものは、IngressのルールをHTTPRouteへ移し替える形になります。
たとえば、もっとも単純なHost + Pathルーティングであれば、次のようなイメージです。

Before: Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  namespace: my-namespace
spec:
  rules:
    - host: app.example.internal
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app
                port:
                  number: 80

After: HTTPRoute

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app
  namespace: my-namespace
spec:
  parentRefs:
    - name: internal-gateway
      namespace: envoy-gateway-system
  hostnames:
    - app.example.internal
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: my-app
          port: 80

もちろん、実際にはこれだけでは足りません。
Ingressでannotationに押し込められていたタイムアウトや認証、IP制限などは、このHTTPRouteとは別にPolicy系リソースへ分解して持たせる必要があります。


実際にハマったポイント

ここまでは選定理由を中心に書いてきましたが、実際に移行を進める中では、ドキュメントを読めばすぐ置き換えられる、という種類の話ばかりではありませんでした。
特に難しかったのは、ingress-nginxのannotationをそのまま別の表現に翻訳するのではなく、そのannotationが実現していた機能を分解して再設計する必要があったことです。

annotationをそのまま移す発想では進めにくい

Ingressではnginx.ingress.kubernetes.io/*のannotationに機能が集約されていたため、最初は「対応する設定項目へ置き換える」発想になりがちでした。
ただ、Gateway APIへ移行すると、HTTPRouteSecurityPolicy、各種Policy、ALB側の設定などに責務が分かれます。

そのため、実際には次のように考え方を切り替える必要がありました。

  • これはルーティングの話なのか
  • 認証の話なのか
  • タイムアウトやボディサイズのような通信制御なのか
  • IP制限のようなポリシーなのか
  • AWSとの接続方式の話なのか

設定の移植というより、要求を機能単位に分解して再配置する作業だった、というのが実感に近いです。

もっとも苦労したのはauth-*系の扱い

特に難しかったのは、auth-urlauth-signinauth-response-headersといった外部認証まわりです。
ボディサイズやタイムアウト、IP制限のような項目は比較的対応方針を立てやすい一方で、認証まわりは既存構成との結びつきが強く、単純な置き換えでは済みませんでした。

みてねではoauth2-proxyを利用した構成が存在していたため、移行先でも同じ構成を維持するのか、それともOIDC連携へ寄せて認証方式そのものを見直すのかを判断する必要がありました。
実際、Envoy Gatewayを選んだ理由のひとつも、CognitoとのOIDC連携を取りやすく、認証構成を単純化できる見込みがあったためです。

一方で、認証パターンは一種類ではなく、既存のoauth2-proxyをすべて一度に置き換えられるわけでもありませんでした。
そのため、「認証基盤を丸ごと刷新する」ではなく、まず対象範囲の認証をどう整理するかを切り分ける必要がありました。

リソースの置き場所は意外と悩む

Gateway APIは柔軟ですが、そのぶんどこに何を書くかは自分たちで決める必要があります。
特に悩んだのは、Gateway / GatewayClassと、HTTPRoute / SecurityPolicyの責務分離でした。

最終的には、Gateway / GatewayClassはコントローラ側、HTTPRoute / SecurityPolicyは各アプリケーション側に置く方針にしました。
ただ、この分け方にするとSecurityPolicyのような設定が複数箇所に現れることもあり、共通化と影響範囲の分離のどちらを優先するかを都度考える必要がありました。

このあたりは、機能の有無よりも、将来どのチームがどの責務で保守するかを意識して決めるのが重要でした。


切り替え戦略

移行先の選定と同じくらい重要だったのが、本番切り替えをどのように安全に進めるかです。

今回の基本方針は、「既存のingress-nginxと新しいGatewayの両方へ、同じホスト名でルーティングできる状態を作る」ことでした。
そのうえで、ALBのリスナールールを用いて、明示的な条件で新経路に振り分けられるようにします。

たとえば、クエリパラメータ?debug=trueのような条件で新経路へ流す方式を取ることで、利用者全体へ影響を出す前に動作確認が可能になります。

なお、クエリパラメータによる経路切り替えは、意図しないユーザーが新経路にアクセスできてしまうリスクがある点には注意が必要です。
みてねでは内部利用かつ限定的な検証フェーズでのみ使用し、検証完了後にはルールを除去する前提だったため問題になりませんでしたが、ユーザー向けのエンドポイントや未検証の経路が外部に露出する構成では、セキュリティホールになり得ます。
そうしたケースでは、IPアドレス制限やカスタムヘッダーなど、外部から容易に再現できない条件を用いるほうが安全です。

ALBリスナールールの条件設定例

実際の表現はTerraform、CloudFormationなどで異なりますが、考え方としては次のような条件分岐です。

優先ルール:
  IF Host header = app.example.internal
  AND Query string debug = true
  THEN Forward -> Envoy Gateway用Target Group

既定ルール:
  IF Host header = app.example.internal
  THEN Forward -> ingress-nginx用Target Group

このようにしておくと、同じホスト名を維持したまま、明示的に新経路だけを踏むことができます。
段階移行の初期では、重みづけよりも「誰が、どうやって、新経路を踏むか」を明確にできる点が重要でした。

この方針を取りやすかった背景には、前述のとおりingress-nginxがユーザー向けルーティングには含まれず、内部利用に限定されていたことがあります。
新旧経路をしばらく共存させながら比較・検証しやすく、段階移行との相性がよい構成でした。

また、この方式には次の利点があります。

  • 新経路を限定的に検証できる
  • 問題発生時に切り戻ししやすい
  • 一括切り替えを避けられる

必要であれば重みづけルーティングも選択肢になりますが、初期段階では明示的な条件で新経路を踏める状態を作るほうが、検証観点を整理しやすいと考えました。

みてねでは内部利用に限定されていたためこの方式で十分でしたが、ユーザー向けのエンドポイントにIngressを置いているケースでは、重みづけルーティングなどによる段階的なトラフィック移行が必要になるでしょう。

切り替え方式の設計においては、新旧共存の期間をどう作るか、どの条件で新経路へ流すか、問題発生時にどこまで即時に戻せるかを事前に決めておくことが重要です。
切り戻し不能な一括移行は避け、新旧を並行させて比較可能な期間を設けることで、移行のリスクを下げることができました。


まとめ

ingress-nginxのretirement対応にあたり、みてねでは以下の流れで移行を進めました。

  1. ingress-nginx固有のannotationを機械的に棚卸しし、移行先に求める要件を明確化
  2. 別Ingress実装への差し替えとGateway APIへの移行を比較し、中長期の拡張性と責務分離を重視してGateway APIを選択
  3. Gateway API実装として、標準への準拠度・L7制御の扱いやすさ・認証要件との相性を評価し、Envoy Gatewayを採用
  4. 新旧経路を共存させ、ALBのリスナールールで段階的に切り替え

みてねではingress-nginxが内部利用に限定されていたため比較的安全に進められましたが、ユーザー向けのエンドポイントにIngressを置いているケースでは、より慎重な段階的移行が必要になるでしょう。
いずれの場合も、依存機能の棚卸し、認証要件の早期検証、切り戻し前提の切り替え設計を先に進めることが、移行をスムーズに進めるうえで有効だと考えています。

実際に移行を進めてみて強く感じたのは、IngressからGateway APIへの移行は単なるマニフェスト変換ではなく、既存構成が担っていた責務を分解し再設計する作業だということです。
特に認証まわりは想定以上に複雑で、早い段階で検証に着手したことが結果的に移行全体をスムーズにしました。

なお、IngressからGateway APIへの単純なマニフェスト変換については、以下のようなツールも存在します。
https://kubernetes.io/blog/2026/03/20/ingress2gateway-1-0-release/

今回の移行はIngress NGINXのretirementという外部要因がきっかけでしたが、Gateway APIへの移行を通じてより柔軟で拡張性の高い構成に進められたことは、(これからの運用次第ではありますが)みてねにとって長期的にプラスだったと考えています。

この記事が、Ingress NGINXのretirement対応やGateway APIへの移行を検討されている方の参考になれば幸いです。

https://team.mitene.us/

脚注
  1. みてねでは、ユーザー向けのルーティングはALBおよびAWS Load Balancer Controllerで完結する構成を取っています ↩︎

  2. 具体的にはF5 NGINX Ingress ControllerやTraefikなどを検討しました ↩︎

みてね Tech Blog
みてね Tech Blog

Discussion