はじめに
お久しぶりです。NEXT事業部のOです。
普段は自社クラウドサービスの運用やインフラ周りを担当しています。
今回は、最近AKS(Azure Kubernetes Service)のネットワーク構築を進める中で、かなり時間を溶かした箇所があったので仕様と対策を共有します。
Kubernetes及びAKSを運用しているチームにとって、2026年はIngressのリプレイス対応を迫られる年になっています。2026年3月にNginx-Ingressのコミュニティサポートが終了し、さらに11月にはAzureマネージドのNginx-Ingress(Web App RoutingのNginx実装)も終了するためです。
Nginxベースで環境を作ってきたため別の手段への移行が必要になり、後継としてGA(一般公開)されたIstioバックエンドの「Application Routingアドオン(Gateway API実装)」の検証を始めました。
Microsoftの公式サポートがある標準構成なので、すんなり切り替えられるかと思ったのですが、Gateway APIの仕様自体に慣れていなかったこともあり手探りの連続でした。結果として「ドキュメント通りに組んでいるはずなのに、なぜか通信がランダムにタイムアウトする」という挙動に悩まされたため、その原因と対策をまとめます。
1. 今回直面した課題
デフォルト状態のままNginxからGateway API環境へ切り替えようとすると、K8sクラスターの仕様やアドオンの内部制約により、主に以下の2つの問題が発生します。
- ソースIPの欠損: 外部ロードバランサー(Azure LB)を経由した時点で、パケットの接続元IPがプライベートIPに変換されてしまい、アプリ側で本来のクライアントIPを識別できなくなる。
- ヘルスチェックの不整合(タイムアウトの原因): Istioの内部プロキシ(Envoy)が配置されていないノードに対してもロードバランサーからの監視パケットが送信されるが、参照するポートがkube-proxyのものになっているため通信が全ノードに振り分けられてしまい、Podがいないノードに当たった通信が遮断(タイムアウト)される。
この問題は、MSのIstio実装がAzure Front DoorやWAFなどのL7プロキシを前段に入れることを想定しているため発生します。
これらを解消するために、設定をいくつか組み合わせて個別に対応する必要がありました。
2. 設定のポイントと仕様
Istioがインターネットから直接通信を受け付ける構成にするために、最終的に以下の3つのアプローチを組み合わせることで解決しました。
① Ingress通信のノード間転送を無効化
デフォルトでは、外部ロードバランサーからの通信を受信したノードから、内部のkube-proxyを経由して内部プロキシ(Envoy)が存在するノードへ転送されます。このノード間転送の際に SNAT(送信元IP変換) が行われるため、パケットの接続元IPがノード内部IPに書き換わってしまいます。
そこで、接続元IPの保持を目的として、Serviceの spec.externalTrafficPolicy を Local に変更します。これにより、kube-proxyは自ノード上に存在するPodに対してのみパケットをルーティングするようになり、ノード間転送とそれに伴うSNATを回避できます。
ロードバランサーから届いたパケットを、他のノードへ転送させずにプロキシのPodへ直接届けるため、ネットワークヘッダー内のクライアントIPが書き換わらずに維持されます。
ドキュメントには、これによってロードバランサーが「内部プロキシ(Envoy)のコンテナが物理的に生存しているノード」だけを正常(Healthy)と判定するようになると記載されています。しかし、実際にはLBが見ているポートがkube-proxyのものになっているため、そのままではすべてのノードが正常と判断されてしまう問題が残ります。
② AzureLBのヘルスチェックポート明示を削除
本来、Azure LBにはServiceで定義された内部プロキシ(Envoy)のヘルスチェックポートを監視させたいところです。
しかし、アドオンの自動生成ロジックが注入するデフォルトのアノテーション(ポート明示)が有効だと、Azure LBはServiceの定義を見ずにkube-proxyのポートを監視してしまいます。
そこで、注入されたAzureのアノテーションを削除・上書きして優先設定を解除します。これにより、Azure LBはServiceで定義された本来のポートを参照し、内部プロキシ(Envoy)Podの生存状態を正しく監視できるようになります。
③ 信頼するプロキシ数の設定
MSのIstio実装のデフォルトでは、直前にL7プロキシが存在することを想定して numTrustedProxies が 1 に設定されています。
しかし、CDNやL7プロキシを挟まずL4ロードバランサーから直接通信を受ける構成の場合、1 のままだと外部から送られてくる偽装可能な X-Forwarded-For ヘッダーを信用してしまいます。
そこで、numTrustedProxies をデフォルトの 1 から 0 に変更します。
これによりEnvoyは外部からの X-Forwarded-For を信用せず、①で維持したTCP接続の送信元IP(クライアントの生 IP)をそのまま正しい接続元として認識できるようになります。
3. 実装マニフェスト(YAML定義)
最終的に検証環境で動作したマニフェストのテンプレートです。
apiVersion: v1
kind: ConfigMap
metadata:
name: gateway-infra-params
data:
service: |
metadata:
annotations:
# ロードバランサーのヘルスチェック先ポート・プロトコル・パスを削除
service.beta.kubernetes.io/port_443_health-probe_port:
service.beta.kubernetes.io/port_443_health-probe_protocol:
service.beta.kubernetes.io/port_443_health-probe_request-path:
spec:
# 接続元IPの透過およびノード間無駄転送の禁止
externalTrafficPolicy: Local
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: app-gateway
spec:
gatewayClassName: approuting-istio
infrastructure:
parametersRef:
group: ""
kind: ConfigMap
# 上記のConfitMapを指定
name: gateway-infra-params
annotations:
# プロキシトポロジーの調整:手前に他のL7プロキシがないため信頼段数を0に設定
proxy.istio.io/config: |
gatewayTopology:
numTrustedProxies: 0
listeners:
- name: https
port: 443
protocol: HTTPS
allowedRoutes:
namespaces:
from: Same
4. 動作確認時のチェックポイント
設定適用後、出力されたプロキシログのパラメータが以下の状態になっていれば正常に動作しています。
x_forwarded_for: 正しいクライアントIPが欠損・改ざんされずに引き継がれていること。response_code:200であり、L7ルーティングおよびバックエンドアプリケーション(Pod)への通信が正常に行われ、タイムアウトが発生していないこと。
おわりに
新しくGAされたAKSのApplication Routingアドオンですが、マネージド機能としての運用のしやすさは感じています。
ただ、Gateway APIという新しい概念を手探りで扱う難しさに加え、従来のNginx環境では意識せず動いていた「ソースIPの透過」などの構成を持ち込もうとした途端、クラウド側の仕様とアドオンの自動生成ロジックの兼ね合いで、今回のような細かい調整が必要になりました。
リプレイスの期限に向けて手探りで検証を進めている方も多いと思いますが、この記録がヘルスチェックの挙動やタイムアウトで時間を溶かす人の参考になれば幸いです。
なお、AKSでの検証からこのブログ随筆までに、LBのヘルスチェックポートについては公式のLearnに追記がされました。併せてそちらもご確認ください。
参考リンク
- アプリケーション ルーティング アドオンを使用して Kubernetes Gateway API を使用してイングレスを構成する
- Kubernetes Gateway API for Azure Kubernetes Service (AKS) を使用して Istio イングレスを構成する
- アプリケーション ルーティング Ingress-Nginx からアプリケーション ルーティング ゲートウェイ API の実装に移行する
- Istio サービス メッシュ アドオンの Gateway API イングレスのトラブルシューティング
| 前の投稿 >

