はじめに
Kubernetesにおいて、ノードにDaemonSetとして配備したエージェントPodとアプリケーションPodの間で、低遅延な通信を実現するために hostPort をバインドするアーキテクチャを採用するケースがあります。この構成において、GKEコントロールプレーンの自動アップグレードと、それに伴うCNI(Calico)の更新が引き金となり、エージェントPodへの通信が不通になる事象が発生しました。
本件はGKE環境で発生したものですが、同じくCalicoや類似 CNI を利用するEKS(AWS)やAKS(Azure)の環境でも同様に起こり得る、iptables転送ルールの不整合に関する事象です。原因の特定から復旧、対策に向けた検証プロセスを整理します。
テレメトリの突然の消失
プラットフォームチームにおいて、共通テレメトリ収集エージェント(以下、Telemetry Agent)のバージョンアップ作業を実施しました。本番環境(東阪マルチリージョン)において適用を完了した直後、アプリケーションPodから出力される分散トレースやメトリクスが一切収集されなくなる状況が発生しました。
エージェントの設定やマニフェスト自体には異常が見当たらず、まずは切り戻し手順に沿ってエージェントのバージョンを以前の状態へ復帰させました。しかし、切り戻しを完了しても通信は不通のままでした。さらにログを確認すると、なぜか「特定のノード」でのみ正常にテレメトリが収集されており、その他の大多数のノードではパケットが宛先に到達していないという状況が生じていました。
アプリケーションPodからTelemetry Agentへの通信は、サイドカーコンテナの挿入に伴うオーバーヘッドを回避するため、ノード側の hostPort を介してホストネットワーク上で待ち受けるエージェントに直接転送する設計をとっていました。
コントロールプレーン更新に伴うネットワーク名の変更
私たちのシステムでは、GKE(Google Kubernetes Engine)の自動アップグレード機能を有効にしています。これはコントロールプレーンやノードのパッチ適用などの運用保守を省力化するための選択ですが、今回の不通事象は、その自動アップデートに伴う内部仕様の変更(過渡状態における不整合)が要因となりました。
不通が発生した要因を調べるため、インシデントの発生前後に実施されたクラスタ全体の変更履歴を追跡しました。その結果、事象発生の約2週間前に、Google Cloudのマネージド自動アップグレードによってGKEコントロールプレーン(Masterノード)の更新が行われていたことが分かりました。
コントロールプレーンの更新に伴い、kube-system ネームスペースで稼働している calico-node も自動的にアップグレードされていました。このCalicoのアップグレード前後で、CNIが利用する内部のネットワーク名が以下のように変更されていました。
- 変更前:
k8s-pod-network - 変更後:
gke-pod-network
この時点では既存のエージェントPodは旧ネットワーク名(k8s-pod-network)のルールに基づいてホスト上のポートにバインドされ、正常に通信を継続していました。しかし、その後に私たちが実施したエージェントPodのアップデートによって、古いエージェントPodが削除され、新しいIPアドレスを持つPodへと入れ替わりました。
ネットワーク名が変わった状態でPodの再作成が行われたため、CNI(Calico)はホストのiptablesに登録されていた「古いエージェントPodへのポートフォワーディングルール」を正しく参照・削除できなくなりました。その結果、古い転送ルールがクリアされないままiptables内に置き去りにされる状況が作られました。
iptablesダンプの比較が示したルーティングの齟齬
不通が発生している原因を絞り込むため、正常に通信できているノードと、通信が遮断されているノードとの間で、ホストのカーネルパラメータやiptablesルールの比較を行いました。
異常が発生しているノードで iptables -t nat -S を実行し、NATテーブルのチェーン情報を取得したところ、CNI-HOSTPORT-DNAT(ホストのポートとPodのIPアドレス間のDNATを処理するチェーン)に以下の齟齬が見つかりました。
# 異常ノードでのダンプ例
-A CNI-HOSTPORT-DNAT -p tcp -m comment --comment "dnat name: \"gke-pod-network\" id: \"...\"" -m tcp --dport 8126 -j DNAT --to-destination 10.120.2.15:8126
-A CNI-HOSTPORT-DNAT -p tcp -m comment --comment "dnat name: \"k8s-pod-network\" id: \"...\"" -m tcp --dport 8126 -j DNAT --to-destination 10.120.2.9:8126
すでに削除されて存在しない古いエージェントPodのIPアドレス(上記例における 10.120.2.9)に対する転送ルール(k8s-pod-network 経由)が、iptables上に残存していました。
アプリケーションPodからノードの hostPort(例:TCP/8126)宛てに送られたパケットは、iptablesの評価順序に従い、この残存していた古いIPアドレス宛てにDNAT(宛先変換)されていました。結果として、パケットは存在しないインターフェースへとルーティングされ、接続タイムアウトを引き起こしていました。
正常に動いていたノード(prod-gke-node-primary-abc12345)については、GKEコントロールプレーンのアップグレード後に新規でプロビジョニングされたノードであり、最初から新しいネットワーク名(gke-pod-network)のiptablesルールのみが生成されていたため、本問題の干渉を受けていませんでした。
検証環境での再現と本番クラスタへのパッチ適用
この事象がCNIのバージョンアップとネットワーク名変更の組み合わせによるものか確証を得るため、テスト環境(QA・Stage環境)において同一の状況を擬似的に再現させました。
テスト環境でクラスタ内のCNI設定をアップグレードした後に、Telemetry AgentのPodを再起動したところ、本番環境と全く同一の iptables 重複ルールおよび通信の不通が再現されました。
この検証を踏まえ、本番環境の全ノードに対して以下の手順で復旧パッチの適用を行いました。
- 対象ノードにSSHログイン、または特定権限を持つメンテナンス用Podを稼働させ、iptables内の該当チェーンを flush(クリア)する。
sudo iptables -t nat -F CNI-HOSTPORT-DNAT - CNIルールをクリアした状態で、Telemetry AgentのPodを順次再起動する。
- 再起動に伴い、CNI(Calico)が現在の正しいネットワーク名(
gke-pod-network)に基づいた新規のDNATルールのみをiptablesに正しく再構築する。
対象nodeに他のhostPort利用workloadが存在しないことを確認したうえで、この手順を東京および大阪リージョンのプロダクションクラスタの全ノードに対して実施したところ、各アプリケーションPodからの分散トレースおよびメトリクスの送信が即座に復旧しました。
差分の時間差と今後の備え
本件から得た重要な教訓は、検証環境(Stage環境等)と本番環境(Prod環境等)における「変更適用タイミングの時間差」のコントロールです。
当時、Stage環境においてエージェントの検証を行ってから本番環境へデプロイするまでに約3週間のタイムラグを設けていました。この期間の間に、本番環境のGKEマスターノードの自動更新(Calicoの変更)が先に実行されてしまい、検証環境と本番環境の間でインフラの足回りに不整合が生じていました。
これらの教訓から、私たちはプラットフォームチームとして以下のアクションを実行することにしました。
-
環境間バージョン差分の厳格なチェック
インフラや共通ミドルウェアのリリース前に、検証環境と本番環境におけるKubernetesバージョンおよびCNIの差分を自動チェックし、バージョンが異なる場合は、本番リリース前に検証環境を同じバージョンへ引き上げて追加の疎通リハーサル(Podの再起動検証など)を必須とする。 -
段階的リリースおよびデプロイタイミングの制御
東京リージョンと大阪リージョンなどのマルチリージョン環境へのリリースは、必ずタイミングをずらし、影響を最小限にとどめる段階的デプロイを徹底する。これにより、インフラ変更に伴う万が一の影響範囲を特定エリアに限定する。 -
自動更新の追跡とキャッチアップの仕組み化
マネージドKubernetesにおけるコントロールプレーン自動アップグレードのスケジュールおよびリリースノート(特にCNIの変更)を追跡するタスクを運用フローに組み込み、プラットフォーム変更を事前にチーム内で共有する。 -
hostPort依存の最小化検討
hostPort を使わずにnode-local性を維持する方式として、DaemonSetをバックエンドとしたClusterIP Serviceに internalTrafficPolicy: Local を設定する構成などを検証する。これにより、アプリケーションからは安定したService IP/DNSを利用しつつ、同一node上のTelemetry Agentへルーティングできる。
まとめ
GKE、EKS、AKSといったマネージドKubernetesサービスが提供する自動アップグレード機構は、コントロールプレーンやノードを安全かつ最新に保つための標準的な選択肢です。一方で、インフラのライフサイクル自動化(クラウドプロバイダー側の更新)と、個別のエージェント適用(ユーザー側のリリース)との間にタイムラグが生じる場合、今回のような「内部仕様の変更に伴うネットワークルールの不整合」という境界値の問題に直面することがあります。
クラウドプロバイダーが提供する自動化の利便性を享受しつつ、自社のリリースプロセスとの時間差や不整合を制御するガードレールを整備することが、信頼性の高いコンテナ基盤の維持につながります。