実践Python:ネットワーク機器の設定ドリフトを自動検出・レポート
「実践ネット自動化スクリプト集」の記事へようこそ。
システムやインフラの運用において、設定の「ドリフト」、すなわち予期しない設定変更や本来あるべき設定からの逸脱は、セキュリティリスクや障害の原因となり得ます。サーバーやクラウドインスタンスではIaC(Infrastructure as Code)によってこのドリフトを抑制・検知する手法が一般的になりつつありますが、ネットワーク機器においても設定ドリフトの検知は運用管理の重要な課題です。
特に、Pythonを用いたインフラ自動化に慣れているものの、ネットワーク機器の細かなCLI操作には不慣れな方にとって、設定ドリフトを効率的に検知し、その差異を分かりやすく把握する仕組みの構築は有用でしょう。この記事では、Pythonを活用してネットワーク機器の設定ドリフトを自動的に検出し、レポートする基本的な手法とその実装例について解説します。
設定ドリフトとは?なぜ検知が必要なのか
設定ドリフトとは、基準となる設定(本来あるべき設定状態)から、実際の機器の設定が乖離してしまう状態を指します。これは、以下のような様々な要因で発生する可能性があります。
- 人手による誤った設定変更
- 緊急対応時の暫定的な設定変更の放置
- 特定のスクリプトやツールによる意図しない変更
- OSアップグレード等による設定フォーマットの変化
設定ドリフトを放置すると、構成管理の整合性が失われ、障害発生時の原因特定が困難になったり、セキュリティポリシー違反につながったりします。設定ドリフトを定期的に検知し、基準との差異を把握することで、これらのリスクを低減し、運用品質の向上に貢献します。
自動検出の基本的な考え方
設定ドリフトの自動検出は、主に以下のステップで実現できます。
- 基準コンフィグの定義: 機器ごとに「あるべき」設定状態を基準コンフィグとして定義・管理します。これはテキストファイルや構造化データとして保存されます。
- 現在コンフィグの取得: 対象のネットワーク機器から、現在の実行コンフィグレーションを取得します。
- コンフィグの比較: 取得した現在コンフィグと基準コンフィグを比較し、差異(Diff)を検出します。
- レポート生成: 検出した差異を、人間が理解しやすい形式でレポートとして出力します。
- 通知/アクション: 生成したレポートを関係者に通知したり、自動修正などの次のアクションをトリガーしたりします。
この一連のプロセスをPythonスクリプトで自動化します。
Pythonを使った実装例
ここでは、比較的シンプルな実装例として、netmiko
を使って機器からコンフィグを取得し、Python標準ライブラリのdifflib
を用いて基準コンフィグファイルと比較する方法を示します。
前提条件
- Pythonがインストールされていること。
netmiko
ライブラリがインストールされていること (pip install netmiko
)。- 対象のネットワーク機器へのSSH接続が可能なこと。
- 各機器の基準コンフィグファイルがローカルまたはアクセス可能な場所に保存されていること。ファイル名は例えば
hostname.cfg
のように、ホスト名と関連付けておくと便利です。
サンプルコード
import os
import difflib
from netmiko import ConnectHandler
# --- 設定 ---
# ネットワーク機器の接続情報リスト
# 認証情報は別途安全な方法で管理することが推奨されます(後述)
DEVICE_LIST = [
{
'device_type': 'cisco_ios', # 機器タイプに合わせて変更
'host': '192.168.1.100',
'username': 'your_username',
'password': 'your_password',
# 'secret': 'your_enable_password', # enableモードが必要な場合
},
# 複数機器がある場合はここに追加
# {
# 'device_type': 'juniper_junos',
# 'host': '192.168.1.101',
# 'username': 'your_username',
# 'password': 'your_password',
# },
]
# 基準コンフィグが保存されているディレクトリのパス
BASELINE_CONFIG_DIR = './baselines'
# 取得したコンフィグを保存する一時ディレクトリのパス
CURRENT_CONFIG_DIR = './current_configs'
# 差異レポートを保存するディレクトリのパス
REPORT_DIR = './reports'
# --- 前準備 ---
# ディレクトリが存在しない場合は作成
os.makedirs(BASELINE_CONFIG_DIR, exist_ok=True)
os.makedirs(CURRENT_CONFIG_DIR, exist_ok=True)
os.makedirs(REPORT_DIR, exist_ok=True)
# --- 関数定義 ---
def get_current_config(device_info):
"""指定された機器から現在の実行コンフィグを取得する"""
print(f"--- {device_info['host']} からコンフィグを取得中 ---")
try:
# Netmikoで機器に接続
with ConnectHandler(**device_info) as net_connect:
# コンフィグ取得コマンドは機器タイプによって異なる場合があります
# Cisco IOS の場合: 'show running-config'
# Juniper Junos の場合: 'show configuration | display set' など
# NAPALM等を使えばコマンドを抽象化できますが、ここではシンプルにCLIコマンドを指定
if device_info['device_type'] == 'cisco_ios':
command = 'show running-config'
elif device_info['device_type'] == 'juniper_junos':
# Junosの場合はset形式が比較しやすいことが多い
command = 'show configuration | display set'
# 他の機器タイプも必要に応じて追加...
else:
print(f"警告: 機器タイプ {device_info['device_type']} のコンフィグ取得コマンドが未定義です。")
return None
# コマンド実行
output = net_connect.send_command(command)
print("コンフィグ取得完了")
return output
except Exception as e:
print(f"エラー: {device_info['host']} からコンフィグ取得中にエラーが発生しました: {e}")
return None
def load_baseline_config(hostname):
"""指定されたホスト名の基準コンフィグをファイルから読み込む"""
baseline_file = os.path.join(BASELINE_CONFIG_DIR, f'{hostname}.cfg')
if not os.path.exists(baseline_file):
print(f"警告: 基準コンフィグファイルが見つかりません: {baseline_file}")
# ファイルが存在しない場合は、現在のコンフィグを基準として保存することを検討
# ただし、これは初回実行時や基準更新時に限定すべき
return None
print(f"基準コンフィグを読み込み中: {baseline_file}")
with open(baseline_file, 'r') as f:
return f.readlines() # 行リストとして返す
def save_current_config(hostname, config_data):
"""取得した現在のコンフィグを一時ファイルとして保存する"""
current_file = os.path.join(CURRENT_CONFIG_DIR, f'{hostname}_current.cfg')
print(f"現在のコンフィグを一時保存: {current_file}")
with open(current_file, 'w') as f:
f.write(config_data)
return current_file # 保存したファイルのパスを返す
def generate_diff_report(hostname, baseline_lines, current_lines):
"""基準コンフィグと現在のコンフィグの差異レポートを生成する"""
print(f"--- {hostname} のコンフィグ差異を比較中 ---")
report_file = os.path.join(REPORT_DIR, f'{hostname}_diff_report.txt')
if baseline_lines is None:
# 基準コンフィグがない場合は、現在のコンフィグの内容をレポートとして保存
print(f"基準コンフィグが存在しないため、現在のコンフィグ全体をレポートとして保存します: {report_file}")
with open(report_file, 'w') as f:
if current_lines:
f.writelines(current_lines)
else:
f.write("現在のコンフィグを取得できませんでした。\n")
return report_file
# difflib.unified_diff で差異を取得
# fromlines, tolines は行リストである必要がある
# n=0 とすると、差異のある行とその前後0行のみを表示 (コンテキストなし)
diff_lines = list(difflib.unified_diff(
baseline_lines, # 基準コンフィグ (ファイルから読み込んだ行リスト)
current_lines.splitlines(keepends=True), # 現在コンフィグ (文字列を改行で分割し、行リスト+改行コードを保持)
fromfile=f'{hostname}_baseline.cfg',
tofile=f'{hostname}_current.cfg',
lineterm='' # デフォルトの改行コードサフィックスを削除
))
print(f"差異レポートを生成: {report_file}")
with open(report_file, 'w') as f:
if not diff_lines:
f.write(f"--- {hostname} ---")
f.write("\n設定ドリフトは検出されませんでした。\n")
else:
f.write(f"--- {hostname} の設定ドリフト検出 ---")
f.write("\n以下の差異が検出されました (+: 現在コンフィグのみ, -: 基準コンフィグのみ):\n\n")
f.writelines(diff_lines) # 差異行を書き込む
print(f"差異レポート出力完了: {report_file}")
return report_file # 生成したレポートファイルのパスを返す
# --- メイン処理 ---
if __name__ == "__main__":
print("--- ネットワーク設定ドリフト自動検出スクリプトを開始します ---")
for device in DEVICE_LIST:
hostname = device['host'] # 例としてhost名をホスト名とする
current_config = get_current_config(device)
if current_config:
# 現在のコンフィグを一時ファイルに保存
save_current_config(hostname, current_config)
# 基準コンフィグを読み込み
baseline_config_lines = load_baseline_config(hostname)
# 差異レポートを生成
report_file_path = generate_diff_report(hostname, baseline_config_lines, current_config)
# ここでレポートファイルの内容を確認したり、メールで送信したりする処理を追加できます
# 例: 簡単な内容出力
print(f"\n生成されたレポートの内容 (ファイル: {report_file_path}):")
with open(report_file_path, 'r') as f:
print(f.read())
print("-" * 20) # 区切り線
else:
print(f"エラー: {hostname} の処理をスキップします。\n")
print("--- ネットワーク設定ドリフト自動検出スクリプトを終了します ---")
コードの解説と補足
DEVICE_LIST
: 接続対象のネットワーク機器情報をリスト形式で定義します。device_type
はnetmiko
が対応しているものを指定します。認証情報(ユーザー名、パスワード)は直接記述していますが、本番環境では後述の安全な管理方法を必ず検討してください。BASELINE_CONFIG_DIR
: 各機器の基準コンフィグファイルを保存するディレクトリです。事前に手動、または別の自動化スクリプトで取得し、配置しておく必要があります。ファイル名はホスト名に.cfg
などをつけた形式が分かりやすいでしょう。get_current_config
関数:netmiko.ConnectHandler
を使用して機器に接続し、send_command
メソッドで実行コンフィグを取得します。コマンドは機器のOSタイプによって適切なものを選択します (show running-config
やshow configuration | display set
など)。load_baseline_config
関数: 基準コンフィグファイルを読み込み、行リストとして返します。difflib
は行単位での比較を行うため、readlines()
で読み込むのが便利です。save_current_config
関数: 取得した現在のコンフィグを一時ファイルとして保存します。これはデバッグや後からの確認に役立ちます。generate_diff_report
関数:difflib.unified_diff
を使用して、基準コンフィグと現在コンフィグの差異を算出します。unified_diff
は、diff -u
コマンドのようなユニファイド形式の差異を出力します。行の追加 (+
)、削除 (-
) が分かりやすく表示されます。出力された差異を行リストとしてレポートファイルに書き込みます。- メイン処理:
DEVICE_LIST
をループし、各機器に対してコンフィグ取得、基準コンフィグ読み込み、差異レポート生成の処理を実行します。
現場での考慮点と発展的なアプローチ
上記のサンプルコードは基本的な検出処理を示していますが、実際の現場で利用するにはいくつかの考慮点と発展的なアプローチがあります。
1. 認証情報の安全な管理
コード中にパスワードを平文で記述するのは非常に危険です。以下のような方法で安全に管理することを強く推奨します。
- 環境変数: 認証情報を環境変数として設定し、スクリプトからは環境変数を読み込む。
- 設定ファイル: 設定ファイル(YAML, JSONなど)に記述し、ファイル自体へのアクセス権限を厳密に管理する。Ansible Vaultのような暗号化ツールを利用するのも良い方法です。
- シークレット管理システム: HashiCorp VaultやCyberArkなどの専用のシークレット管理システムを利用する。
- SSHキー: パスワード認証ではなく、SSHキー認証を利用する。
2. 複数機器への並列処理とエラーハンドリング
対象機器が多い場合、1台ずつ処理すると時間がかかります。Pythonのconcurrent.futures
モジュールや、ネットワーク自動化に特化したフレームワークであるNornir
を利用することで、複数機器への処理を並列化し、効率を向上させることができます。Nornir
はプラグインによって様々なタスク(Netmikoによるコマンド実行、NAPALMによる構造化データ取得など)を抽象化し、機器のインベントリ管理や並列実行を容易に行えるため、より大規模な自動化に適しています。また、フレームワークを利用することで、個別の機器処理におけるエラーハンドリングも構造的に記述しやすくなります。
3. 基準コンフィグの更新とバージョン管理
基準コンフィグは、設定変更を実施した際に適切に更新される必要があります。手動での更新はミスにつながりやすいため、設定変更自動化のワークフローに組み込むことが理想的です。
- IaCツールとの連携: Ansible, SaltStack, ChefなどのIaCツールでネットワーク設定を管理している場合、これらのツールが管理する設定ファイルを基準コンフィグとして利用します。設定変更はIaCツール経由で行い、IaCツールが管理する最新の設定ファイルを基準とすることで、ドリフト検知の仕組みと設定管理の仕組みを統合できます。
- Gitによるバージョン管理: 基準コンフィグファイルはGitリポジトリで管理し、変更履歴を追跡できるようにします。設定変更を実施するプルリクエスト/マージリクエストの一部として、基準コンフィグの更新も含める運用が良いでしょう。
4. レポート形式と通知方法
生成される差異レポートは、運用担当者が迅速に状況を把握できるよう、分かりやすい形式で出力される必要があります。
- Diff形式: 上記コード例のようにDiff形式は変更箇所が明確で便利です。
- 構造化データでの差異検出: NAPALMのようなライブラリを使用すると、コンフィグをYAMLやJSONといった構造化データとして取得できます。この構造化データ同士を比較することで、Diff形式よりもさらにプログラムで扱いやすい形で差異を検出できます。これは、特定のパラメータのみに着目したドリフト検知に有効です。
- 通知: レポートファイルをファイルとして保存するだけでなく、メール、Slack, Microsoft Teamsなどのコミュニケーションツールに自動通知することで、ドリフトの発生を迅速に関係者に知らせることができます。
5. 定期実行の仕組み
ドリフト検知は定期的に実行することで効果を発揮します。LinuxのcronやWindowsのタスクスケジューラ、あるいはJenkinsやGitLab CI/CDなどのCI/CDツールを用いて、スクリプトを定時または特定イベント(例: 設定変更後)に実行する仕組みを構築します。
IaC/CI/CD文脈での位置づけ
設定ドリフトの自動検出は、インフラ自動化全体のライフサイクルにおいて重要な役割を果たします。
- 設定変更後の確認: IaCツールやスクリプトで設定変更を行った後に、意図した通りに設定が反映されているか、あるいは予期しない変更が発生していないかを確認する自動テスト/検証として利用できます。これはCI/CDパイプラインのステージとして組み込むことができます。
- 継続的なコンプライアンスチェック: 定期的に実行することで、セキュリティポリシーや標準設定からの逸脱(コンプライアンス違反)を継続的にチェックし、是正措置を迅速に行うためのトリガーとすることが可能です。
- 運用監視との連携: ネットワーク機器の状態監視ツールと連携させ、特定のイベント(例: ポートダウン)が発生した際に、関連する設定箇所にドリフトがないか自動的にチェックするような仕組みも考えられます。
まとめ
この記事では、Pythonを使ってネットワーク機器の設定ドリフトを自動的に検出し、レポートする基本的な手法を解説しました。netmiko
によるコンフィグ取得とdifflib
によるテキスト比較は、比較的シンプルにドリフト検知を実現できる方法です。
しかし、実際の運用においては、認証情報の安全な管理、複数機器への効率的な適用(Nornirなどの活用)、基準コンフィグの適切な管理(Git, IaCツール連携)、分かりやすいレポートと通知方法など、様々な要素を考慮する必要があります。
設定ドリフトの自動検出は、ネットワーク運用の品質向上とリスク低減に大きく貢献します。ぜひこの記事で紹介した手法を参考に、現場の課題に合わせた自動化スクリプトの作成に挑戦してみてください。IaCやCI/CDといった broader な自動化戦略の中で、ネットワーク自動化がどのように組み込めるかを考える上でも、こうした基本的な自動化スクリプトの理解と実践は非常に有用となるでしょう。