- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 切り替え動機の整理
- インベントリ作成
- アノテーションマッピング表
- 共存戦略: IngressClass分離
- アノテーション → CRD/ミドルウェア変換
- DNS重み付けベースの移行
- 段階的ロールアウトとロールバック
- 検証: トラフィック比較と合成モニター
- 落とし穴: 動作差
- 移行チェックリスト
- おわりに
- 参考資料
はじめに
Ingress Controllerは一度決めるとなかなか変えないインフラです。ところが2026年に入って切り替えを検討する組織が一気に増えました。最大の理由は、最も広く使われていたingress-nginxが保守モードへ移行し、アノテーションベースの設定注入(snippet)で複数のセキュリティ問題(CVE)が報告され、運用リスクが高まったためです。さらにIngress APIがfrozenとなりGateway APIへ流れが移る大きな潮流も重なりました。
問題は、Ingress Controllerが外部トラフィックの入口であることです。誤って切り替えるとサービス全体が一度に止まります。したがって切り替えは「一気に差し替える」のではなく、「2つのコントローラを共存させながらトラフィックを少しずつ移す」無停止戦略で臨むべきです。
本記事では、切り替え動機の整理から、インベントリ作成、アノテーションマッピング、IngressClass分離による共存、DNS重み付けベースの移行、段階的ロールアウトとロールバック、検証、よくある落とし穴、最終チェックリストまで、実践手順を段階的に扱います。
切り替え動機の整理
まず「なぜ変えるのか」を明確にすることで、移行目標と検証基準が定まります。よくある動機は次のとおりです。
- 保守/セキュリティ: ingress-nginxの保守モード移行、snippet関連CVEへの対応
- Gateway API移行: Ingress frozenの流れに合わせてGateway APIネイティブ実装へ移行
- 機能要件: APIゲートウェイ機能(認証、レートリミット、変換)やマルチテナンシー委譲モデルが必要
- 性能/可観測性: 動的reload、より優れたメトリクス/トレーシング
- コスト: ロードバランサ統合、運用の単純化
動機によって目標コントローラが変わります。セキュリティ/保守が動機ならHAProxyやTraefikのようなIngress互換コントローラへの比較的単純な切り替え、Gateway API移行が動機ならEnvoy Gateway等へのモデル変更が目標になります。
インベントリ作成
切り替えの最初の実務ステップは、現在何がデプロイされているかを漏れなく把握することです。次のコマンドでIngressリソースをすべて収集します。
# 全ネームスペースのIngress一覧
kubectl get ingress -A -o wide
# アノテーションを含む全定義をバックアップ
kubectl get ingress -A -o yaml > ingress-backup.yaml
# 使用中のIngressClassを確認
kubectl get ingressclass
# どのアノテーションが使われているか集計
kubectl get ingress -A -o json \
| jq -r '.items[].metadata.annotations | keys[]' \
| sort | uniq -c | sort -rn
最後のコマンドでクラスタ全体でどのアノテーションがどれだけ使われているかを集計すると、どの機能を新コントローラで必ず再現すべきかが見えます。TLS Secret、cert-manager発行の証明書、外部DNSレコードも併せて目録化します。
アノテーションマッピング表
最も手間がかかる作業が、アノテーションを新コントローラの表現へ移すことです。代表的なingress-nginxアノテーションの対応関係を整理します。
| 機能 | ingress-nginx | Traefik | Contour |
|---|---|---|---|
| パスrewrite | rewrite-target | Middleware(ReplacePathRegex) | pathRewritePolicy |
| SSLリダイレクト | ssl-redirect | Middleware(RedirectScheme) | virtualhost.tls 自動 |
| ボディサイズ制限 | proxy-body-size | Middleware(Buffering) | グローバル設定 |
| レートリミット | limit-rps | Middleware(RateLimit) | グローバル/外部 |
| バックエンドプロトコル | backend-protocol | serversTransport | service protocol |
| 許可リスト | whitelist-source-range | Middleware(IPWhiteList) | authorization/外部 |
表のとおり、ingress-nginxの単一アノテーション一つがTraefikでは別個のMiddleware CRDへ、ContourではHTTPProxyのフィールドやグローバル設定へ散らばります。つまり一対一変換ではなくモデル変換であると認識する必要があります。
共存戦略: IngressClass分離
無停止移行の核心は、2つのコントローラを同時に立ち上げておき、IngressClassで誰がどのIngressを処理するかを明確に区別することです。
外部DNS
│
┌────────────┴────────────┐
│ (重み/レコードで分配) │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ LB (old) │ │ LB (new) │
│ nginx ctrl │ │ traefik ctrl │
└──────┬───────┘ └──────┬───────┘
│ class: nginx │ class: traefik
▼ ▼
┌───────────────────────────────────────────┐
│ 同一 backend Service/Pod │
└───────────────────────────────────────────┘
新コントローラは別個のIngressClassでインストールします。
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: traefik
spec:
controller: traefik.io/ingress-controller
こうすると、既存のIngress(ingressClassName: nginx)はそのままnginxコントローラが処理し、新たに作ったリソース(ingressClassName: traefik)だけを新コントローラが処理します。両者は互いに干渉しないため、安全に並行運用できます。
アノテーション → CRD/ミドルウェア変換
Traefikへ移すなら、ingress-nginxのrewriteアノテーションをMiddlewareへ変換します。
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: strip-api-prefix
spec:
replacePathRegex:
regex: ^/api/(.*)
replacement: /$1
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: web
spec:
entryPoints:
- websecure
routes:
- match: Host(`app.example.com`) && PathPrefix(`/api`)
kind: Rule
services:
- name: api
port: 80
middlewares:
- name: strip-api-prefix
Contourへ移すならHTTPProxyへ変換します。
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: web
spec:
virtualhost:
fqdn: app.example.com
tls:
secretName: app-tls
routes:
- conditions:
- prefix: /api
pathRewritePolicy:
replacePrefix:
- replacement: /
services:
- name: api
port: 80
肝心なのは、変換したリソースを新IngressClassで作り、既存トラフィックと隔離した状態でまず検証することです。
DNS重み付けベースの移行
リソースを新コントローラへ複製し検証まで終えたら、いよいよ実際のトラフィックを移す番です。2つのコントローラはそれぞれのLoadBalancerを持つため、DNSで重みを調整して段階的にトラフィックを移動します。
段階1: new 0% ─ 新コントローラへ合成トラフィックのみ (内部検証)
段階2: new 5% ─ カナリア。エラー率/遅延を比較
段階3: new 25% ─ メトリクス安定を確認
段階4: new 50% ─ 両側均等、負荷を比較
段階5: new 100% ─ 全面切り替え
段階6: old 削除 ─ 安定化期間後に旧コントローラを整理
重み付けDNS(例: ルーティングポリシー)を使うか、2つのLBの前に共有入口を置く方式のいずれも可能です。各段階の間に十分な観察期間を置き、異常時は即座に前の重みへ戻します。
段階的ロールアウトとロールバック
切り替え手順をコマンド単位で整理すると次のようになります。
# 1) 新コントローラをインストール (別個のIngressClass)
helm install traefik traefik/traefik \
--namespace traefik --create-namespace \
--set ingressClass.name=traefik
# 2) 変換したリソースを適用 (新class)
kubectl apply -f converted-routes/
# 3) 新LBのエンドポイントへ直接検証
curl -H "Host: app.example.com" http://<NEW_LB_IP>/healthz
# 4) DNS重みを段階的に上げる (5 -> 25 -> 50 -> 100)
# 各段階でメトリクスを観察
# 5) ロールバックが必要なら、DNS重みを即座に0へ
# リソースはそのままなのでトラフィックだけ旧コントローラへ回帰
ロールバック設計の核心は「旧コントローラとそのIngressを切り替え完了後の安定化期間まで絶対に消さないこと」です。DNS重みだけ戻せば即座に原状復帰するように作ってこそ安全です。
検証: トラフィック比較と合成モニター
切り替え段階ごとに次を比較検証します。
- ステータスコード分布: 2xx/4xx/5xxの比率が旧コントローラと同一か
- 遅延(latency): p50/p95/p99分位の遅延が悪化していないか
- TLS動作: 証明書チェーン、SNI、リダイレクトが同一か
- パスマッチング: rewrite結果のURLがバックエンドの期待と一致するか
合成モニター(synthetic check)で主要パスを両コントローラへ同時に投げ、応答をdiffすると動作差を早期に発見できます。
# 同じリクエストを両LBへ送って応答を比較
for path in / /api/users /login /static/app.js; do
old=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: app.example.com" http://<OLD_LB_IP>$path)
new=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: app.example.com" http://<NEW_LB_IP>$path)
echo "$path old=$old new=$new"
done
落とし穴: 動作差
一見同じ設定でも、コントローラごとに微妙な動作差があり事故が起きます。代表的なものです。
- rewrite正規表現の差: ingress-nginxのrewrite-targetとキャプチャグループの動作がTraefik/Contourと異なります。スラッシュ処理、末尾スラッシュの有無を必ず確認します。
- パスマッチング優先順位: Prefix vs Exact、最長一致優先のルールが実装ごとに異なる場合があります。
- デフォルトタイムアウト: バックエンド応答タイムアウト、idleタイムアウトのデフォルト値が異なります。長いリクエストが新コントローラでだけ切れることがあります。
- ヘッダ処理: X-Forwarded-* ヘッダの追加/上書きポリシーの差。
- ボディサイズ制限: デフォルト制限が異なり、アップロードが新コントローラでだけ阻まれることがあります。
- 正規表現ホストマッチング: ワイルドカードホストの処理方式の差。
これらの差は仕様比較だけでは捉えにくく、上記の合成モニターdiffで実トラフィックを流してみて初めて表れます。
移行チェックリスト
切り替え前後で点検する項目を整理します。
[ ] 全Ingressインベントリのバックアップ (kubectl get ingress -A -o yaml)
[ ] 使用中アノテーションの全数集計とマッピング表作成
[ ] TLS Secret / cert-manager発行証明書の目録化
[ ] 新コントローラを別個のIngressClassでインストール (共存)
[ ] アノテーション -> CRD/Middleware 変換リソースの作成
[ ] 新LBエンドポイントの直接検証 (Hostヘッダで)
[ ] 合成モニターで両側応答をdiff
[ ] DNS重み段階移行 (5->25->50->100)、各段階で観察
[ ] ステータスコード/遅延/TLS/パスマッチング比較の合格
[ ] ロールバックシナリオ(重み0復帰)のリハーサル完了
[ ] 安定化期間後に旧コントローラ/旧Ingressを整理
[ ] 長期的にGateway API移行の経路を計画
おわりに
Ingress Controllerの切り替えは外部入口を変える危険な作業ですが、IngressClass分離で2つのコントローラを共存させ、DNS重みで段階移行すれば無停止で安全に成し遂げられます。核心は3つです。第一に、正確なインベントリとアノテーションマッピング。第二に、一対一変換ではなくモデル変換であるという認識と、動作差に対する合成モニター検証。第三に、DNS重みだけ戻せば即座に復旧するロールバック設計です。
最後に、どうせコントローラを切り替えるついでに、長期的にはIngress frozenの流れを考慮してGateway APIへの移行経路まで併せて計画しておけば、次の大きな切り替えをもう一度経験せずに済みます。
参考資料
- Kubernetes Ingress 公式ドキュメント: https://kubernetes.io/docs/concepts/services-networking/ingress/
- Kubernetes IngressClass ドキュメント: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class
- ingress-nginx アノテーションドキュメント: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/
- Traefik Kubernetes Ingress ドキュメント: https://doc.traefik.io/traefik/providers/kubernetes-ingress/
- Traefik Middleware ドキュメント: https://doc.traefik.io/traefik/middlewares/overview/
- Project Contour HTTPProxy ドキュメント: https://projectcontour.io/docs/main/config/fundamentals/
- HAProxy Kubernetes Ingress ドキュメント: https://www.haproxy.com/documentation/kubernetes-ingress/
- Gateway API 移行(ingress2gateway): https://gateway-api.sigs.k8s.io/guides/migrating-from-ingress/
- cert-manager 公式ドキュメント: https://cert-manager.io/docs/