Batfishは、ルータやファイアウォールの設定ファイルを読み込み、そのネットワークでパケットがどこへ届き、どのACL行に当たり、BGPセッションが張れるかを計算するオープンソースの検証ツールです。実機にもエミュレータにも接続しないため、変更案の設定ファイルを用意するだけで、本番に入れる前に「SSHを全開放してしまう」「上の行に隠れて効かないACL」を見つけられます。この記事では2026年8月27日公開のv2026.08.27とpybatfish 2026.9.17.3748で実行した出力を載せながら、導入からCIへの組み込みまでを説明します。
なお、英語で batfish と呼ばれる魚(アカグツやツバメウオの仲間)の話ではなく、ネットワーク構成解析ツールの記事です。
まとめ:Batfishでできることと導入の要点
- 設定ファイルからベンダー非依存のモデルを作り、到達性・ACL・経路・BGPを計算する。実機への接続は不要
- 最新はBatfish v2026.08.27(2026年8月27日)とpybatfish 2026.9.17.3748(2026年9月18日)。pybatfishはPython 3.10以上、サーバはJDK 21以上が必要
- 起動は
docker run -p 9996:9996 batfish/batfishで足りる。9997番の公開はpybatfish 2022.9.7以降不要 - 設定ファイルはスナップショット直下の
configs/に置く。フォルダを直下に置かないと読み込まれない - ACLの死に行は
filterLineReachability、変更前後のACL差分はcompareFilters、到達性はtracerouteで調べる pybatfish.client.assertsをpytestから呼べば、危険な変更でCIを失敗させられる
以下、仕組み・導入・使い方・向かない場面の順に説明します。
Batfishの仕組み:設定ファイルから挙動を計算する流れ
Batfishは、Cisco IOSやJuniper Junosなどの設定文をベンダーごとのパーサで読み、共通のデータモデルへ変換します。そのモデルからルーティングプロトコルの収束結果(データプレーン)を計算し、利用者が投げた「質問(question)」に表形式で答えます。GitHubの batfish/batfish はApache License 2.0で公開され、2026年9月28日時点のGitHub APIが返すスター数は1,488です。
処理は次の4段階です。
- スナップショット(設定ファイル一式)をアップロードし、ベンダーを自動判定してパースする
- パース結果をベンダー非依存のモデルへ変換する。未対応の構文はここで警告になる
- OSPF・BGP・静的経路などを収束させ、各機器のRIBと転送表を計算する
- 質問を受けて、到達性・ACL評価・セッション状態などをpandasのDataFrameで返す
計算対象は論理的な振る舞いです。パケットを実際に流すわけではないので、帯域や遅延、機器のソフトウェア不具合は結果に現れません。
仮想ラボ・NetBox・Ansibleとの役割分担
仮想ルータを立てて実際に動かすラボは、ベンダーOSそのものの挙動を確かめられる代わりに、イメージの入手と起動に手間がかかります。Batfishは機器イメージを起動せず設定ファイルだけで計算するので、変更のたびに毎回回す検査に向いています。
管理系のOSSとは担当範囲が違います。NetBoxはIPアドレスや機器の「あるべき姿」を記録する台帳で、Ansibleは設定を機器へ配る実行役です。Batfishはその間に入り、配る前の設定が意図どおりに振る舞うかを判定します。台帳から設定を生成し、Batfishで検査し、合格したものだけをAnsibleで適用する、という並べ方が自然です。
最新版v2026.08.27の変更点と動作要件
Batfishは日付を版番号にしており、2026年9月28日時点のGitHub Releasesの最新は2026年8月27日のv2026.08.27です。pybatfishの版と対応PythonはPyPIで確認できます。その前のv2025.07.07から約1年あいたため、変更の幅が大きくなっています。
| 項目 | 2026年9月28日時点 |
|---|---|
| Batfish(サーバ) | v2026.08.27(ビルド2026.08.27.3685) |
| pybatfish | 2026.9.17.3748(2026年9月18日) |
| Python | 3.10以上(分類上は3.10〜3.14) |
| Java | JDK 21以上(推奨はJDK 25) |
| Dockerイメージ | linux/amd64・linux/arm64 |
| ライセンス | Apache License 2.0 |
リリースノートのうち、運用に響く変更は次のとおりです。
- Nokia SR OS(MD-CLI)に新たに対応。長く要望が多かったベンダーで、インターフェース、OSPF、IS-IS、BGP、VPRNまで変換する
- Broadcom FASTPATH(管理プレーンから)と、Azureの実験的サポートを追加
- Dockerイメージがamd64とarm64のマルチプラットフォームになり、Apple Silicon機でもネイティブに動く
- pybatfishにベータ版のMCPサーバが入った。
pip install 'pybatfish[mcp]'で導入しbatfish-mcpで起動すると、36個のツールをAIエージェントから呼べる。ツール名と引数は今後変わる可能性がある - 破壊的変更として、
routes・bgpRib・evpnRibの結果から旧Next_Hop_IP・Next_Hop_Interface列が消えた。Next_Hop列を参照するスクリプトへ書き換える必要がある
既存のスクリプトで Next_Hop_IP を読んでいる場合、サーバだけ更新するとKeyErrorで止まります。サーバとpybatfishは同じ時期の版へまとめて上げてください。
対応ベンダーと設定ファイルの取り方
pybatfishの公式ドキュメント(formats)が挙げる対応ベンダーは、A10、Arista、AWS、Azure、Cisco、Check Point、Cumulus Linux、F5 BIG-IP、Fortinet、Juniper、Palo Alto Networks、SONiCです。v2026.08.27で加わったNokia SR OSは、2026年9月28日時点でこの一覧にまだ載っていません。ベンダーごとに、どのコマンドの出力を渡すかが決まっています。
| ベンダー | 渡す内容 | 置き場所 |
|---|---|---|
| Cisco IOS・IOS-XE・IOS-XR・NX-OS・ASA | show running-config | configs/ |
| Arista EOS | show running-config | configs/ |
| Juniper Junos | show configuration | display set(階層形式も可) | configs/ |
| Fortinet FortiOS | show | configs/ |
| Palo Alto | Panoramaのset形式または機器単体のshow出力 | configs/ |
| SONiC | config_db.json・frr.conf | sonic_configs/機器名/ |
| AWS | describe系APIのJSON | aws_configs/ |
| Azure | リソースのJSONビュー | azure_configs/ |
Junosは display set の平坦な形式が基本ですが、階層形式の設定も受け付け、内部でset形式へ変換します。機器から取った設定には版を示す行が含まれるのでベンダーは自動判定されますが、テンプレートから生成した設定では判定に失敗することがあります。その場合はファイル先頭に !RANCID-CONTENT-TYPE: arista のような行を足してベンダーを明示します。IOS系の違いはIOS-XRとCisco IOS・IOS-XEの違いを参照してください。
導入手順:Dockerでサーバを起動しpybatfishから接続
Batfishはサーバ(Java)とクライアント(Python)に分かれています。サーバはDockerで動かし、手元のPythonにpybatfishを入れて接続するのが公式の手順です。
サーバの起動とポート
Docker Hubには2種類のイメージがあります。batfish/batfish はサーバ単体、batfish/allinone はサーバにpybatfishとサンプルのJupyterノートブックを同梱したものです。検証を自動化するならサーバ単体で十分です。
docker run -d --name batfish \
-v batfish-data:/data \
-p 9996:9996 \
batfish/batfish
pybatfish 2022.9.7以降は9996番(V2 API)だけで全機能を使えます。batfish/batfish イメージ自体もEXPOSEしているのは9996番だけです。README には9997番を公開する例が残っていますが、現行の組み合わせでは不要です。ノートブックを試すときは batfish/allinone を使い、-p 8888:8888 を足してブラウザから開きます。ノートブックはコンテナ内で同居するサーバへ接続するので8888番だけで動きますが、ホスト側のPythonからpybatfishで接続するには9996番の公開が別に必要です。
起動が済んだかは curl http://localhost:9996/v2/version で確かめられます。v2026.08.27のサーバは {"Batfish":"2026.08.27.3685","api_version":"0.0.0"} を返します。MacでDocker Desktopを使わずに動かす場合はColimaでも起動できます。
pybatfishのインストール
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pybatfish
最新のpybatfishはPython 3.10未満に対応していません。Python 3.9の環境でインストールすると、3.9で入る最後の版である2025.7.7.2423が選ばれ、v2026.08.27のサーバと版がずれます。
Batfishサーバの推奨メモリ容量
公式のシステム要件は、サンプルノートブックを動かすPCに8GB、実ネットワークを処理するサーバに最低32GBのRAMを推奨しています。batfish/batfish イメージのJavaはコンテナが使えるメモリの80%までをヒープに使う設定(-XX:MaxRAMPercentage=80)で起動します。Dockerの --memory は上限の指定なので、ホストや、Docker Desktop・Colimaの仮想マシンに割り当てたメモリ自体が足りなければ、指定しても使える量は増えません。先に仮想マシン側の割り当てを確認してください。
スナップショットのフォルダ構成
Batfishは「スナップショット」というフォルダ単位で設定を読み込みます。ベンダー設定はスナップショット直下の configs/ に置くのが決まりで、フォルダの直下にファイルを並べただけでは読み込まれません。configs/ の中はサブフォルダを切っても再帰的に読まれ、ベンダーが混在していても構いません。
snapshot/
├── configs/ # 機器の設定ファイル(必須)
│ ├── edge1.cfg
│ └── core/core1.cfg
├── hosts/ # 端末をモデル化するJSON(任意)
├── aws_configs/ # AWSのdescribe結果(任意)
├── batfish/
│ ├── layer1_topology.json # 物理結線(任意)
│ ├── isp_config.json # ISPのモデル(任意)
│ └── runtime_data.json # IF速度など実行時情報(任意)
└── external_bgp_announcements.json
物理結線を示す layer1_topology.json を渡さない場合、BatfishはIPアドレスから隣接を推定します。たとえば 192.168.1.1/24 と 192.168.1.2/24 を持つインターフェースは隣り合っているとみなします。同じアドレス空間を複数の場所で使い回している構成や、リンクローカルアドレスで接続している構成ではこの推定が働かないため、結線ファイルを渡す必要があります。
pybatfishによるACLの誤り検出と変更差分の検証
ここからは、1台のCisco IOSルータを例に、よくある誤りを検出します。インターネット側から443番だけを許可し、SSHは管理用の198.51.100.0/24からだけ通すつもりで書いた設定です。
hostname edge1
!
interface GigabitEthernet0/0
ip address 203.0.113.1 255.255.255.0
ip access-group INBOUND in
!
interface GigabitEthernet0/1
ip address 192.168.10.1 255.255.255.0
!
ip access-list extended INBOUND
permit tcp any any eq 443
permit tcp any host 192.168.10.10 eq 443
deny tcp any host 192.168.10.10 eq 22
permit tcp 198.51.100.0 0.0.0.255 host 192.168.10.10 eq 22
deny ip any any
この内容を base/configs/edge1.cfg として保存します。
読み込み結果の確認:fileParseStatus・initIssues
最初に、全ファイルがパースできたかを確かめます。ここで失敗や警告が出ている状態で後の質問に答えさせても、結果は信用できません。
from pybatfish.client.session import Session
bf = Session(host="localhost")
bf.set_network("edge-demo")
bf.init_snapshot("base", name="base", overwrite=True)
print(bf.q.fileParseStatus().answer().frame())
print(bf.q.initIssues().answer().frame())
File_Name Status File_Format Nodes
0 configs/edge1.cfg PASSED CISCO_IOS ['edge1']
Empty DataFrame
Columns: [Nodes, Source_Lines, Type, Details, Line_Text, Parser_Context]
StatusがPASSEDで、initIssuesが空なら読み込みは正常です。未対応の構文があるとStatusがPARTIALLY_UNRECOGNIZEDになり、initIssuesに該当行が並びます。
効かないACL行の検出:filterLineReachability
df = bf.q.filterLineReachability().answer().frame()
print(df[["Unreachable_Line", "Blocking_Lines"]].to_string())
Unreachable_Line Blocking_Lines
0 permit tcp 198.51.100.0 0.0.0.255 host 192.168.10.10 eq 22 ['deny tcp any host 192.168.10.10 eq 22']
1 permit tcp any host 192.168.10.10 eq 443 ['permit tcp any any eq 443']
管理用のSSH許可は、その前の deny にすべて吸われて一度もマッチしません。つまり管理者もSSHできない設定です。2行目の443番許可は1行目と重複しているだけなので実害はありませんが、ACLの整理対象になります。目視のレビューでは見落としやすい行の順序の誤りを、Batfishは行単位で示します。
古い解説記事には bf.q.aclReachability() という書き方が残っていますが、現行のサーバにこの質問はなく、呼ぶと AttributeError: 'Questions' object has no attribute 'aclReachability' になります。同じ目的には filterLineReachability を使います。
変更前後の比較:compareFilters・searchFilters
次に、誰かが「SSHが通らない」と言われて、1行目の直後に permit tcp any any eq 22 を足した変更案を cand/configs/edge1.cfg として用意したとします。変更前後を比べると、何が変わるかがわかります。
from pybatfish.datamodel.flow import HeaderConstraints
bf.init_snapshot("cand", name="cand", overwrite=True)
print(bf.q.compareFilters(filters="INBOUND")
.answer(snapshot="cand", reference_snapshot="base").frame())
ssh = HeaderConstraints(srcIps="0.0.0.0/0", dstIps="192.168.10.10",
applications=["ssh"])
print(bf.q.searchFilters(headers=ssh, filters="INBOUND", action="permit")
.answer(snapshot="cand").frame())
compareFilters は、新しい2行目が旧設定の deny tcp any host 192.168.10.10 eq 22 と deny ip any any の2行と異なる判定をすると返しました。searchFilters は、条件に当てはまる具体的なパケットを1つ探して見せます。変更案では 8.8.8.8:49152->192.168.10.10:22 のSSHが permit tcp any any eq 22 で許可されました。管理者を通すつもりの変更で、このACLは送信元を問わずSSHを許可するようになっています。経路まで含めて外部から届くかどうかは、traceroute や reachability で別に確かめます。
経路とBGPの確認:routes・bgpSessionStatus・traceroute
ルーティングを含むネットワークでは、次の質問をよく使います。
| 目的 | 質問名 |
|---|---|
| 読み込みの確認 | fileParseStatus・initIssues・parseWarning |
| ACLの検査 | filterLineReachability・searchFilters・testFilters |
| ACLの変更比較 | compareFilters |
| 到達性 | traceroute・reachability |
| 変更前後の到達性差分 | differentialReachability |
| 経路とBGP | routes・bgpSessionStatus |
Batfishに同梱されているサンプルネットワーク(ルータ13台と端末2台の構成)の場合、Intel Core i9-9880HのMac上のv2026.08.27で、スナップショットの初期化が3.0秒、37本のBGPセッションの判定が3.0秒です。結果は確立34本、設定の不一致3本です。traceroute の1回の問い合わせは0.5秒でした。計算時間は機器の台数と質問の種類で変わるため、CIに組み込む前に自社の設定一式で一度測っておくと、1回の検査にかかる時間を見積もれます。
pytestによるCI/CDへの組み込みと危険な設定変更の検出
pybatfish.client.asserts には、検査に失敗すると例外を投げる関数がそろっています。pytestから呼べば、設定リポジトリへのプルリクエストごとに検査を回せます。
import os
import pytest
from pybatfish.client.session import Session
from pybatfish.datamodel.flow import HeaderConstraints
from pybatfish.client.asserts import (
assert_filter_denies,
assert_filter_permits,
assert_filter_has_no_unreachable_lines,
assert_no_undefined_references,
)
SNAPSHOT_DIR = os.environ.get("SNAPSHOT_DIR", "cand")
@pytest.fixture(scope="module")
def bf():
s = Session(host=os.environ.get("BATFISH_HOST", "localhost"))
s.set_network("edge-ci")
s.init_snapshot(SNAPSHOT_DIR, name="candidate", overwrite=True)
status = s.q.fileParseStatus().answer().frame()
assert not status.empty and (status["Status"] == "PASSED").all(), status
issues = s.q.initIssues().answer().frame()
assert issues.empty, issues
return s
def test_no_undefined_references(bf):
assert_no_undefined_references(session=bf)
def test_ssh_from_internet_is_denied(bf):
ssh = HeaderConstraints(srcIps="0.0.0.0/0 \\ 198.51.100.0/24",
dstIps="192.168.10.10", applications=["ssh"])
assert_filter_denies("INBOUND", ssh, session=bf)
def test_ssh_from_admin_is_permitted(bf):
ssh = HeaderConstraints(srcIps="198.51.100.0/24",
dstIps="192.168.10.10", applications=["ssh"])
assert_filter_permits("INBOUND", ssh, session=bf)
def test_acl_has_no_dead_lines(bf):
assert_filter_has_no_unreachable_lines("INBOUND", session=bf)
fixtureの中で、全ファイルのパース成功と initIssues が空であることを先に確かめています。解釈できない行を残したままACLの検査だけが通る事態を防ぐためです。SSHの拒否を確かめる送信元は 0.0.0.0/0 \ 198.51.100.0/24 と書き、管理用サブネットを除いています。\ は集合の差を表す指定子の記法です。
3つの設定に対して実行した結果は次のとおりです。元の設定(base)は管理者のSSHが通らず、変更案(cand)はインターネットからのSSHが通るため、どちらも 2 failed, 2 passed で終了コード1になります。管理用の許可行を deny より上へ移し、重複した443番の行を消した次の設定だけが 4 passed になります。
ip access-list extended INBOUND
permit tcp any any eq 443
permit tcp 198.51.100.0 0.0.0.255 host 192.168.10.10 eq 22
deny ip any any
失敗時に投げられるのは BatfishAssertException で、Pythonの AssertionError を継承していません。自前のコードで except AssertionError と書いても捕まらない点に注意してください。pytestは例外の種類を問わず失敗として扱います。
GitHub Actionsでは、Batfishをサービスコンテナとして起動し、APIが応答してからテストを流します。リポジトリには、上のテストを tests/test_edge.py、変更案の設定を snapshots/candidate/configs/、ワークフローを .github/workflows/batfish.yml として置く想定です。
name: batfish-check
on: pull_request
jobs:
verify:
runs-on: ubuntu-latest
services:
batfish:
image: batfish/batfish
ports:
- 9996:9996
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- run: pip install pybatfish pytest
- name: wait for batfish
timeout-minutes: 2
run: until curl -sf --max-time 5 http://localhost:9996/v2/version; do sleep 2; done
- run: pytest -q tests/test_edge.py
env:
SNAPSHOT_DIR: snapshots/candidate
Ansibleから呼ぶための公式ロール batfish.base もありましたが、リポジトリ batfish/ansible は2023年8月22日にアーカイブされ、読み取り専用になっています。新しく組むなら、上のようにpybatfishをテストコードから直接呼ぶ形にしてください。
Batfishが向かない場面と導入前に決めておくこと
Batfishは設定の論理を検査する道具で、ネットワークが実際に動くことを保証するものではありません。次の用途には使わないでください。
- 帯域・遅延・輻輳の見積もり。トラフィック量は計算に入らない
- 機器のソフトウェア不具合やハードウェア故障の検出。Batfishはベンダーの仕様どおりに動く前提で計算する
- 未対応の構文が多い機器の検査。Batfishは解釈できない行を無視することがあるため、その行が効いている前提の結論は誤りになる
3つ目が最も事故につながります。CIに組み込むときは、initIssues が空であること、fileParseStatus がすべてPASSEDであることを最初のテストにしてください。これが通らない機器の結果は、合格でも信用しないという線を先に決めておきます。実ネットワークを載せるサーバは、公式推奨の32GB以上のメモリで用意してください。
MCPサーバ経由でAIエージェントに調査させる使い方も始まっていますが、v2026.08.27の時点ではベータで、ツール名と引数が変わる可能性があると明記されています。CIの合否判定のように結果を固定したい用途は、pybatfishのAPIで書くほうが安全です。
よくある質問
Batfishは無料で使えますか?
無料で使えます。Apache License 2.0のOSSで、GitHubの batfish/batfish でソースと、リリースノートを公開しています。商用利用も可能です。
BatfishのGitHubリポジトリはどこですか?
サーバ本体は github.com/batfish/batfish、Pythonクライアントは github.com/batfish/pybatfish です。最新リリースはv2026.08.27で、サンプルのノートブックはpybatfish側の jupyter_notebooks にあります。
Cisco機器の設定はそのまま読み込めますか?
読み込めます。IOS、IOS-XE、IOS-XR、NX-OS、ASAの show running-config の出力を configs/ に置けば、ベンダーは自動判定されます。NX-OSは可能なら show run all の出力を渡すよう公式ドキュメントが推奨しています。
Apple SiliconのMacでDockerイメージは動きますか?
動きます。v2026.08.27から batfish/batfish と batfish/allinone はlinux/arm64のイメージも配布しており、エミュレーションを介さずに動作します。
pybatfishはどのPythonのバージョンで動きますか?
pybatfish 2026.9.17.3748はPython 3.10以上が必要で、3.10から3.14までが対応版として記載されています。サーバとpybatfishは同じ時期の版にそろえてください。