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

現場で役立つ!Pythonによるネットワーク機器インベントリ自動収集スクリプト

Tags: Python, ネットワーク自動化, インベントリ管理, 機器検出, Netmiko, SNMP

はじめに

システムインフラの自動化を進める上で、対象となるネットワーク機器の情報を正確に把握し、一元管理することは非常に重要です。IPアドレス、ホスト名、OSバージョン、シリアル番号、設置場所といったインベントリ情報は、設定変更、監視、セキュリティ対策など、あらゆる自動化タスクの基盤となります。

手動でのインベントリ収集や更新は、機器数が増えるにつれて大きな負担となり、情報の陳腐化や入力ミスが発生しやすくなります。このような課題に対し、Pythonを活用したネットワーク機器のインベントリ自動収集は、効率的かつ正確な情報管理を実現する有効な手段です。

Pythonは、豊富なライブラリと高い汎用性により、ネットワーク機器との対話やデータ処理を柔軟に行うことができます。本記事では、Pythonを用いてネットワーク機器を検出し、インベントリ情報を自動で収集するための具体的な手法とスクリプト例をご紹介します。インフラ自動化に長けているものの、ネットワーク機器の操作に不慣れな読者の皆様に向けて、Pythonスキルを活かせる実践的なアプローチを解説いたします。

ネットワーク機器インベントリ収集の基本的なアプローチ

ネットワーク機器のインベントリ情報を収集するには、いくつかの基本的なアプローチがあります。Pythonでこれらを組み合わせることで、網羅的かつ効率的な収集スクリプトを構築できます。

  1. 機器の検出:

    • 特定のIPアドレス範囲に対してPingを送信し、応答がある機器を特定します。
    • 一般的なネットワークプロトコルのポート(SSH: 22, Telnet: 23, SNMP: 161, HTTPS: 443など)への接続を試行し、アクティブな機器やサービスを検出します。
    • ARPテーブルやMACアドレステーブルを利用して、通信している機器の情報を収集します。
  2. インベントリ情報の取得:

    • 検出した機器に対し、SSHやTelnetで接続し、CLIコマンドを実行して必要な情報を取得します。(例: show version, show inventory, show ip interface briefなど)
    • SNMPプロトコルを使用して、MIB(Management Information Base)から機器情報(sysDescr, sysObjectID, ifDescrなど)を取得します。
    • 機器がサポートしていれば、RESTConfやNETConfといったネットワーク機器向けのAPIを利用して構造化されたデータを取得します。
  3. データの整形と保存:

    • 収集した生データ(CLI出力など)を、必要なインベントリ項目(ホスト名、OS、シリアル番号、インターフェース情報など)に解析・整形します。
    • 整形したデータを、CSV, JSON, データベース, またはCMDBツールが取り込める形式で保存します。

Pythonによる機器検出のスクリプト例

ここでは、Python標準ライブラリや一般的なライブラリを使った簡単な機器検出の例を示します。

1. Pingによる疎通確認

特定のIPアドレスに対してPingコマンドを実行し、応答があるかを確認する基本的な方法です。

import subprocess
import platform
import os

def check_ping(ip_address):
    """
    指定されたIPアドレスにPingを実行し、応答があればTrueを返します。
    """
    # OSによってpingコマンドのオプションが異なるため調整
    param = '-n 1' if platform.system().lower() == 'windows' else '-c 1'
    command = ['ping', param, ip_address]

    try:
        # subprocess.runでコマンドを実行し、出力をキャプチャしない
        # check=Trueで非ゼロの終了コードの場合はCalledProcessErrorを送出
        # timeoutでタイムアウトを設定
        subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=1)
        return True
    except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
        return False
    except FileNotFoundError:
        print(f"Error: 'ping' command not found. Please ensure it's in your PATH.")
        return False

# 例:特定のサブネットの機器をスキャン
subnet = "192.168.1."
print(f"Scanning subnet: {subnet}...")
active_hosts = []
for i in range(1, 255): # 1から254までスキャン
    ip = f"{subnet}{i}"
    if check_ping(ip):
        print(f"{ip} is reachable.")
        active_hosts.append(ip)
    # else:
    #     print(f"{ip} is unreachable.") # 疎通しないIPも表示する場合はコメント解除

print(f"\nActive hosts found: {active_hosts}")

解説:

このスクリプトは基本的な疎通確認に過ぎませんが、アクティブなIPアドレスリストを作成する出発点となります。

2. SSHポートへの接続試行

多くのネットワーク機器は管理にSSHを使用します。SSHポート(TCP 22)への接続可能性を確認することで、管理可能なネットワーク機器を絞り込むことができます。paramikoライブラリを使用すると、SSH接続の試行が容易です。

