現場で使える!Pythonによるネットワーク機器コンフィグのDiff自動取得
はじめに
システム開発やクラウドインフラの自動化を進める上で、IaC(Infrastructure as Code)の概念は不可欠です。コードとしてインフラ構成を管理し、変更を差分(Diff)として把握・管理することは、安定運用や監査の観点から非常に重要になります。
一方、ネットワーク機器の構成管理においては、依然として手作業やCLIベースの操作が中心となる場面も少なくありません。しかし、ネットワーク構成もまた重要なインフラの一部であり、その変更履歴や現状を正確に把握することは運用上不可欠です。
本記事では、Pythonのスキルを活かし、ネットワーク機器のコンフィグレーション(設定)を自動的に取得し、その差分(Diff)を生成する方法について解説します。これにより、ネットワーク機器の構成変更を自動的に検知・記録し、IaCライクな変更管理の一助とすることが可能になります。
なぜネットワークコンフィグのDiff自動取得が必要か
ネットワーク機器のコンフィグDiffを自動取得することには、いくつかの大きなメリットがあります。
- 変更の正確な把握: 手作業による設定変更は、意図しないミスや記録漏れが発生する可能性があります。自動的にDiffを取得することで、実際に何が変更されたのかを正確かつ網羅的に把握できます。
- 変更管理と監査: いつ、誰(またはどのスクリプト)が、どのような変更を行ったのかをDiffとして記録することで、変更管理プロセスを強化し、監査要件への対応を容易にします。
- トラブルシューティング: ネットワークの不具合が発生した場合、直近のコンフィグ変更点が有力な原因特定の手がかりとなることが多々あります。Diffを迅速に参照できることで、原因特定までの時間を短縮できます。
- 設定のバックアップと比較: 定期的にコンフィグを取得し、前回の取得分と比較することで、予期しない設定変更や逸脱(Drift)を検知できます。
PythonによるDiff自動取得の基本的な考え方
Pythonを使ってネットワーク機器のコンフィグDiffを自動取得するには、主に以下のステップを踏みます。
- 現在のコンフィグを取得する: ネットワーク機器にログインし、現在の稼働コンフィグ(running-configなど)を取得します。これには、NetmikoやParamikoといったSSHライブラリがよく使われます。機器がAPI(RESTConf, NETConfなど)をサポートしていれば、requestsライブラリなどを用いてAPI経由で取得することも可能です。
- 基準となるコンフィグを準備する: 比較の基準となる過去のコンフィグファイルを準備します。これは、前回自動取得して保存しておいたファイルでも良いですし、Gitなどでバージョン管理している理想的な状態のコンフィグでも構いません。
- 取得したコンフィグと基準コンフィグを比較する:
Pythonの標準ライブラリ
difflib
などを使用して、取得したコンフィグ文字列(またはリスト化した行)と基準コンフィグ文字列(またはリスト化した行)を比較し、差分情報を生成します。 - 差分情報を整形・出力する: 生成された差分情報を、人間が読みやすい形式(Unified Diff形式など)に整形して出力したり、ファイルに保存したり、通知システムに連携したりします。
実装例:Netmikoとdifflibを使う
ここでは、Pythonの代表的なネットワーク自動化ライブラリである Netmiko
を使ってコンフィグを取得し、標準ライブラリ difflib
で差分を生成する基本的なスクリプト例を示します。
前提条件:
- Pythonがインストールされていること。
netmiko
ライブラリがインストールされていること (pip install netmiko
)。- 対象のネットワーク機器にSSHで接続できること。
- 比較元のコンフィグファイルがローカルに存在すること。
import os
from netmiko import ConnectHandler
import difflib
from datetime import datetime
# --- 設定 ---
# ネットワーク機器の接続情報 (適宜変更してください)
device = {
'device_type': 'cisco_ios', # 機器タイプに合わせて変更 (例: juniper_junos, arista_eosなど)
'host': 'YOUR_DEVICE_IP',
'username': 'YOUR_USERNAME',
'password': 'YOUR_PASSWORD',
# 'port': 22, # デフォルトは22
# 'secret': 'YOUR_ENABLE_PASSWORD', # 特権EXECモードへの移行が必要な場合
}
# 比較元の基準コンフィグファイルパス
# 例: 前回の取得時に保存したファイル
BASELINE_CONFIG_FILE = 'baseline_config.txt'
# 新規取得コンフィグを保存する場合のファイル名フォーマット
CURRENT_CONFIG_FILE_FORMAT = 'current_config_{hostname}_{timestamp}.txt'
# --- 処理 ---
def get_running_config(device_info):
"""
Netmikoを使ってネットワーク機器からrunning-configを取得する
"""
config = None
try:
print(f"{device_info['host']} に接続中...")
with ConnectHandler(**device_info) as net_connect:
# 機器タイプに応じたrunning-config取得コマンド
# Cisco IOS/NX-OS/XE/XR, Juniper Junos, Arista EOSなどで 'show running-config' または 'show configuration'
# 個別のコマンドが必要な場合は net_connect.send_command() を使用
print("running-configを取得中...")
config = net_connect.send_command('show running-config')
print("取得完了")
except Exception as e:
print(f"接続またはコマンド実行エラー: {e}")
# エラー発生時はNoneを返すか、例外を再raiseすることも検討
# 現場ではより詳細なエラーハンドリングが必要
pass
return config
def save_config_to_file(config, filename):
"""
取得したコンフィグをファイルに保存する
"""
try:
with open(filename, 'w') as f:
f.write(config)
print(f"コンフィグを '{filename}' に保存しました。")
except IOError as e:
print(f"ファイル保存エラー: {e}")
def load_config_from_file(filename):
"""
ファイルからコンフィグを読み込む
"""
config = None
try:
with open(filename, 'r') as f:
config = f.read()
print(f"コンフィグを '{filename}' から読み込みました。")
except FileNotFoundError:
print(f"エラー: 基準コンフィグファイル '{filename}' が見つかりません。")
except IOError as e:
print(f"ファイル読み込みエラー: {e}")
return config
def generate_diff(text1, text2, fromfile='Baseline Config', tofile='Current Config'):
"""
2つのテキスト間の差分をUnified Diff形式で生成する
"""
if text1 is None or text2 is None:
return None
lines1 = text1.splitlines(keepends=True) # keepends=Trueで改行コードを保持
lines2 = text2.splitlines(keepends=True)
differ = difflib.UnifiedDiff(lines1, lines2, fromfile=fromfile, tofile=tofile)
diff_output = ''.join(list(differ))
return diff_output
# --- メイン処理 ---
if __name__ == "__main__":
# 1. 基準コンフィグをファイルから読み込む
baseline_config = load_config_from_file(BASELINE_CONFIG_FILE)
if baseline_config is None:
# 基準ファイルがない場合は処理を中断
print("基準コンフィグファイルが見つからないため、Diff生成をスキップします。")
print(f"初回実行時は現在のコンフィグを取得し、'{BASELINE_CONFIG_FILE}' として保存してください。")
else:
# 2. 現在のコンフィグをネットワーク機器から取得する
current_config = get_running_config(device)
if current_config:
# 3. 取得したコンフィグをファイルに保存 (履歴として残す場合)
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
hostname = device['host'].replace('.', '_') # ファイル名に使える形式に変換
current_config_filename = CURRENT_CONFIG_FILE_FORMAT.format(
hostname=hostname, timestamp=timestamp
)
# save_config_to_file(current_config, current_config_filename) # 必要に応じてコメント解除
# 4. Diffを生成する
diff_output = generate_diff(baseline_config, current_config,
fromfile=BASELINE_CONFIG_FILE,
tofile=f"{device['host']} Running Config ({timestamp})")
# 5. Diffを表示または保存する
if diff_output:
print("\n--- Configuration Diff ---")
print(diff_output)
print("------------------------")
# 必要に応じてDiffをファイルに保存したり、メールやSlackなどで通知したりする処理を追加
# diff_filename = f"diff_{hostname}_{timestamp}.txt"
# save_config_to_file(diff_output, diff_filename)
else:
print("\n--- Configuration Diff ---")
print("差分はありません。")
print("------------------------")
# 注: この例では、取得した現在のコンフィグを自動的にBASELINE_CONFIG_FILEとして上書きしていません。
# 継続的にDiffを取りたい場合は、成功時に current_config を BASELINE_CONFIG_FILE として保存するロジックを追加するか、
# GitOpsのようにバージョン管理システムを利用して基準コンフィグを管理する必要があります。
else:
print("現在のコンフィグが取得できなかったため、Diff生成を中断します。")
コード解説:
device
ディクショナリに接続情報を設定します。device_type
はNetmikoが機器に合わせた適切なプロンプトやコマンドを扱うために重要です。BASELINE_CONFIG_FILE
は比較対象となる過去のコンフィグファイルを指定します。初回実行時にはこのファイルが存在しないため、エラー処理を入れています。get_running_config
関数はNetmiko
を使って機器にSSH接続し、show running-config
コマンド(または機器に応じたコマンド)を実行して結果を返します。接続エラーやコマンド実行エラーが発生した場合の基本的なエラーハンドリングを含んでいます。現場では、リトライ処理や詳細なロギングなども検討が必要です。save_config_to_file
およびload_config_from_file
は、コンフィグをファイルに保存・読み込みするためのシンプルなヘルパー関数です。generate_diff
関数は、difflib.UnifiedDiff
を使用して2つのテキスト文字列の差分を計算し、Unified Diff形式の文字列として返します。keepends=True
は行末の改行コードを保持するために重要です。- メイン処理 (
if __name__ == "__main__":
) では、まず基準コンフィグを読み込み、成功すれば現在のコンフィグを取得し、Diffを生成・表示しています。基準ファイルが存在しない場合の処理も考慮しています。
現場での応用と考慮点
この基本的なスクリプトを現場で活用するためには、いくつかの拡張や考慮が必要です。
- 複数の機器への対応:
対象機器リストを定義し、ループ処理やPythonの並列・非同期処理 (
concurrent.futures
,asyncio
) を利用して複数の機器から効率的にコンフィグを取得するように拡張します。Nornirのようなフレームワークは、複数の機器に対する操作やデータ処理を効率的に行う機能を提供しており、このような用途に適しています。 - 認証情報の安全な管理: スクリプト内に直接パスワードなどを記述するのは危険です。環境変数、設定ファイル、よりセキュアなVaultのようなツールから認証情報を取得するように変更が必要です。
- エラーハンドリングとログ: 接続失敗、認証失敗、コマンドタイムアウト、コマンドエラーなど、様々な異常ケースを想定したエラーハンドリングと、問題発生時の原因究明に役立つ詳細なログ出力の実装が重要です。
- Diffの保管とバージョン管理: 生成したDiffや取得したコンフィグファイルを、タイムスタンプや機器名を含めた命名規則で保存し、Gitのようなバージョン管理システムで管理することで、変更履歴の追跡やロールバックの際の参照が容易になります。これはIaCの考え方とも親和性が高いアプローチです。
- Diffの活用:
Diffを生成するだけでなく、その内容に基づいて自動的なアクションを起こすことも可能です。例えば、特定のキーワード(
no shutdown
,permit ip any any
など)が含まれる変更が検知されたら警告を通知したり、設定の逸脱が検知されたら自動的に設定を是正(ロールバックや再適用)したりするワークフローに組み込むことができます。IaCツール(Ansible, SaltStackなど)のネットワークモジュールと組み合わせ、状態のチェックと是正を行う基盤の一部とすることも考えられます。 - APIの利用: 対象機器がRESTConfやNETConfといった標準的なネットワークAPIをサポートしている場合、CLIスクレイピングよりも構造化されたデータを扱いやすいため、APIを利用する方が堅牢なスクリプトになる可能性があります。
まとめ
本記事では、PythonとNetmiko、difflibといったライブラリを活用し、ネットワーク機器の稼働コンフィグを自動的に取得して差分を生成する基本的な手法について解説しました。これにより、ネットワーク構成の変更を自動的に把握し、変更管理や監査の精度を向上させることが可能です。
ここで紹介したスクリプトはあくまで基礎となる部分です。実際の運用環境では、複数の機器への対応、堅牢なエラーハンドリング、認証情報の安全な管理、そして生成したDiff情報の効果的な活用といった観点から、さらなる機能拡張が必要になります。
IaCやCI/CDの文脈でネットワーク自動化を捉える際、ネットワーク機器の現在の「状態」を正確に把握し、意図した「状態」との差分を管理することは、非常に重要なステップです。Pythonは、こうしたネットワークの状態管理や変更検知の自動化を実現するための強力なツールとなります。
Diff自動取得の仕組みを構築することは、ネットワーク自動化の次のステップ、例えばDiffに基づいた自動テストや自動設定是正などへ発展させるための土台ともなります。ぜひ、この手法を参考に、ご自身の環境でのネットワーク自動化を進めてみてください。