ノードからは ping が通るのに Pod からは失敗し、オンプレミス側には何も届かない。トンネルとルートは正常で、ワイヤー上の送信元アドレスが正常でない。この二つの違いを分けるのは、パケットがクラスタを出るときに何を名乗るかであり、GKE にはそれについての見解がある。仕組みはマスカレード除外リストであり、読むのに必要なコマンドは一つだけだ。

何かを変える前に有効なリストを読む

GKE は、宛先範囲ごとに Pod のトラフィックを送出時にノードのアドレスへ書き換えるかどうかを決めるエージェントを同梱している。その既定の設定は、三つの RFC1918 ブロック、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 を除外対象として挙げている。そのため Pod からオンプレミスの 10.x ホストへ送ったものは、Pod 自身のアドレスを付けたままノードを出る。ノード自身のトラフィックは書き換えられず、まさにそれがノードの ping が通る理由だ。

意図された設定と実際にインストールされたルールを読むこと。両者は食い違うことがある。kubectl -n kube-system get configmap ip-masq-agent -o yaml はエージェントに何が伝えられたかを示し、kubectl -n kube-system get pods -l k8s-app=ip-masq-agent はそれを適用する何かが動いているかを示す。ノード上では iptables-save | grep -i masq がカーネルに実際に存在するルールを示す。ConfigMap とルールが食い違うとき、疑うべきはネットワークではなくエージェントだ。

キャプチャで送信元アドレスを確認する

リストを変える前にキャプチャする。宛先を一つに絞ったフィルタなら出力は読みやすい: tcpdump -ni any host 10.20.30.40。

それを相手側の報告と突き合わせる。オンプレミス側のキャプチャに Pod のアドレスが写り、応答がなければ話は閉じる。除外リストで非対称性が説明でき、トンネルの障害はない。一つの送信元アドレスについて二つの観測があれば、CNI についての推測はやめられる。

除外を Pod の CIDR に絞る

サポートされている修正は除外を削除せず狭める: Pod の CIDR だけを除外に残し、10.x 空間の残りを外す。そのリストはエージェントの ConfigMap の nonMasqueradeCIDRs にあり、変更は編集とその後のロールアウトになる。広い項目をクラスタの実際の Pod 範囲に置き換え、クラスタ内トラフィックがまだ必要なら他のプライベートブロックは残し、理由がなければ resyncInterval はそのままにする。適用し、エージェントの Pod が再起動したことを確認し、カーネルのルールが変わったことを確認する。

これはクラスタ全体に及ぶ変更で、影響範囲は現実的だ。適用後は、以前は Pod の送信元を保っていたトラフィックがノードへ書き換えられて届くため、それらの Pod アドレスに依存していたものは別の送信者を見ることになる。犠牲になるのは Pod アドレスを名指ししたログ、許可リスト、オンプレミスのファイアウォールルールで、しかも静かに失敗する。変更は修正ではなく挙動の変更として告知すること。

ノードプールが複数あるクラスタでは、影響を受ける Pod が実際に動くノードにエージェントがスケジュールされているか確認する。DaemonSet のないプールは古いルールのままで、ConfigMap を見てもそれは分からない。

もう一つの選択肢とそのコスト

もう一つの方法は Pod アドレスをそのまま保ち、経路側にそれを教える。Pod の CIDR を Cloud VPN のトラフィックセレクタに入れ、オンプレミス側には戻り経路が必要になる。そうすれば応答の行き先が分かる。これは一部のセキュリティモデルが求める Pod の同一性を保つが、相手側が受け入れるルーティングとファイアウォールの範囲を広げる。

取引は可読性と範囲の交換だ。変換すればオンプレミス側にはノードごとに一つのアドレスが見え、許可リストに入れやすく、どの Pod が何を送ったかは隠れる。変換しなければ Pod アドレスが端から端まで保たれ、帰属には良いが、新しい範囲を覚える必要のある経路上のすべての機器には悪い。どちらも Docker や CNI の欠陥ではなく、運用コストを伴うルーティングの判断だ。チームが支払えるほうを選び、その理由を書き残すこと。

検証と切り戻し

同じキャプチャをもう一度実行する。編集後もノード側のキャプチャに Pod のアドレスが出るなら、ConfigMap が何と言おうとルールは変わっていない。事実はワイヤーであり、それ以外は推測だ。

次に逆方向を確認する。これらの変更の半分は外向きしか検証されないからだ。応答が要求を送ったノードまたは Pod に届くこと、アプリケーションが SYN を受け取っただけでなく完了したことを確認する。要求だけが写り応答がないキャプチャは、こちら側のマスカレード問題ではなく、相手側のルーティング問題だ。

切り戻しは以前の ConfigMap を同じ手順で適用し、エージェントが再起動しルールが変わったことを同じように確認する。変更記録には、既知の要求一件について変更前と変更後の送信元アドレスを残す。その一行が、変更が言ったとおりに働いた証拠になる。IP マスカレードエージェントの既定値と ConfigMap のフィールドは GKE の IP マスカレードエージェントのガイド に書かれている。ロールアウト後も送信元アドレスが変わらないなら、そのノードでエージェントが動いておらず、YAML をいくら編集しても直らない。

関連記事