import paramiko
import socket
import threading
from queue import Queue

def check_ssh_port(ip_address, port=22, timeout=0.5):
    """
    指定されたIPアドレスのSSHポートに接続を試行し、接続できればTrueを返します。
    """
    try:
        # ソケットを作成し、タイムアウトを設定
        sock = socket.create_connection((ip_address, port), timeout=timeout)
        sock.close()
        return True
    except (socket.timeout, ConnectionRefusedError, OSError):
        # 接続タイムアウト、接続拒否、ネットワーク関連のエラーを捕捉
        return False
    except Exception as e:
        # その他の予期しないエラー
        print(f"Error checking SSH on {ip_address}:{port} - {e}")
        return False

# 例:アクティブなホストリストに対してSSHポートを確認(並列処理)
ip_list_to_check = active_hosts # 上記Pingスクリプトで取得したリストを使用
ssh_enabled_hosts = []
print(f"\nChecking SSH port (22) on {len(ip_list_to_check)} hosts...")

# 並列処理のためのQueueとスレッド
q = Queue()
for ip in ip_list_to_check:
    q.put(ip)

def worker():
    while not q.empty():
        ip = q.get()
        if check_ssh_port(ip):
            print(f"{ip}: SSH port 22 is open.")
            ssh_enabled_hosts.append(ip)
        # else:
        #     print(f"{ip}: SSH port 22 is closed or unreachable.")
        q.task_done()

# スレッドの作成と開始
threads = []
num_threads = 10 # 同時実行するスレッド数
for _ in range(num_threads):
    t = threading.Thread(target=worker)
    t.daemon = True # メインスレッド終了時に一緒に終了
    threads.append(t)
    t.start()

# キューの全てのタスクが完了するのを待つ
q.join()

print(f"\nSSH enabled hosts: {ssh_enabled_hosts}")

解説:

Pythonによるインベントリ情報の取得

SSHポートが開いていることが確認できた機器に対して、実際に接続して情報を取得します。ここではnetmikoライブラリを使用するのが最も一般的で効率的です。netmikoは多数のベンダーのCLIに対応しており、コマンド実行結果のパースも容易にします。

Netmikoを使ったCLIコマンド実行

netmikoを使用するには、事前にインストールが必要です(pip install netmiko)。

from netmiko import ConnectHandler
import json
import yaml # YAML形式で認証情報を管理することも多い
import os

# 認証情報を別途ファイル等で管理することを推奨します
# 例: credentials.yaml
# devices:
#   cisco_ios:
#     device_type: cisco_ios
#     port: 22
#     username: admin
#     password: password123
#     secret: enable123

def get_device_info(ip_address, device_type, username, password, secret=None):
    """
    Netmikoを使って機器に接続し、基本情報を取得します。
    """
    device_params = {
        'device_type': device_type, # 例: 'cisco_ios', 'arista_eos', 'juniper_junos'など
        'host': ip_address,
        'username': username,
        'password': password,
        'secret': secret, # 特権EXECモードへのパスワード(必要な場合)
        'port': 22,
        'timeout': 10, # 接続タイムアウト
        'global_delay_factor': 2 # コマンド実行間の遅延調整(必要な場合)
    }

    try:
        print(f"Connecting to {ip_address}...")
        with ConnectHandler(**device_params) as net_connect:
            # 特権EXECモードへ遷移(必要な場合)
            if secret:
                 net_connect.enable()

            # インベントリ情報を取得するコマンドを実行
            # ベンダーやOSによってコマンドは異なります
            commands = [
                'show version',
                'show inventory' # または別のインベントリ表示コマンド
                # 他に取得したい情報があればコマンドを追加
                # 'show ip interface brief',
                # 'show running-config'
            ]

            device_info = {ip_address: {}}
            for cmd in commands:
                print(f"  Executing command: {cmd}")
                output = net_connect.send_command(cmd)
                device_info[ip_address][cmd] = output

            print(f"Successfully got info from {ip_address}.")
            return device_info

    except Exception as e:
        print(f"Failed to get info from {ip_address}: {e}")
        return {ip_address: {"error": str(e)}}

# 例:SSHが有効なホストリストに対して情報収集
# 実際には、各IPに対応するデバイスタイプや認証情報が必要です
# ここではSSH_enabled_hostsリストと、仮の認証情報・デバイスタイプを使用
inventory_data = {}
# 実際の運用では、IPと認証情報、デバイスタイプを紐付けたリストや設定ファイルを用意します
# 例: [{"ip": "192.168.1.10", "type": "cisco_ios", "user": "admin", "pass": "pass", "secret": "enable"}, ...]
dummy_inventory_targets = [
    {"ip": ip, "type": "cisco_ios", "user": "dummy_user", "pass": "dummy_password", "secret": "dummy_enable_password"}
    for ip in ssh_enabled_hosts # 上記スクリプトで取得したSSH有効ホストリスト
]

