実践ネット自動化スクリプト集

現場で使える!Pythonによるネットワーク機器コンフィグのDiff自動取得

Tags: Python, ネットワーク自動化, コンフィグ管理, Diff, Netmiko

はじめに

システム開発やクラウドインフラの自動化を進める上で、IaC(Infrastructure as Code)の概念は不可欠です。コードとしてインフラ構成を管理し、変更を差分(Diff)として把握・管理することは、安定運用や監査の観点から非常に重要になります。

一方、ネットワーク機器の構成管理においては、依然として手作業やCLIベースの操作が中心となる場面も少なくありません。しかし、ネットワーク構成もまた重要なインフラの一部であり、その変更履歴や現状を正確に把握することは運用上不可欠です。

本記事では、Pythonのスキルを活かし、ネットワーク機器のコンフィグレーション(設定)を自動的に取得し、その差分(Diff)を生成する方法について解説します。これにより、ネットワーク機器の構成変更を自動的に検知・記録し、IaCライクな変更管理の一助とすることが可能になります。

なぜネットワークコンフィグのDiff自動取得が必要か

ネットワーク機器のコンフィグDiffを自動取得することには、いくつかの大きなメリットがあります。

  1. 変更の正確な把握: 手作業による設定変更は、意図しないミスや記録漏れが発生する可能性があります。自動的にDiffを取得することで、実際に何が変更されたのかを正確かつ網羅的に把握できます。
  2. 変更管理と監査: いつ、誰(またはどのスクリプト)が、どのような変更を行ったのかをDiffとして記録することで、変更管理プロセスを強化し、監査要件への対応を容易にします。
  3. トラブルシューティング: ネットワークの不具合が発生した場合、直近のコンフィグ変更点が有力な原因特定の手がかりとなることが多々あります。Diffを迅速に参照できることで、原因特定までの時間を短縮できます。
  4. 設定のバックアップと比較: 定期的にコンフィグを取得し、前回の取得分と比較することで、予期しない設定変更や逸脱(Drift)を検知できます。

PythonによるDiff自動取得の基本的な考え方

Pythonを使ってネットワーク機器のコンフィグDiffを自動取得するには、主に以下のステップを踏みます。

  1. 現在のコンフィグを取得する: ネットワーク機器にログインし、現在の稼働コンフィグ(running-configなど)を取得します。これには、NetmikoやParamikoといったSSHライブラリがよく使われます。機器がAPI(RESTConf, NETConfなど)をサポートしていれば、requestsライブラリなどを用いてAPI経由で取得することも可能です。
  2. 基準となるコンフィグを準備する: 比較の基準となる過去のコンフィグファイルを準備します。これは、前回自動取得して保存しておいたファイルでも良いですし、Gitなどでバージョン管理している理想的な状態のコンフィグでも構いません。
  3. 取得したコンフィグと基準コンフィグを比較する: Pythonの標準ライブラリ difflib などを使用して、取得したコンフィグ文字列(またはリスト化した行)と基準コンフィグ文字列(またはリスト化した行)を比較し、差分情報を生成します。
  4. 差分情報を整形・出力する: 生成された差分情報を、人間が読みやすい形式(Unified Diff形式など)に整形して出力したり、ファイルに保存したり、通知システムに連携したりします。

実装例:Netmikoとdifflibを使う

ここでは、Pythonの代表的なネットワーク自動化ライブラリである Netmiko を使ってコンフィグを取得し、標準ライブラリ difflib で差分を生成する基本的なスクリプト例を示します。

前提条件:

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生成を中断します。")

コード解説:

現場での応用と考慮点

この基本的なスクリプトを現場で活用するためには、いくつかの拡張や考慮が必要です。

まとめ

本記事では、PythonとNetmiko、difflibといったライブラリを活用し、ネットワーク機器の稼働コンフィグを自動的に取得して差分を生成する基本的な手法について解説しました。これにより、ネットワーク構成の変更を自動的に把握し、変更管理や監査の精度を向上させることが可能です。

ここで紹介したスクリプトはあくまで基礎となる部分です。実際の運用環境では、複数の機器への対応、堅牢なエラーハンドリング、認証情報の安全な管理、そして生成したDiff情報の効果的な活用といった観点から、さらなる機能拡張が必要になります。

IaCやCI/CDの文脈でネットワーク自動化を捉える際、ネットワーク機器の現在の「状態」を正確に把握し、意図した「状態」との差分を管理することは、非常に重要なステップです。Pythonは、こうしたネットワークの状態管理や変更検知の自動化を実現するための強力なツールとなります。

Diff自動取得の仕組みを構築することは、ネットワーク自動化の次のステップ、例えばDiffに基づいた自動テストや自動設定是正などへ発展させるための土台ともなります。ぜひ、この手法を参考に、ご自身の環境でのネットワーク自動化を進めてみてください。