現場で役立つPython:ネットワーク経路情報の自動収集と可視化スクリプト
はじめに
システム運用やインフラ管理において、ネットワーク経路情報は非常に重要な要素です。サービスの疎通確認、パフォーマンス問題の調査、セキュリティポリシーの確認など、様々な場面でネットワーク機器のルーティングテーブルや実際のパケットが通過する経路(traceroute結果など)を参照する必要があります。
これらの情報の収集は、通常ネットワーク機器にログインしてCLIコマンドを実行することで行われます。しかし、対象機器が多数にわたる場合や、頻繁に状態を確認する必要がある場合、手動での作業は非効率であり、ミスが発生するリスクも高まります。
Pythonを活用することで、これらのネットワーク経路情報の収集と解析、さらにはその後の可視化に向けたデータ準備を自動化できます。本記事では、Pythonを用いてネットワーク機器から経路情報を自動収集し、扱いやすい構造化データとして解析する基本的な手法と、簡単な活用例としての可視化への連携について、具体的なスクリプト例を交えて解説します。
なぜネットワーク経路情報の自動化が必要か
システム開発やクラウドインフラの自動化に慣れているエンジニアにとって、ネットワーク機器への操作は馴染みが薄いかもしれません。しかし、アプリケーションやサービスが稼働する上で、ネットワークの挙動は常に考慮すべき課題です。
- トラブルシューティング: 特定のサービスにアクセスできない、通信速度が遅いなどの問題発生時、パケットが意図した経路を通っているかを確認することは原因特定に不可欠です。手動で複数のホップの
traceroute
を実行したり、大量のルーティングエントリから該当情報を探したりするのは時間がかかります。 - 設計確認とコンプライアンス: ネットワーク設計通りにルーティングが設定されているか、特定の通信が規定の経路を通るようになっているかなどを定期的に確認する必要があります。
- 変更管理: ネットワーク設定変更が既存の経路にどのような影響を与えるかを事前に確認したり、変更後の経路を迅速に検証したりする際に自動化が役立ちます。
- キャパシティプランニング: ネットワーク負荷分散のために複数の経路が存在する場合、それぞれの利用状況を把握するためにルーティング情報を定期的に収集・分析することが有効です。
これらの作業を自動化することで、作業時間の短縮、ヒューマンエラーの削減、そしてより迅速かつ正確な情報に基づいた判断が可能になります。
Pythonによるネットワーク経路情報の収集手法
Pythonでネットワーク機器から経路情報を収集するには、いくつかの方法があります。代表的なのはCLIベースのアプローチとAPIベースのアプローチです。対象読者のスキルセットと現場での利用シーンを考慮し、本記事ではCLIベースのアプローチに焦点を当てます。多くの現場で既存のネットワーク機器がCLI操作に対応しており、Pythonistaにとって最も始めやすい方法の一つであるためです。
1. CLI経由でのコマンド実行
これは最も一般的な方法です。SSHを使用してネットワーク機器に接続し、show ip route
, show route
, show ip bgp summary
, traceroute
といったCLIコマンドを実行してその出力結果を取得します。
- 使用ライブラリ:
paramiko
(SSHクライアントライブラリ)や、より高レベルなネットワーク機器操作ライブラリであるnetmiko
が適しています。netmiko
は多くのベンダーのCLIに対応しており、プロンプトの判定やページャー(--More--
など)の自動処理など、CLI操作特有の面倒な部分を吸収してくれるため推奨されます。
2. CLI出力のパース(構造化データへの変換)
CLIコマンドの出力は人間が読むには適していますが、Pythonスクリプトで機械的に処理するには不向きな非構造化テキストです。これを解析(パース)し、扱いやすいJSONやPythonの辞書のような構造化データに変換する必要があります。
- 使用ライブラリ:
TextFSM
: CiscoやJuniperなど、多くのネットワーク機器のCLI出力に対応したテンプレート集(ntc-templatesなど)が存在します。独自のテンプレートを作成することも可能です。Genie Parser
: Ciscoが開発しているより強力なパースライブラリです。特定のコマンド出力に対して詳細かつ正確な構造化データを提供します。YAML形式のパーサー定義を使用します。NAPALM
: マルチベンダー対応のネットワーク自動化ライブラリで、特定のコマンド(例:get_route_to
,get_environment
など)を実行すると、ベンダーの違いを吸収した統一的な構造化データとして結果を返します。内部でGenie ParserやTextFSMなどを利用しています。
対象読者はPythonスキルが高いことから、CLI出力のパース部分に焦点を当てることで、既存のPythonスキルを活かせるイメージを持ってもらいやすくなります。ここではnetmiko
でコマンドを実行し、genie.libs.parser
でパースする例を中心に紹介します。Genie Parserは特定のコマンドに対して非常に高品質なパース結果を提供するため、お勧めです。
実践スクリプト例:ルーティングテーブルの収集と解析
ここでは、netmiko
を使用してネットワーク機器のshow ip route
コマンドを実行し、genie.libs.parser
(PyATSの一部)でその出力をパース、特定の情報を抽出するスクリプト例を示します。
まず、必要なライブラリをインストールします。
pip install netmiko pyats.libs
次に、スクリプトコードです。
import json
from netmiko import ConnectHandler
from genie.libs.parser.iosxe.show_routing import ShowIpRoute
# 機器接続情報(実際の環境に合わせて修正してください)
device = {
'device_type': 'cisco_ios', # 使用する機器のベンダータイプ
'host': 'your_device_ip',
'username': 'your_username',
'password': 'your_password',
# 'secret': 'your_enable_password', # 特権EXECモードに移行する場合
'port': 22,
}
def get_and_parse_route_info(device_params: dict, target_prefix: str = None):
"""
ネットワーク機器からルーティングテーブルを取得し、解析する関数
Args:
device_params (dict): Netmiko接続パラメータ
target_prefix (str, optional): 抽出したい特定のネットワークプレフィックス. Defaults to None.
Returns:
dict or list or None: 解析されたルーティング情報。target_prefix指定時はリスト、指定なし時は全情報。
取得/解析に失敗した場合はNone。
"""
try:
# NetmikoでSSH接続
print(f"Connecting to {device_params['host']}...")
with ConnectHandler(**device_params) as net_connect:
# 特権EXECモードに移行(必要に応じて)
# net_connect.enable()
# 'show ip route' コマンド実行
print("Executing 'show ip route'...")
cli_output = net_connect.send_command('show ip route')
print("Command executed successfully.")
# Genie ParserでCLI出力をパース
# Parserクラスのインスタンスを作成し、parse()メソッドを呼び出す
# 引数としてCLI出力を渡す
parser = ShowIpRoute(device_params['device_type']) # device_typeはparser初期化に必要
parsed_output = parser.parse(cli_output)
print("CLI output parsed successfully.")
# 解析結果はネストされた辞書構造になる。
# 例えば、IPv4のRIB情報は parsed_output['vrf']['default']['address_family']['ipv4']['routes'] に格納されている。
# 特定のプレフィックスを抽出、または全情報を返す
if target_prefix:
print(f"Extracting information for prefix: {target_prefix}")
extracted_routes = []
# Genie Parserの出力構造は複雑なため、実際の出力を見てパスを確認する必要があります。
# ここでは一般的なパスを例として使用しますが、機器/OSバージョンにより異なる場合があります。
routes = parsed_output.get('vrf', {}).get('default', {}).get('address_family', {}).get('ipv4', {}).get('routes', {})
for prefix, route_info in routes.items():
if prefix == target_prefix:
extracted_routes.append({prefix: route_info}) # 見つかったルート情報をリストに追加
print(f"Found {len(extracted_routes)} routes for {target_prefix}.")
return extracted_routes if extracted_routes else None
else:
# 全ルーティング情報を返す
return parsed_output.get('vrf', {}).get('default', {}).get('address_family', {}).get('ipv4', {}).get('routes', {})
except Exception as e:
print(f"An error occurred: {e}")
return None
# スクリプト実行部分
if __name__ == "__main__":
# 特定のプレフィックスを検索する場合
# target_prefix = '192.168.1.0/24'
# route_info = get_and_parse_route_info(device, target_prefix=target_prefix)
# 全ルーティング情報を取得する場合
route_info = get_and_parse_route_info(device)
if route_info:
print("\n--- Parsed Route Information ---")
# 取得した情報をJSON形式で整形して表示
print(json.dumps(route_info, indent=4))
# 特定の情報を抽出する例(全情報取得時)
if not target_prefix and isinstance(route_info, dict):
print("\n--- Extracting sample info (e.g., next-hops for direct routes) ---")
for prefix, info in route_info.items():
if 'directly_connected' in info and info['directly_connected']:
print(f"Prefix: {prefix}, Directly Connected Via: {info.get('next_hop', {}).get('outgoing_interface')}")
elif 'next_hop' in info:
# ネクストホップが複数ある場合もあるためリストとして扱う
next_hops = info['next_hop']
if isinstance(next_hops, list):
for nh in next_hops:
print(f"Prefix: {prefix}, Next Hop: {nh.get('next_hop_list', [{}])[0].get('next_hop')}, Interface: {nh.get('next_hop_list', [{}])[0].get('outgoing_interface')}")
elif isinstance(next_hops, dict):
# Single next-hop case might be dictionary depending on parser output structure
print(f"Prefix: {prefix}, Next Hop: {next_hops.get('next_hop_list', [{}])[0].get('next_hop')}, Interface: {next_hops.get('next_hop_list', [{}])[0].get('outgoing_interface')}")
else:
print("\nFailed to retrieve or parse route information.")
コードの解説:
- インポート:
netmiko
で機器接続、json
で結果表示整形、genie.libs.parser.iosxe.show_routing.ShowIpRoute
でCisco IOS XEのshow ip route
パーサーをインポートしています。 - 機器接続情報:
device
辞書に接続に必要な情報を定義します。device_type
はNetmikoだけでなくGenie Parserでも機器のOSタイプを識別するために重要です。実際の環境に合わせてIPアドレス、認証情報などを修正してください。 get_and_parse_route_info
関数:ConnectHandler(**device_params)
でNetmikoによるSSH接続を確立します。with
構文を使うことで、接続が自動的に閉じられるようにします。net_connect.send_command('show ip route')
でコマンドを実行し、標準出力を文字列として取得します。ShowIpRoute(device_params['device_type'])
でパーサーオブジェクトを作成します。parser.parse(cli_output)
で取得したCLI出力をパースします。Genie Parserは複雑なCLI出力を非常に詳細なPython辞書に変換してくれます。- パース結果の辞書構造は、コマンドや機器のOSバージョンによって異なります。Genie Parserの公式ドキュメントや、実際にコマンド出力をパースして結果を確認することで、正確な構造を把握する必要があります。例として示したパス (
parsed_output.get('vrf', {}).get('default', {}).get('address_family', {}).get('ipv4', {}).get('routes', {})
) は一般的な構造ですが、環境によっては異なる可能性があります。.get({}, {})
のようにデフォルト値として空の辞書を指定することで、キーが存在しない場合でもエラーにならないようにしています。 target_prefix
が指定されていれば、パース結果の中から該当するプレフィックスの情報だけを抽出してリストとして返します。指定されていなければ、全ルーティング情報(IPv4のデフォルトVRF)を返します。- エラーハンドリングとして、
try...except
ブロックで例外をキャッチし、エラーメッセージを表示してNone
を返します。
- スクリプト実行部分 (
if __name__ == "__main__":
)get_and_parse_route_info
関数を呼び出します。- 返された
route_info
がNone
でなければ、json.dumps
を使って整形されたJSON形式でコンソールに表示します。これは、取得した構造化データの全体像を確認するのに便利です。 - 全情報取得時 (
target_prefix
を指定しない場合) のみ、パース結果から特定の情報(例: Directly Connectedな経路のインターフェース、ネクストホップを持つ経路のネクストホップIPとインターフェース)を抽出して表示する例を示しています。この部分も、Genie Parserの出力構造に合わせて適切に修正する必要があります。
注意点:
- このスクリプトはCisco IOS/IOS XEを想定した
ShowIpRoute
パーサーを使用しています。他のベンダーの機器を対象とする場合は、適切なGenie Parserライブラリ(例:genie.libs.parser.junos
,genie.libs.parser.nxos
など)の、対応するコマンドのパーサーを使用してください。あるいはTextFSMとntc-templatesを使用することも検討できます。 - Genie Parserの出力構造は詳細かつ複雑です。目的の情報を抽出するには、パース結果の辞書構造を理解する必要があります。デバッグ用にパース結果全体をファイルに出力して確認すると良いでしょう。
- エラーハンドリングは簡易的なものです。実際の運用では、より詳細なログ出力や、特定の例外に対する処理を追加する必要があります。
- 認証情報の管理には、安全な方法(環境変数、設定ファイル、Secrets Managerなど)を使用することを強く推奨します。本記事の例ではコード内に直接記述していますが、これはあくまで解説のためです。
収集したデータの活用と可視化への連携
Pythonで構造化された経路情報を取得できれば、様々な応用が可能になります。
- レポート生成: 特定のプレフィックスに関する経路情報や、特定のネクストホップを使用している経路一覧などをCSVやMarkdown形式で出力し、人間がレビューしやすいレポートを作成できます。
- 設定確認自動化: 期待する経路(例: デフォルトルートのネクストホップはこれであるべき)と収集したデータを比較し、差異があればアラートを出すといった自動確認スクリプトを作成できます。
- 他のツールとの連携:
- CMDB/IPAM: 収集したルーティング情報をCMDB (Configuration Management Database) やIPAM (IP Address Management) ツールに登録・更新することで、ネットワークの状態を最新に保つことができます。
- 監視ツール: ルーティングテーブルの変化(特定の経路の消滅、意図しない経路の出現など)を検知し、ZabbixやPrometheusなどの監視ツールに連携してアラートを発報できます。
- 可視化ツール: 収集したルーティング情報やtraceroute結果を元に、ネットワークトポロジの一部や特定の通信パスを図示化するツール(Graphviz, draw.ioなど)への入力データを生成できます。
例えば、取得したルーティング情報から「どのネットワークがどのネクストホップを経由しているか」という情報を抽出し、GraphvizのDOT言語形式で出力するスクリプトを作成すれば、簡単な経路マップを自動生成することも可能です。
# 前述のget_and_parse_route_info関数で取得したparsed_routes(全情報)を想定
parsed_routes = get_and_parse_route_info(device) # この関数呼び出しは別途実行済みとする
if parsed_routes:
print("\n--- Generating Graphviz DOT format ---")
dot_output = "digraph Routes {\n"
dot_output += ' rankdir="LR";\n' # 左から右への描画方向
for prefix, info in parsed_routes.items():
# Directly Connectedな経路は除外するか、特別なノードとして扱う
if 'directly_connected' in info and info['directly_connected']:
continue # この例では直接接続経路はスキップ
if 'next_hop' in info:
next_hops = info['next_hop']
# ネクストホップがリスト形式の場合
if isinstance(next_hops, list):
for nh_group in next_hops: # ECMPなどでnext_hop_listが複数グループある場合
# 各next_hop_list内のエントリを処理
if 'next_hop_list' in nh_group and isinstance(nh_group['next_hop_list'], list):
for nh_entry in nh_group['next_hop_list']:
next_hop_ip = nh_entry.get('next_hop')
outgoing_interface = nh_entry.get('outgoing_interface')
if next_hop_ip:
# ノードとエッジをDOT形式で記述
# プレフィックスとネクストホップをノードとする
dot_output += f' "{prefix}" -> "{next_hop_ip}";\n'
# インターフェース情報をエッジのラベルとして追加
if outgoing_interface:
dot_output += f' "{prefix}" -> "{next_hop_ip}" [ label="{outgoing_interface}" ];\n'
# ネクストホップが辞書形式(単一ネクストホップ)の場合
elif isinstance(next_hops, dict):
if 'next_hop_list' in next_hops and isinstance(next_hops['next_hop_list'], list):
for nh_entry in next_hops['next_hop_list']:
next_hop_ip = nh_entry.get('next_hop')
outgoing_interface = nh_entry.get('outgoing_interface')
if next_hop_ip:
dot_output += f' "{prefix}" -> "{next_hop_ip}";\n'
if outgoing_interface:
dot_output += f' "{prefix}" -> "{next_hop_ip}" [ label="{outgoing_interface}" ];\n'
dot_output += "}\n"
print(dot_output)
print("\n--- End of DOT format ---")
print("You can use graphviz tools (e.g., dot command) to render this output.")
# Graphvizのインストールが必要です(例: sudo apt-get install graphviz)
# 生成されたDOTファイルを保存し、コマンドラインで実行例: dot -Tpng output.dot -o route_graph.png
このGraphviz出力スクリプトは、非常に単純化された例であり、ECMP(等コストマルチパス)など複雑なルーティング構造には対応していません。しかし、収集した構造化データを様々なツールで活用できる可能性を示しています。
実践的な考慮事項
現場で自動化スクリプトを運用する際には、以下の点を考慮することが重要です。
- エラーハンドリング: ネットワーク接続の失敗、認証エラー、コマンド実行時のエラー(コマンドが存在しない、権限がないなど)、CLI出力の予期しない変化によるパースエラーなど、様々な失敗要因が考えられます。これらのエラーを適切にハンドリングし、リトライ処理を行ったり、詳細なログを残したりすることが堅牢なスクリプトには不可欠です。
- 認証情報の管理: 機器への認証情報は機密情報です。スクリプト内にハードコードせず、環境変数、設定ファイル(適切な権限設定を行う)、あるいは専用のSecrets Manager(HashiCorp Vault, AWS Secrets Managerなど)を利用して安全に管理してください。
- タイムアウトとリトライ: ネットワークの状態によっては、コマンド実行に時間がかかったり、一時的に接続が不安定になったりすることがあります。適切なタイムアウト設定を行い、必要に応じてリトライ処理を実装することで、スクリプトの信頼性が向上します。
- 並列処理: 対象機器が多い場合、1台ずつ順番に処理すると時間がかかります。Pythonの
concurrent.futures
モジュールや、ネットワーク自動化に特化したNornir
のようなフレームワークを利用して、複数の機器に対して同時に処理を実行することで、効率を大幅に向上できます。 - CI/CDパイプラインへの組み込み: ネットワーク経路情報の自動収集スクリプトをCI/CDパイプラインに組み込むことで、コード変更やデプロイメントの前後にネットワークの状態を確認し、意図しない変更がないか検証するといった自動テストの一部として活用できます。
まとめ
本記事では、Pythonとライブラリ(Netmiko, Genie Parserなど)を用いて、ネットワーク機器からルーティングテーブルなどの経路情報を自動収集し、構造化データとして解析する基本的な手法を解説しました。手動作業の非効率性やヒューマンエラーのリスクを回避し、迅速かつ正確なネットワーク状態の把握を自動化できることを示しました。
収集した構造化データは、単に情報を参照するだけでなく、レポート生成、設定の自動検証、CMDB/監視ツールとの連携、そして可視化ツールの入力データ生成など、様々な形で活用可能です。
ネットワーク機器に直接的な操作経験が限定的であっても、Pythonのスキルを活用することで、ネットワーク領域の自動化に大きく貢献できます。今回紹介した手法を基礎として、ぜひご自身の現場で役立つネットワーク自動化スクリプト開発に取り組んでみてください。今後は、traceroute
結果の自動解析や、BGP/OSPFといった特定のルーティングプロトコル情報の詳細な収集・分析についても掘り下げていく予定です。
関連情報・参考文献
- Netmiko Documentation: https://netmiko.readthedocs.io/en/latest/
- Genie Parsers Documentation: https://pubhub.devnetcloud.com/media/genie-docs/docs/userguide/parsers/index.html
- PyATS Documentation: https://pubhub.devnetcloud.com/media/pyats/docs/index.html
- Nornir Documentation: https://nornir.tech/
- Graphviz: https://graphviz.org/
これらのリソースを参照することで、さらに深くネットワーク自動化の技術を学ぶことができます。