BGP接続の成功とHTTP応答の間にある四つの段階
一言でいうと
BGPピアのEstablishedは、経路交換の接続が確立されたことの証拠であって、ユーザーのHTTPリクエストが成功したことの証拠ではありません。このモジュールは、同じサーバーの前に5つのルーターを置き、ピアの接続、受信経路であるRIB、カーネルの転送経路であるFIB、実際の応答を別々に比較します。異なる失敗を同じtimeoutとしてひとまとめにせず、どの観測がどの主張まで裏付けるのかを説明することが目標です。
なぜ必要なのか
オンコールの画面では、ピア5つのうち4つが緑色です。ところが、サービス担当者は、依然として接続できないと言っています。ここですべてのagentを再起動すると、経路が一瞬変わったり、過去のログが消えたりして、原因をさらに探しにくくなります。まず、運用者が確認した成功と、ユーザーが期待した成功が、同じかどうかを問う必要があります。TCP接続を確立したこと、プレフィックスを学習したこと、カーネルに経路を入れたこと、サーバーの応答を受け取ったことは、それぞれ別の出来事です。
ラボには、AS番号を間違えて書いたピア、接続だけができたピア、経路を学習してもカーネルにインストールしないルーター、正常に転送するルーターがあります。1つを直すときに、ほかの比較が失われないように、各ルーターをLinuxのネットワークネームスペースに別々に配置しました。ネームスペースごとにインターフェースとルーティングテーブルが分離されますが、すべて個人用VMの中にあるので、自宅のルーターやプラットフォームの本番BGPには触れません。以下のアドレスはラボ内部用であり、実際の組織のルーティング設定に、そのまま移してはいけません。
どう動くのか
アドレスを先に読み、接続の方向を合わせる
各ルーターは、vethでVMと直接接続されています。VM側のCiliumのASは65001、ルーター側のFRRのASは65002です。Ciliumのpeer ASNには、相手のFRRの65002が入る必要があります。反対に、FRRのremote-asには、Ciliumの65001が入ります。localとremoteは、ドキュメントに永遠に固定された方向ではなく、今どの機器の設定を読んでいるかによって変わる名前です。
| 比較用ルーター | VMのインターフェースとIP | ルーターのIP | 役割 |
|---|---|---|---|
| empty | bgp-empty, 192.0.2.1 | 192.0.2.2 | 間違ったASを直したあと、アドバタイズなしで接続を維持 |
| rib | bgp-rib, 192.0.2.5 | 192.0.2.6 | PodCIDRを学習するが、カーネルにはインストールしない |
| pod | bgp-pod, 192.0.2.9 | 192.0.2.10 | PodCIDRを学習して、実際のPodへ転送 |
| vip | bgp-vip, 192.0.2.13 | 192.0.2.14 | 選択したServiceのVIPにだけアクセス |
| withdraw | bgp-withdraw, 192.0.2.17 | 192.0.2.18 | 初期のServiceのアドバタイズだけを取り下げ |
各接続ネットワークは/30で、ルーターのインターフェース名はrouterです。FRRはパッシブな接続待ちの状態で、Ciliumが接続を開始します。emptyピアだけ、CiliumのpeerASNが65003と誤って入っています。最初の復旧は、ネットワーク全体を作り直すことではなく、その値だけを65002に合わせることです。sourceInterfaceは、ピアごとにVMのvethを指定するので、別の接続ネットワークのアドレスに接続しようとするミスも区別できます。CiliumのBGPリソースとピア設定で、設定の各方向を確認してください。
PodCIDRのアドバタイズとカーネルの経路は、1つの段階ではない
CiliumBGPAdvertisementのPodCIDRは、ノードが割り当てられたPodのネットワークをアドバタイズします。このラボの全体のプールは10.42.0.0/16ですが、実際にノードに割り当てられた範囲は、CiliumNodeから読み取る必要があります。観測ツールは、実際のPodのIPがその範囲の中にあるかも確認します。/16全体を暗記して書いたり、観測されていないアドレスで表を埋めたりすると、その経路がどのノードのものなのかを区別できません。
ribルーターには、意図的にFRRのbgp no-ribが設定されています。この状態でも、BGP RIBには受信したベストパスが見えることがありますが、カーネルにはその経路がインストールされません。ここで学習者がすべきことは、すべてのルーターを同じように復旧することではなく、この比較状態を保ちながら、なぜHTTPが失敗するのかを説明することです。podルーターでは、同じPodCIDRをアドバタイズしつつ、FRRがカーネルに経路をインストールするようにして、HTTP 200と比較します。2つのルーターが異なる結果を返すのが、正常です。FRR 8.4のno-kernel・no-ribの説明を読むときも、RIBという言葉がどの層のテーブルを指しているのかを確認してください。
FIBでは、宛先プレフィックスだけを見るわけではありません。protocolがbgpか、ネクストホップが該当の接続ネットワークのVMのアドレスか、出力デバイスがrouterかも突き合わせます。デフォルト経路や静的な/32を入れて成功させた答えは、BGPによる転送の成功を証明できません。iproute2のJSONは、/32の宛先をサフィックスのないIPで表示することがあるので、文字列の形ではなく、正規化したネットワークを比較します。
IPの割り当てとServiceのアドバタイズも分ける
LoadBalancer IPAMは、Serviceに使うアドレスを割り当てます。アドレスができたという事実だけで、外部のルーターがそのアドレスを知るわけではありません。ステップ5では、学習者用のプール203.0.113.10–11を作成し、selectedとexcludedのServiceが、それぞれ異なるアドレスを受け取るところまでだけを確認します。2つのbackend selectorは同じで、同じHTTP Podを指しています。この状態で、アドレスだけを見て、アドバタイズまでされたと結論づけてはいけません。LB IPAMのアドレス割り当てと、BGPのアドバタイズを、別々の責務として読んでみてください。
ステップ6では、CiliumBGPAdvertisementのServiceセレクターで、publish=selectedだけを選びます。vipルーターには、selectedの正確な/32がある必要があり、excludedの経路やPodCIDRは追加しません。同じbackendが生きていても、excludedには到達できない必要があります。正常な応答を1つだけ確認すると、誤って2つのServiceをどちらも公開した構成を見逃すので、成功すべき対象と失敗すべき対象を、あわせて試します。セレクターは、PeerConfigがアドバタイズのリソースを選ぶ層と、アドバタイズのリソースがServiceを選ぶ層に、それぞれ存在することも区別する必要があります。
取り下げは、サーバーを止めることではない
取り下げの比較用Service cca-retireは、学習者用のプールとは別に、203.0.113.20をあらかじめ割り当てられています。初期化では、実際のアドバタイズのUIDとServiceのUID、RIB/FIB、HTTP 200を確認したうえで、baseline.jsonを残します。学習者は、その証拠を読み、cca-withdrawのアドバタイズだけを削除します。HTTPサーバーとServiceは残っていて、ピアもEstablishedですが、withdrawルーターの宛先の経路が消えて、接続が失敗する状態を作る必要があります。
Serviceやピアを削除して失敗を作るのは、別の実験です。宛先のサーバー自体がなくなると、アドバタイズの取り下げの効果を切り分けられません。そのため、採点は、初期のPod・ノード・ServiceのUIDが維持されているかを確認し、現在のピア・経路と、新しいHTTPリクエストまで突き合わせます。Ciliumのアドバタイズの変化は非同期で伝達されるので、削除APIが終わった途端に、経路が消えたと仮定せず、観測コマンドが収束を確認したあとで採点してください。BGPの運用ガイドは、コントロールプレーンの変化と、転送の連続性をあわせて扱っています。
同じ接続の失敗も、経路の表とあわせて解釈する
curlの終了コード7だけで、経路がないと断定することはできません。経路があっても、宛先のポートが接続を拒否すると、同じコードが出ることがあります。このラボでは、同じサーバーがpodルーターで正常に応答するという比較と、失敗したルーターのRIB/FIBに宛先の経路がないという観測を、あわせて使います。反対に、ribの比較は、RIBがあってFIBがないので、emptyとHTTPの結果が同じでも、止まっている層が異なります。数字が同じ結果を、1つの原因にまとめないことが、インシデントレポートの核心です。
観測ツールが失敗の理由を要約してくれても、原文を自分で読んでみてください。まずkubectl get ciliumbgpclusterconfig cca-bgp -o yamlで、意図したASとピア、そして状態のconditionを確認し、cilium bgp peersで、接続の状態を確認します。続いて、各ルーターのshow bgp ipv4 unicast jsonと、ip -n cca-pod -j route showを突き合わせます。設定を保存したという証拠だけがあって、ルーターの受信経路がなければ、まだアドバタイズが収束していないか、セレクターが合っていない可能性があります。このとき、Serviceのデプロイから直すと、別の層に手を加えることになります。
状態のconditionでNoMatchingNodeが見えたらノードセレクターを、MissingPeerConfigsが見えたら、参照したPeerConfigの存在を、まず確認します。これらのconditionは原因の範囲を絞り込んでくれますが、conditionが消えたという事実だけで、HTTPの成功が証明されるわけではありません。今回のラボのように、初期のピアとサーバーを保存する課題では、間違った実験を元に戻すときも、何を変更したのかを記録する必要があります。新しいオブジェクトを同じ名前で作ると、UIDが変わり、以前の記録は同じ環境の証拠ではなくなります。
現場での姿
LabHubのレシピ探査では、単一VMのルーターのネームスペースが、実際の外部のマシンと完全に同じではないという点が明らかになりました。socket-LBがconnectの時点でVIPをPodのIPに書き換えると、外部ルーターのVIPの経路を試していると思いながら、実際にはPodの経路を試している可能性がありました。この環境は、最初のインストールからsocketLB.hostNamespaceOnlyを有効にして、vethのパケット処理で比較します。複数のvethのうち、直接のルーティングデバイスも明示します。これは、すべての運用障害に適用できる万能な設定ではなく、測定環境の条件です。socket-LBのバイパスとデバイスの選択の前提もあわせて読んでください。
失敗の名前も、正確に書く必要があります。curlが7で終わり、出力が000なら、このラボでは接続の失敗として記録します。000は、サーバーが送ったHTTPコードではありません。これを、アプリケーションのHTTP 500や、NetworkPolicyのtimeoutと同じものとして書いてはいけません。反対に、HTTP 200も、別のサーバーの応答であれば不十分なので、実際のサーバーは、今回のリクエストの識別子を応答に返します。レポートは、現在のリソースUIDと設定のハッシュ、各ルーターの経路、異なるリクエストの応答を、あわせてまとめます。
次のラボですること
初期環境のアイデンティティを記録し、間違ったピアASを1つ修正します。続いて、RIBだけを持つ比較と、実際のPodへの転送を作成し、アドレスの割り当てと、選択的なアドバタイズを分けます。最後に、初期のServiceのアドバタイズを取り下げたあと、5つの状態が同時に残ったレポートを作成します。設定の変更やPodの再作成のあとは、古いレポートを再利用しません。このラボは、個人用VMのIPv4・単一ノードの比較であり、複数の物理ノードのECMP分散やフェイルオーバーまで検証したと、拡大して言うことはありません。