# dummy_inventory_targets が空の場合は処理をスキップ
if not dummy_inventory_targets:
    print("\nNo SSH enabled hosts found to collect inventory.")
else:
    print("\nCollecting inventory information...")
    for target in dummy_inventory_targets:
        info = get_device_info(target['ip'], target['type'], target['user'], target['pass'], target.get('secret'))
        inventory_data.update(info)

    # 収集したデータの表示(またはファイル保存)
    print("\n--- Collected Inventory Data ---")
    print(json.dumps(inventory_data, indent=2))

    # データの保存例 (JSON形式)
    # with open('network_inventory.json', 'w') as f:
    #     json.dump(inventory_data, f, indent=2)
    # print("\nInventory data saved to network_inventory.json")

解説:

実践的な考慮点:

SNMPを使ったインベントリ情報の取得

機器がSNMPをサポートしている場合、SNMPプロトコルを使ってより構造化されたインベントリ情報を取得できます。pysnmpなどのライブラリを使用します(pip install pysnmp)。

from pysnmp.hlapi import *

def get_snmp_info(ip_address, community='public', oid='1.3.6.1.2.1.1.1.0'): # sysDescr.0
    """
    指定されたIPアドレスからSNMPを使って情報を取得します。
    デフォルトではsysDescr (MIB-2のシステム説明) を取得します。
    """
    iterator = getCmd(
        SnmpEngine(),
        CommunityData(community, mpModel=0), # v1の場合はmpModel=0
        UdpTransportTarget((ip_address, 161)),
        ContextData(),
        ObjectType(ObjectIdentity(oid))
    )

    errorIndication, errorStatus, errorIndex, varBinds = next(iterator)

    if errorIndication:
        print(f"SNMP Error on {ip_address}: {errorIndication}")
        return None
    elif errorStatus:
        print(f"SNMP Error on {ip_address}: {errorStatus.prettyPrint()} at {errorIndex and varBinds[int(errorIndex) - 1][0] or '?'}")
        return None
    else:
        # varBindsはOIDと値のリスト
        for varBind in varBinds:
            # OIDと値を文字列で返す
            return f"{varBind[0].prettyPrint()} = {varBind[1].prettyPrint()}"
        return None # 結果がない場合

# 例:アクティブなホストリストに対してSNMP情報を取得
# 実際には、SNMPコミュニティ名や取得したいOIDを指定する必要があります
snmp_enabled_hosts = ssh_enabled_hosts # SSH有効ホストリストを流用(仮)
snmp_inventory_data = {}

if not snmp_enabled_hosts:
     print("\nNo hosts found to check SNMP.")
else:
    print("\nCollecting SNMP information (sysDescr)...")
    # 実際には、各ホストのコミュニティ名を知っている必要があります
    # ここでは全てのホストで 'public' を試行 (非推奨: 実際の環境ではセキュリティリスク)
    for ip in snmp_enabled_hosts:
        sysdescr = get_snmp_info(ip, community='public', oid='1.3.6.1.2.1.1.1.0')
        if sysdescr:
            print(f"{ip}: {sysdescr}")
            snmp_inventory_data[ip] = {"sysDescr": sysdescr}
        # else:
        #     print(f"{ip}: Failed to get sysDescr via SNMP.")

    print("\n--- Collected SNMP Inventory Data ---")
    print(json.dumps(snmp_inventory_data, indent=2))

解説:

実践的な考慮点:

収集したインベントリデータの活用と管理

収集したインベントリデータは、単に集めるだけでなく、その後の自動化や運用プロセスで活用できる形式で管理することが重要です。

まとめ

本記事では、Pythonを使用してネットワーク機器の検出からインベントリ情報の自動収集・管理までを行うための基本的なアプローチと実践的なスクリプト例をご紹介しました。PingやSSHポートスキャンによる機器検出、Netmikoを使ったCLIからの情報取得、SNMPによる情報収集といった手法を組み合わせることで、手動では困難な大規模なインベントリ収集を効率化できます。

スクリプトを現場で活用する際には、認証情報の安全な管理、様々なベンダー/OSへの対応、取得データの適切なパースと構造化、そして収集したデータの永続的な管理(CMDB連携など)が重要な考慮点となります。

Pythonはネットワーク自動化のための強力なツールです。今回ご紹介したスクリプトはあくまで基本的な例ですが、これらを基盤として、皆様の環境や要件に合わせたより高度なインベントリ自動収集システムを構築していただければ幸いです。