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

現場で役立つPythonネットワーク自動化:複数機器からの情報一括取得と解析

Tags: Python, ネットワーク自動化, Netmiko, Paramiko, 情報取得

システムエンジニアやインフラエンジニアの皆様にとって、日々の運用・保守業務の中でネットワーク機器からの情報取得は避けて通れない作業の一つです。しかし、管理対象となるネットワーク機器の数が増えるにつれて、手動での作業は非効率になり、設定の取得漏れや確認ミスといったヒューマンエラーのリスクも高まります。

この記事では、Pythonのスキルを活かして、複数のネットワーク機器から設定情報や運用状態を一括で取得し、さらに取得したデータを解析・活用するための自動化手法についてご紹介します。ネットワーク機器のコマンド操作に不慣れな方でも、Pythonライブラリを活用することで、効率的かつ正確な情報取得プロセスを構築することが可能です。

なぜネットワーク情報取得を自動化するのか

ネットワーク機器から情報を取得する作業は、以下のような目的で行われます。

これらの作業を手動で行う場合、TelnetやSSHクライアントを用いて一台ずつ機器に接続し、コマンドを実行し、結果をコピー&ペーストするという手順を踏むのが一般的です。しかし、この方法には以下のような課題があります。

ネットワーク情報取得を自動化することで、これらの課題を解決し、作業の効率化、ミスの削減、作業品質の標準化を実現できます。

Pythonを使ったネットワーク情報取得の基本

Pythonを使ってネットワーク機器から情報を取得する最も一般的な方法は、SSHプロトコルを利用することです。多くのエンタープライズ向けネットワーク機器はSSHによるCLIアクセスをサポートしており、PythonからSSHクライアントとして接続し、コマンドを送信して結果を受け取ることができます。

PythonでSSH接続を行うためのライブラリとしては、paramikonetmiko がよく利用されます。

今回は、ネットワーク機器操作に特化しており、より簡単にコマンド実行を実装できる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_typenetmiko が機器の種類を判別し、適切なハンドリングを行うために重要です。対応するベンダーと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)」と呼びます。

テキストデータをパースする方法はいくつかあります。

  1. 正規表現 (Regular Expression): 最も汎用的な方法です。複雑なテキストパターンも記述できますが、コマンド出力のフォーマットが少し変わるだけで正規表現の修正が必要になる場合があります。
  2. 文字列操作: split(), find(), スライスなど、基本的な文字列操作メソッドを組み合わせてパースします。単純なフォーマットには有効ですが、複雑になるとコードが読みにくくなりがちです。
  3. パーシングライブラリ:
    • TextFSM: Googleが開発した、CLI出力から構造化データを抽出するためのテンプレートベースのパーサーです。多くのネットワーク機器ベンダーやコマンドに対応したテンプレートが公開されており、テンプレートを作成・利用することで効率的にパースできます。netmikonapalm と組み合わせて使用されることが多いです。
    • TTP (Template Text Parser): TextFSMと同様にテンプレートを使用しますが、より柔軟な機能を持っています。
    • genie parser: Ciscoが開発したパーサーライブラリです。主にCisco製品のコマンド出力に対応しています。

ここでは、比較的シンプルで応用範囲の広い正規表現を使ったパースの例を示します。先ほどの 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のようなテンプレートベースのパーサーライブラリを検討することをお勧めします。

実践的な考慮点

現場でこれらのスクリプトを運用する上で、いくつか重要な考慮点があります。

まとめ

この記事では、Pythonと netmiko ライブラリを中心に、ネットワーク機器からの情報一括取得と基本的なデータ解析の方法についてご紹介しました。手動では時間と手間がかかる情報収集作業も、Pythonスクリプトを活用することで効率化し、ヒューマンエラーのリスクを低減できます。

今回紹介したスクリプトはあくまで基本的なものであり、実際の運用では認証情報の安全な管理、堅牢なエラーハンドリング、大規模環境に対応するための並行処理や専用フレームワーク(Nornirなど)の導入、そして取得データの高度な解析と活用といった要素が必要になります。

Pythonによるネットワーク自動化は、日々の運用業務を改善する強力な手段です。ぜひ、この記事で紹介した内容を参考に、皆様の現場での自動化に取り組んでいただければ幸いです。