現場で役立つPythonネットワーク自動化:複数機器からの情報一括取得と解析
システムエンジニアやインフラエンジニアの皆様にとって、日々の運用・保守業務の中でネットワーク機器からの情報取得は避けて通れない作業の一つです。しかし、管理対象となるネットワーク機器の数が増えるにつれて、手動での作業は非効率になり、設定の取得漏れや確認ミスといったヒューマンエラーのリスクも高まります。
この記事では、Pythonのスキルを活かして、複数のネットワーク機器から設定情報や運用状態を一括で取得し、さらに取得したデータを解析・活用するための自動化手法についてご紹介します。ネットワーク機器のコマンド操作に不慣れな方でも、Pythonライブラリを活用することで、効率的かつ正確な情報取得プロセスを構築することが可能です。
なぜネットワーク情報取得を自動化するのか
ネットワーク機器から情報を取得する作業は、以下のような目的で行われます。
- 設定バックアップ: 機器の設定変更履歴を管理し、障害発生時の復旧に備えます。
- 運用状態の確認: インターフェースの状態、ルーティングテーブル、ログなどを確認し、機器やネットワークの健全性を把握します。
- セキュリティ監査: セキュリティ関連の設定(ACL、認証設定など)がポリシー通りか確認します。
- インベントリ収集: 機器のモデル、OSバージョン、シリアル番号といった資産情報を収集します。
これらの作業を手動で行う場合、TelnetやSSHクライアントを用いて一台ずつ機器に接続し、コマンドを実行し、結果をコピー&ペーストするという手順を踏むのが一般的です。しかし、この方法には以下のような課題があります。
- 時間とコスト: 管理対象機器数が多いほど、作業にかかる時間が膨大になります。
- ヒューマンエラー: コマンドの入力ミス、コピー&ペーストミス、作業漏れといったエラーが発生しやすくなります。
- 作業の属人化: 作業手順が標準化されていない場合、特定の担当者しか作業できない状況が生まれる可能性があります。
ネットワーク情報取得を自動化することで、これらの課題を解決し、作業の効率化、ミスの削減、作業品質の標準化を実現できます。
Pythonを使ったネットワーク情報取得の基本
Pythonを使ってネットワーク機器から情報を取得する最も一般的な方法は、SSHプロトコルを利用することです。多くのエンタープライズ向けネットワーク機器はSSHによるCLIアクセスをサポートしており、PythonからSSHクライアントとして接続し、コマンドを送信して結果を受け取ることができます。
PythonでSSH接続を行うためのライブラリとしては、paramiko
や netmiko
がよく利用されます。
- Paramiko: PythonでSSH2プロトコルを実装したライブラリです。低レベルな操作が可能で、SFTPなどの機能も提供します。汎用的なSSHクライアントとして使用できます。
- Netmiko:
paramiko
をベースに構築されており、様々なネットワーク機器ベンダー(Cisco, Juniper, Aristaなど)に対応したドライバーを提供します。機器のプロンプト判定やページャー(--More--
表示)の自動処理など、ネットワーク機器特有のCLI操作を容易に行えるように設計されています。
今回は、ネットワーク機器操作に特化しており、より簡単にコマンド実行を実装できるnetmiko
を中心に解説を進めます。
準備:ライブラリのインストール
netmiko
をインストールするには、pipコマンドを使用します。
pip install netmiko
これにより、依存関係にある paramiko
なども一緒にインストールされます。
単一機器からの情報取得スクリプト例
まずは、一台の機器から情報を取得する基本的なスクリプトを見てみましょう。ここでは、機器のOSバージョンや稼働状況を確認するための show version
コマンドを実行する例を示します。
from netmiko import ConnectHandler
import getpass
import os
# 接続情報の定義
# 実践的にはハードコードせず、外部ファイルや環境変数から読み込むことを推奨します
device = {
'device_type': 'cisco_ios', # 機器のベンダーとOSタイプを指定
'host': '192.168.1.1', # 機器のIPアドレスまたはホスト名
'username': 'your_username',# 接続ユーザー名
# 'password': 'your_password',# パスワード。入力プロンプトを使うか、安全な方法で管理します
# 'port': 22, # デフォルトは22番ポート
# 'secret': 'your_enable_password', # enableモードに移行する場合のパスワード
}
# パスワードを安全に入力させる例
if 'password' not in device:
device['password'] = getpass.getpass(prompt=f"Enter password for {device['username']}@{device['host']}: ")
# enableパスワードが必要な場合
# if 'secret' not in device:
# device['secret'] = getpass.getpass(prompt=f"Enter enable password for {device['host']}: ")
# 機器への接続とコマンド実行
try:
print(f"Connecting to {device['host']}...")
# ConnectHandlerで機器に接続
with ConnectHandler(**device) as net_connect:
print("Connection successful.")
# コマンドの実行
command = "show version"
print(f"Executing command: '{command}'")
output = net_connect.send_command(command)
# 実行結果の表示
print("\n--- Output ---")
print(output)
print("--------------")
except Exception as e:
print(f"An error occurred: {e}")
print("Script finished.")
このスクリプトでは、netmiko.ConnectHandler
を使用して機器に接続し、send_command()
メソッドで指定したコマンドを実行しています。接続情報は辞書形式で指定しますが、パスワードなどの機密情報はコード内に直接記述せず、実行時に入力させる(getpass
)か、環境変数や設定ファイルから読み込むのがより安全な方法です。
device_type
は netmiko
が機器の種類を判別し、適切なハンドリングを行うために重要です。対応するベンダーとOSのリストは netmiko
のドキュメントで確認できます。
複数機器からの情報一括取得
次に、複数の機器から同じ情報を一括で取得するスクリプトを作成します。機器リストを定義し、ループ処理で各機器に接続してコマンドを実行します。
機器リストはPythonのリストや辞書としてコード内に記述しても良いですが、管理対象が増えることを考えると、CSVファイルやYAMLファイルのような外部ファイルで管理するのが一般的です。ここでは、シンプルにPythonのリストで機器情報を保持する例を示します。
from netmiko import ConnectHandler
import getpass
import os
import yaml # YAMLファイルから読み込む場合
# --- 機器リストの定義 ---
# 通常はファイルから読み込むことを推奨します
# 例:YAMLファイル
# devices.yaml
# - device_type: cisco_ios
# host: 192.168.1.1
# username: your_username
# - device_type: juniper_junos
# host: 192.168.1.2
# username: your_username
# ...
# デモ用の機器リスト(実際にはIPアドレスやユーザー名を適切に設定してください)
device_list = [
{
'device_type': 'cisco_ios',
'host': '192.168.1.10',
'username': 'your_username',
# 'password': 'your_password',
# 'secret': 'your_enable_password',
},
{
'device_type': 'juniper_junos',
'host': '192.168.1.20',
'username': 'your_username',
# 'password': 'your_password',
},
# 必要に応じて機器を追加
]
# 全機器で共通の認証情報を入力させる
# パスワードは共通、ユーザー名は機器ごとに異なる場合などを考慮
common_password = getpass.getpass("Enter SSH password for all devices: ")
# common_enable_password = getpass.getpass("Enter enable password (if required): ")
# 機器リストに認証情報を追加
for device in device_list:
device['password'] = common_password
# if 'secret' in device_list[0]: # 例: 最初の機器にsecret設定があれば共通パスワードを適用
# device['secret'] = common_enable_password
# 実行するコマンドリスト
commands_to_run = [
"show version",
"show ip interface brief",
"show running-config"
]
# --- 機器への接続とコマンド実行(ループ処理) ---
results = {} # 取得結果を格納する辞書
for device in device_list:
hostname = device.get('host', 'N/A')
print(f"\n--- Processing {hostname} ({device['device_type']}) ---")
try:
# ConnectHandlerで機器に接続
with ConnectHandler(**device) as net_connect:
print("Connection successful.")
device_results = {}
for command in commands_to_run:
print(f"Executing command: '{command}'")
# コマンド実行
output = net_connect.send_command(command)
device_results[command] = output
# 特にrunning-configなどは出力が大きい場合があるため注意
# print(f"Received {len(output)} characters.") # 受信データ量の確認
results[hostname] = device_results
print("Finished processing.")
except Exception as e:
print(f"An error occurred for {hostname}: {e}")
results[hostname] = {"error": str(e)} # エラー情報を記録
# --- 取得結果の表示または保存 ---
print("\n--- Summary of Results ---")
for hostname, data in results.items():
print(f"\n--- Results for {hostname} ---")
if "error" in data:
print(f"Error: {data['error']}")
else:
for command, output in data.items():
print(f"\nCommand: '{command}'")
print(output[:500] + "..." if len(output) > 500 else output) # 出力が多い場合は一部のみ表示
# 例:設定ファイルとして保存
# if "show running-config" in data:
# config_filename = f"{hostname}_running-config.txt"
# with open(config_filename, 'w') as f:
# f.write(data["show running-config"])
# print(f"Running config saved to {config_filename}")
print("\nScript finished.")
このスクリプトでは、device_list
に複数の機器情報を格納し、for
ループで各機器に順次接続しています。各機器に対して commands_to_run
にリストアップされたコマンドを実行し、その結果を results
辞書にまとめています。接続やコマンド実行中にエラーが発生した場合も、try...except
ブロックで捕捉し、処理が中断しないようにしています。
取得した結果は results
辞書に格納されており、この後のステップでデータを解析・加工するために利用できます。
取得データの解析と活用
ネットワーク機器のCLIコマンド出力は、人間が読むための整形されたテキスト形式であることがほとんどです。このテキストデータをプログラムで扱いやすい構造化されたデータ(例:Pythonの辞書やリスト)に変換することを「パース(Parse)」と呼びます。
テキストデータをパースする方法はいくつかあります。
- 正規表現 (Regular Expression): 最も汎用的な方法です。複雑なテキストパターンも記述できますが、コマンド出力のフォーマットが少し変わるだけで正規表現の修正が必要になる場合があります。
- 文字列操作:
split()
,find()
, スライスなど、基本的な文字列操作メソッドを組み合わせてパースします。単純なフォーマットには有効ですが、複雑になるとコードが読みにくくなりがちです。 - パーシングライブラリ:
- TextFSM: Googleが開発した、CLI出力から構造化データを抽出するためのテンプレートベースのパーサーです。多くのネットワーク機器ベンダーやコマンドに対応したテンプレートが公開されており、テンプレートを作成・利用することで効率的にパースできます。
netmiko
やnapalm
と組み合わせて使用されることが多いです。 - TTP (Template Text Parser): TextFSMと同様にテンプレートを使用しますが、より柔軟な機能を持っています。
- genie parser: Ciscoが開発したパーサーライブラリです。主にCisco製品のコマンド出力に対応しています。
- TextFSM: Googleが開発した、CLI出力から構造化データを抽出するためのテンプレートベースのパーサーです。多くのネットワーク機器ベンダーやコマンドに対応したテンプレートが公開されており、テンプレートを作成・利用することで効率的にパースできます。
ここでは、比較的シンプルで応用範囲の広い正規表現を使ったパースの例を示します。先ほどの show ip interface brief
コマンドの出力から、インターフェース名、IPアドレス、ステータス情報(status, protocol)を抽出することを考えます。
import re
# show ip interface brief の出力例
# 機器やOSバージョンによって出力形式は異なります
show_ip_int_brief_output = """
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 192.168.1.1 YES manual up up
GigabitEthernet0/1 unassigned YES unset administratively down down
Loopback0 10.0.0.1 YES manual up up
Vlan1 unassigned YES unset up down
"""
# 正規表現パターン
# 各インターフェース行を抽出
# インターフェース名、IPアドレス、ステータス、プロトコルをキャプチャグループで取得
# \s+ は1つ以上の空白文字、.*? は最短一致で任意の文字、\S+ は1つ以上の非空白文字
pattern = re.compile(
r"^(?P<interface>\S+)\s+" # インターフェース名
r"(?P<ip_address>\S+)\s+" # IPアドレス
r"\s+\S+\s+\S+\s+" # OK?, Method をスキップ
r"(?P<status>\S+)\s+" # Status
r"(?P<protocol>\S+)$", # Protocol
re.MULTILINE # 各行の先頭(^)と末尾($)にマッチさせる
)
parsed_data = []
# パターンにマッチするすべての行を検索
matches = pattern.finditer(show_ip_int_brief_output)
for match in matches:
# マッチしたグループから辞書を作成
interface_info = match.groupdict()
parsed_data.append(interface_info)
# 解析結果の表示
import json
print("\n--- Parsed Data (show ip interface brief) ---")
print(json.dumps(parsed_data, indent=4))
print("---------------------------------------------")
# この parsed_data を使用して、例えば
# - IPアドレスが設定されているインターフェース一覧を作成
# - statusが 'administratively down' のインターフェースを抽出
# - データベースに登録
# - レポートを生成
# といった処理に進むことができます。
この例では、re
モジュールを使って正規表現を定義し、finditer()
でコマンド出力テキスト全体からマッチする箇所(インターフェースごとの行)を全て見つけ出しています。各マッチオブジェクトから groupdict()
を使って名前付きキャプチャグループ(例: (?P<interface>\S+)
の interface
)に対応する文字列を辞書として取得し、リストに追加しています。
正規表現は強力ですが、コマンド出力形式の微妙な違いに対応するためには、堅牢なパターン設計が必要です。より複雑なコマンド出力や、複数の機器・OSタイプに対応する場合は、TextFSMやTTPのようなテンプレートベースのパーサーライブラリを検討することをお勧めします。
実践的な考慮点
現場でこれらのスクリプトを運用する上で、いくつか重要な考慮点があります。
- 認証情報の安全な管理: パスワードや秘密鍵をスクリプト内に直接記述することは絶対に避けてください。環境変数、安全な設定ファイル(パーミッションを適切に設定)、パスワードマネージャー、Vaultのような秘密情報管理システムなどを活用してください。
- エラーハンドリング: ネットワークの遅延、機器の負荷、設定ミスなど、様々な要因で接続やコマンド実行が失敗する可能性があります。
try...except
ブロックを適切に使い、エラーが発生してもスクリプト全体が停止しないようにし、問題の原因を特定できるようなログ出力を含めることが重要です。 - タイムアウト設定: 接続やコマンド実行に時間がかかりすぎる場合に備え、適切なタイムアウトを設定します。
netmiko
のConnectHandler
やsend_command
メソッドにはタイムアウト関連の引数があります。 - 並行処理: 多数の機器から情報を取得する場合、逐次処理では時間がかかりすぎることがあります。Pythonの
concurrent.futures
やasyncio
ライブラリを使って、複数の機器への接続やコマンド実行を並行して行うことで、処理時間を大幅に短縮できます。ただし、機器やネットワークへの負荷増加には注意が必要です。 - 冪等性: 情報取得スクリプト自体は基本的に冪等性(何度実行しても結果が変わらない、副作用がない)を満たしやすいですが、取得したデータを元に設定変更などの操作を行う場合は、変更操作が冪等になるように設計することが重要です。
- IaCツールとの連携: 取得したインベントリ情報や設定内容は、Ansible, Terraform, Chef, PuppetといったInfrastructure as Code (IaC) ツールと連携させることで、設定管理やデプロイメントパイプラインの一部として活用できます。例えば、スクリプトで取得した現在の設定と、IaCツールで管理している望ましい設定を比較し、差異を検出する、といった応用が考えられます。
まとめ
この記事では、Pythonと netmiko
ライブラリを中心に、ネットワーク機器からの情報一括取得と基本的なデータ解析の方法についてご紹介しました。手動では時間と手間がかかる情報収集作業も、Pythonスクリプトを活用することで効率化し、ヒューマンエラーのリスクを低減できます。
今回紹介したスクリプトはあくまで基本的なものであり、実際の運用では認証情報の安全な管理、堅牢なエラーハンドリング、大規模環境に対応するための並行処理や専用フレームワーク(Nornirなど)の導入、そして取得データの高度な解析と活用といった要素が必要になります。
Pythonによるネットワーク自動化は、日々の運用業務を改善する強力な手段です。ぜひ、この記事で紹介した内容を参考に、皆様の現場での自動化に取り組んでいただければ幸いです。