はじめに
Batfish というネットワーク機器のコンフィグレーションファイルを解析してくれる便利ツールがあります。batfish 自体はクライアント・サーバ型で使うようになっていて、API は REST なんですが、その python wrapper として batfish/pybatfish があります。ここでは batfish の基本的な「管理単位」とそれを pybatfish から扱うための方法を step-by-step に見ていきます。
batfish および pybatfish については過去何回か記事を書いているのですが、私が書いているスクリプトがちょっと古くてですね。ちゃんと現時点版でのあれこれをまとめておこうという意図もあります。
セットアップ
オフィシャルのチュートリアルでは all-in-one 版のコンテナを持ってきて、同梱されている jupyter notebook 上で操作しよう、という流れになっています。それはそれでいいのですが、今回は python script で batfish 操作していくための土台として考えているので、もうちょっとベタな方法で見ていきます。(と言ってもたいした違いはないですが)
all-in-one 版ではなく通常版のコンテナを落とします。jupyter とかが同梱されていないので軽量です。
docker pull batfish/batfish
2022-01-22 時点 latests ではこれくらいサイズが違います。
$ docker image ls | grep batfish
batfish/allinone latest 5ba3c111cc7c 5 weeks ago 1.08GB
batfish/batfish latest bb872fd73111 5 weeks ago 403MB
pybatfish をインストールします。これは pip でインストールできます。(ここでは python/3.8.3, pip/21.3.8 を使っています)
pip install pybatfish
pybatfish 2021.11.4.1095 です
$ pip list | grep batfish
pybatfish 2021.11.4.1095
pybatfish version に関する余談
このあと Welcome to the Batfish documentation! — pybatfish 0.36.0 documentation ってドキュメントを参照しながら書いてるんですが、この title のところに "0.36.0" ってありますよね。
pip install したバージョンと違うので何だろうというので探して見るんですが、pybatfish · PyPI の Release history も Releases · batfish/pybatfish も YYYY.MM.DD な日付ベースのバージョン設定なんですよ。じゃあ 0.36.0 ってどっから来たバージョンなんだと。おそらくこの __init__.py
で定義してあるやつじゃないかと思うんですが (python package 作ったことないのでよくわかってない)、このファイルの last commit が “15 Oct 2019”…。
ということで、この 0.36.0 っていうのがあまり信用なりません。このバージョン番号を指定してもインストールはできるみたいなんですが、もし日付ベースのバージョンになっていなければ、いったん消して入れ直すほうが良いかもしれません。
$ pip install pybatfish==2021.11.4.1095
...省略...
Successfully installed pybatfish-2021.11.4.1095
$ pip list | grep batfish
pybatfish 2021.11.4.1095
分析対象の準備
あらかじめ、batfish で解析したいネットワーク機器の設定ファイルを用意します。batfish は決まったディレクトリ構成に基づいてデータを読むのと、”snapshot” という考え方があるので、まずはそれを押さえておきましょう。
Snapshot
Batfish が分析する対象は、単一の NW 機器コンフィグファイルではなく、複数 (環境全体) の NW 機器コンフィグファイルです。「環境全体」を把握するために、NW 機器コンフィグファイル以外にも、物理 (L1) トポロジ情報、エンドポイント情報等のデータを同梱できます。これらのデータ一式をまとめて “snapshot” と呼びます。
1つの環境(ネットワーク)に対して、例えば設定変更作業の前後や日次など、複数の snapshot を設定できます。後ほど出てきますが、Batfish は下の図のような考え方で “snapshot” を持っています。
- batfish は複数の network を持つ
- network は複数の snapshot を持つ
- Batfish は 1 つの snapshot を指定して分析を実行する
ディレクトリ構成
snapshot ごとにディレクトリを用意し、その中に特定の名前でファイルやディレクトリを設置します。
- snapshot-directory/
- configs/ (必須)
- NW機器コンフィグファイルを置きます。
- ref: Format of vendor and supplemental data
- hosts/
- ホスト (endpoint) 設定ファイル
- iptables/
- ホスト (endpoint) の iptables 設定ファイル
- batfish/
- layer1_topology.json : L1トポロジ情報
- isp_config.json : ISP (インターネット接続) 情報
- runtime_data.json : インターフェスの実行時状態 (runtime state) 情報
- external_bgp_announcements.json : 外部 AS との BGP 情報
- configs/ (必須)
configs 以外のディレクトリはオプショナルです。そこで設置するファイルについては Format of vendor and supplemental data を参照してください。(わたしも全部を使ったことがあるわけではないのであまり細かく説明できません…)
Sample snapshots
…と、いうのを踏まえて今回はサンプルのコンフィグを使います。
-
corestate55/batfish-test-topology
- 別記事で使ったものなので、細かい中身は README に貼ってあるリンクからたどってください。
pybatfish step-by-step
ref: Interacting with the Batfish service — pybatfish 0.36.0 documentation
Batfish (server) の起動
Batfishコンテナを起動します。
$ docker run -d --rm --name batfish -p 9996:9996 -p 9997:9997 batfish/batfish
59cf4ad5ca6dc67ee38e4a5e2f501949c8eca197db5b12e86f3a6cf14aa23dc7
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
59cf4ad5ca6d batfish/batfish "java -XX:-UseCompre…" 10 seconds ago Up 9 seconds 0.0.0.0:9996-9997->9996-9997/tcp, :::9996-9997->9996-9997/tcp batfish
pybatfish (client) の実行
corestate55/batfish-test-topology を clone して、そのディレクトリに移動して作業していきます。
$ git clone https://github.com/corestate55/batfish-test-topology.git
$ cd batfish-test-topology
python を対話モードで起動します。
$ python -i
Python 3.8.3 (default, Jan 1 2021, 16:39:20)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
pybatfish Session を作ります
>>> from pybatfish.client.session import Session
>>> bf = Session()
>>>
Session
の引数を何も指定しないと localhost に接続します。別途 batfish service を起動している場合は host
引数を指定します。(引数なしの場合は Session(host="localhost")
と同等)
snapshot の登録
次は分析したい NW機器コンフィグファイルを登録していくのですが、先に、snapshot を束ねる単位である network を設定しておきます。
>>> # ネットワーク一覧を出してみるけどまだ何もない
>>> bf.list_networks()
[]
>>> # ネットワークを登録する
>>> bf.set_network("batfish-test-topology")
'batfish-test-topology'
>>> bf.list_networks()
['batfish-test-topology']
>>>
>>> # ネットワークの中に snapshot が...まだ入っていない
>>> bf.list_snapshots()
[]
では snapshot を登録しましょう。batfish-test-topology にはいくつかの snapshot が入っています。そのうちの1つを登録してみます。
>>> bf.init_snapshot('./l2/sample3', name="l2_sample3")
'l2_sample3'
>>> bf.list_snapshots()
['l2_sample3']
登録できました。
分析 (query) の実行
snapshot が登録できれば、対象とする snapshot を選択して分析 (batfish query) を実行できます。
ここでは L1 edges (物理リンク情報) を取得してみます。
>>> bf.list_snapshots()
['l2_sample3']
>>> bf.set_snapshot('l2_sample3')
'l2_sample3'
>>> bf.q.edges(edgeType='layer1').answer().frame()
Interface Remote_Interface
0 host13[eth0] switch2[GigabitEthernet1/0/3]
1 host14[eth0] switch2[GigabitEthernet1/0/4]
2 host22[eth0] switch1[GigabitEthernet1/0/6]
3 switch2[GigabitEthernet1/0/4] host14[eth0]
4 switch1[Port-channel1] switch2[Port-channel1]
5 switch2[Port-channel1] switch1[Port-channel1]
6 host23[eth0] switch2[GigabitEthernet1/0/7]
7 switch2[GigabitEthernet1/0/8] host24[eth0]
8 switch1[GigabitEthernet1/0/6] host22[eth0]
9 host21[eth0] switch1[GigabitEthernet1/0/5]
10 switch2[GigabitEthernet1/0/7] host23[eth0]
11 host11[eth0] switch1[GigabitEthernet1/0/1]
12 host12[eth0] switch1[GigabitEthernet1/0/2]
13 host24[eth0] switch2[GigabitEthernet1/0/8]
14 switch1[GigabitEthernet1/0/5] host21[eth0]
15 switch2[GigabitEthernet1/0/3] host13[eth0]
16 switch1[GigabitEthernet1/0/1] host11[eth0]
17 switch1[GigabitEthernet1/0/2] host12[eth0]
この query では pandas dataframe として結果を取得しています(frame()
)。query によっては異なるオブジェクトで返ってきます。それらのデータオブジェクトを扱う際は対応するデータモデルをインポートしておく必要があります。
from pybatfish.datamodel import *
from pybatfish.datamodel.answer import *
from pybatfish.datamodel.flow import *
異なる snapshot の登録・使い分け
l2/sample3/batfish
ディレクトリの layer1_topology.json
を編集して、host11-switch1 間のリンクを消します。(片方向ずつ 2 本セットで双方向になっている点に注意)
diff --git a/l2/sample3/batfish/layer1_topology.json b/l2/sample3/batfish/layer1_topology.json
index 5f482be..5260579 100644
--- a/l2/sample3/batfish/layer1_topology.json
+++ b/l2/sample3/batfish/layer1_topology.json
@@ -1,25 +1,5 @@
{
"edges": [
- {
- "node1": {
- "hostname": "switch1",
- "interfaceName": "GigabitEthernet1/0/1"
- },
- "node2": {
- "hostname": "host11",
- "interfaceName": "eth0"
- }
- },
- {
- "node1": {
- "hostname": "host11",
- "interfaceName": "eth0"
- },
- "node2": {
- "hostname": "switch1",
- "interfaceName": "GigabitEthernet1/0/1"
- }
- },
{
"node1": {
"hostname": "switch1",
この状態で、別な snapshot 名をつけて登録してみましょう。
>>> bf.init_snapshot('./l2/sample3', name="l2_sample3_sw1_gi101_off")
'l2_sample3_sw1_gi101_off'
>>> bf.list_snapshots()
['l2_sample3_sw1_gi101_off', 'l2_sample3']
同様に L1 edges を取得してみます。
>>> bf.set_snapshot('l2_sample3_sw1_gi101_off')
'l2_sample3_sw1_gi101_off'
>>> bf.q.edges(edgeType='layer1').answer().frame()
Interface Remote_Interface
0 host13[eth0] switch2[GigabitEthernet1/0/3]
1 host14[eth0] switch2[GigabitEthernet1/0/4]
2 host22[eth0] switch1[GigabitEthernet1/0/6]
3 switch2[GigabitEthernet1/0/4] host14[eth0]
4 switch1[Port-channel1] switch2[Port-channel1]
5 switch2[Port-channel1] switch1[Port-channel1]
6 host23[eth0] switch2[GigabitEthernet1/0/7]
7 switch2[GigabitEthernet1/0/8] host24[eth0]
8 switch1[GigabitEthernet1/0/6] host22[eth0]
9 host21[eth0] switch1[GigabitEthernet1/0/5]
10 switch2[GigabitEthernet1/0/7] host23[eth0]
11 host12[eth0] switch1[GigabitEthernet1/0/2]
12 host24[eth0] switch2[GigabitEthernet1/0/8]
13 switch1[GigabitEthernet1/0/5] host21[eth0]
14 switch2[GigabitEthernet1/0/3] host13[eth0]
15 switch1[GigabitEthernet1/0/2] host12[eth0]
>>>
layer1_topology.json
で指定したとおり、host11-switch1 間のリンクが 2 本 (片方向ずつ、双方向) 見えなくなりました。snapshot l2_sample3
と同じNW機器コンフィグファイルを使用していますが、L1 topology が違う別な snapshot として扱うことができています。
おわりに
pybatfish の基本的な使い方をまとめてみました。pybatfish v2021.11.04 tag のリリースノートに “examples and documentation have been updated to use Session
s” とあるので、おそらくこのへんから Session
中心の書き方に変わっています。
古い書き方は legacy_docs の方に残してあるみたいなんですが、Session
が明示的に出てこなかったりします。ディレクトリ構成も追加データの json file が batfish
ディレクトリじゃなくて snapshot ディレクトリ直下にあったりするかも。混乱しないように注意してください。
このあたりの管理方法等は変わってきているので、これから使う方はいま一度ドキュメントを参照するようにしてください。