はじめに
ネットワーク自動化 Advent Calendar 2019 ってことでタイトルはちょっと変えていますが、続き物の記事です。これまでの話については BatfishでL2 Topologyを出せるかどうか調べてみる (1)・調べてみる (2) を参照してください。
以前、Batfish を使ってネットワーク構成を可視化してみよう では Batfish のチュートリアル用サンプルをもとにトポロジの可視化を試しました。が、これが上手くいっていたのは、対象のネットワークが L3 P2P 構成で、L3 トポロジ = L1 トポロジになるからなんですよね。でも L3 P2P な構成は SIer がよくやる Enterprise な構成では一般的ではなく、やはり VLAN + P2MP (Point-to-Multipoint) なネットワークが扱えてほしい。
調べてみる (1)・(2) では Batfish で L2 のトポロジ情報が取れるのかどうか……というのをいくつかのサンプルを元に調べてみました。その結果、Batfish の内部的には L2 の情報をちゃんとつかって L3 隣接関係を計算しているみたいだけど、L2 トポロジの情報を直接的に取り出す API はないみたい、ということがわかりました。仕方がないので、Batfish で取得できる L1/L2/L3 の情報をもとに、強引に L2 トポロジを作ってみます。
「強引に」と書きましたが、物理ネットワーク図 (L1 トポロジ) と NW 機器コンフィグ・パラメータシートをもとに L2 がどうつながっているのかを調べようというのは普段の業務の中でもやっている作業だと思います。いくつかの情報を突き合わせて L2 の構造を調べる面倒な作業ですね。パラメータシートに相当する部分を Batfish で出力して、得られたデータを元に情報の突き合わせ・マッチングをするスクリプトを書き、L1-L2-L3 のトポロジを合成してみます。対象となるネットワークは 調べてみる (2) でつかった Sample 3-5 の 3 パターンです。
できたもの
いくつか特徴的なところを紹介します。実際に動くものは http://netoviz.herokuapp.com/ にあるよ。
- Sample3: http://netoviz.herokuapp.com/model/bf_l2s3.json/nested
- Sample4: http://netoviz.herokuapp.com/model/bf_l2s4.json/nested
- Sample5: http://netoviz.herokuapp.com/model/bf_l2s5.json/nested
Nested view での全体像はこんな感じ。レイヤ間依存関係とかも付けてあるので実際に触ってみてください。1
例 (Sample5/Nested-Bottom-view) 全体像, 元の(手書きの)ネットワーク構成図と同等の内容です。
L1-L3 それぞれレイヤごとにデータの組み立てをやっているので個別に見てみましょう。
L1 Topology
L1 トポロジ情報は Given なのであまりすることがありません。ただ、調べてみる (2) で扱いましたが、Batfish に Link Aggregation (port channel) があるコンフィグを食わせると、L1 topology では port channel の方が使われます。まあ L2 より上から見るときにはこの見え方で特に問題ないんですが、Channel の下には物理インタフェースの情報があるわけでそこをどう扱うか次第ですね。Channel 関連の情報・チャネルと物理インタフェースの対応関係の情報は pybatfish interfaceProperties
query で確認できます。
例 (Sample3/Topology): Layer1トポロジ
L2 Topology
スイッチごとに VLAN がひとつのノード(ネットワーク機器)として見えるような形でモデル化してます。VRF/GRT については、シンプルにスイッチ内にある L3 device (instance) みたいな見え方になるようにしてあります。あとはこのほかのホスト (IPを持っているホスト) を "VLAN" に接続させる形ですね。
例 (Sample4/Nested-Bottom-view): switch1, L1-L3 重ね合わせ
ここで問題になるのがレイヤ間のマッピングをどうやるか。基本的に batfish query はレイヤ単位になっていて、出力される情報はレイヤ内の情報で閉じています。L1~L2, L2~L3 をつなぐような情報が出ないので、どこかでその紐付けをやってあげないといけない。この辺はコンフィグ (パラメータ) 見ながら地道に洗い出す作業とやってることは同じです。
- インタフェースの vlan 情報 (
interfaceProperties
query)- ホスト → 接続先インタフェース(L1トポロジ) → インタフェースのVLAN情報 → VLAN(L2セグメント)へのマッピング
- スイッチの vlan 情報 (
switchVlanProperities
query)- どのVLANがどんなインタフェースを持っているか (L1 インタフェースと L2 の対応関係)
L3 Topology
L3の構成図を出すにあたってまずL3セグメントの情報がほしいのですが、「同じ IP subnet だけど別セグメント」がある前提なので IP アドレスに頼ったグルーピングは使えません。これまでの調査で Batfish で L3 topology をクエリすると、ちゃんと L2 topology を元に L3 node の隣接関係を出してくれることがわかっています。これを利用します。
まずは L3 topology をもとに L3 segment を出します。ひとつのセグメントにノードABCとルータRがある場合、batfish の edges (L3) query では真ん中の表のような形で隣接関係を出力してくれます。これは ABCR が相互に隣接している (P2MP: フルメッシュ) 状態です。
このリンク情報 (from/to 2点ペアの情報) をいったん以下の表にまとめます。
from | To |
---|---|
A | B, C, R |
B | A, C, R |
C | A, B, R |
R | A, B, C |
この表から from/to をあわせてひとつの集合を作ると ABCR すべてのノードについて [A, B, C, R] となり、同じセグメントでフルメッシュでつながっているものについては同じになります。"隣接ノードの集合" が同じものををまとめて、同じ L3 segment に所属している L3 node は何かというのを調べることができます。
コードはこのへんですね。Ruby だと Set で集合作ってから uniq でおわり。
def make_link_group
link_group = {}
@l3_edges.each do |edge|
src, dst = edge.values(%i[src dst]).map(&:to_s)
link_group[src] = [] unless link_group[src]
link_group[src].push(dst)
end
link_group.keys.map { |k| Set.new([k, link_group[k]].flatten) }.uniq
end
この情報を元に、L3 node がどの L2 (VLAN) にマップされていくかをたどるんですが、L1 topology 情報で対向 switch port の vlan 情報を調べるとかいくつかテーブルをまたいで情報のマッチングをしており面倒くさいです。そして Sample5 みたいに同じセグメントだけどスイッチによって使われてる VLAN ID が違うみたいな偏屈なケースを考えるとなおさら突き合わせの手順がややこしい。このあたりのマッチングをどうにか作ると、Sample5 みたいな、スイッチで異なる VLAN ID でひとつのセグメントを構成しているややこしい構成についても、下記の図のようひとつのセグメントとして扱えます。
例 (Sample5/Nested-Top-view): Seg.0 として switch1 Vlan200 と switch2 Vlan300 (異なる VLAN ID の L2 セグメントでひとつのセグメントを形成している)
おわりに
いったん力業でどうにかしてみるのをやってみましたが……これはなかなか難しいですね。
- 冗長化されているものをどう扱うべきか
- 目的次第: 目的によってロジックをかき分けないといけない。今回だと Port-channel とか、結局入れられていないけど HSRP どうするかとか。
- いくつかの query 結果 (表) を横断しながら情報をつなぎ合わせないといけない
- 対向 (接続先) の情報を見ないとわからないことがある (host がどの vlan に所属しているか、とか)
- 結局今回は L1-L3 のトポロジ作るために 6 種類のテーブル (query) をつなぎ合わせてデータ処理をしてます。
- レイヤ間をつなぎ合わせるための情報は知識をベースにロジックを組まないといけない
- "Vlan100" というインタフェースは、そのスイッチの VLAN ID 100 の L2 segment (bridge) とつながっている…とか。どこで上下のレイヤと結びつくのか、というのを考えながらデータ処理のロジックを作る必要がある。
- データがベンダ非依存でもロジックがベンダに依存してしまう。
- Batfish がどういう結果を出してくるのかがある程度見えていないとロジックがかけない
- たとえば、あるホストが直接 802.1Q tag 付けたパケットを吐く (vlan sub interface をもつ) ようなケースを batfish がどう扱うのかわかってないので現状そうした構成はフォローできていないです。使われている構成パターンについて「どう扱われるか」を一通りチェックしておく必要がある。
- これが思いのほかネックで、こういうネットワーク構成に対してはこういうデータが返るよ、みたいなドキュメントも特にないし、予想されるパターンそれぞれに対して返ってくる結果をチェックするみたいな作業がいるんですよね。
以上、力業で L1-L3 トポロジ出力をやってみました。基本的な構成パターンを網羅するためいくつかのサンプルに対してやってみましたが、もうちょっと実際使われているくらいのサイズ・複雑度の環境に対してどうなのかというのを試していかないと、なかなか精度が上がらないだろうなーというところですね。
こういう面倒なところを batfish が吸収してくれるとうれしかったんですが……まあ、ある程度の情報をまとめて出してくれるだけでもやりやすくはなっているかな。batfish は本来、各種 query (test, simulation) を実行するもので、そのために内部で持っている構造化データがある。なので、ベンダ非依存なデータソースとして使う……みたいなところはあまり重視されてないのかな、という印象です。