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

現場で使えるPython:ネットワーク機器の状態データ収集と可視化基盤連携

Tags: Python, ネットワーク自動化, データ収集, 可視化, 時系列データベース, Netmiko, TTP, InfluxDB

はじめに

システムエンジニアやインフラエンジニアの業務において、サーバーやクラウドインフラの監視、自動化は日常的に行われています。しかし、ネットワーク機器に関しては、CLIによる手動操作や、専用の監視ツールに依存しているケースも少なくありません。特に、開発やIaC(Infrastructure as Code)の文脈でインフラ全体をコードで管理・自動化している環境では、ネットワーク機器の状態もプログラムから効率的に取得し、活用したいというニーズがあります。

本記事では、Pythonを使ってネットワーク機器の現在の状態を示すデータ(インターフェース統計、CPU/メモリ使用率、環境情報など)を自動的に収集し、そのデータを時系列データベースのような外部システムに連携して可視化や分析に活用する方法を紹介します。ネットワーク機器に不慣れでも、Pythonのスキルを活かして、これらのデータを収集・整形し、既存の監視・可視化基盤に統合する実践的なアプローチを提供します。

なぜネットワーク機器の状態データを収集し、外部連携するのか

ネットワーク機器の状態データは、運用において非常に重要です。

これらのデータをCLIで手動取得するのは非効率であり、取得タイミングやフォーマットのばらつきといった問題があります。Pythonで自動化することで、定期的・網羅的にデータを収集し、構造化されたデータとして時系列データベースやストレージに保存することで、後続の処理や可視化が容易になります。

Pythonによるネットワーク機器からのデータ収集手法

ネットワーク機器から状態データを取得する方法はいくつかあります。現場でよく使われるのは、主にCLIコマンドの実行結果を取得する方法と、APIを利用する方法です。

1. CLIコマンド実行結果の取得

多くのネットワーク機器は、SSH経由でCLIコマンドを実行し、その出力を取得できます。この方法は汎用性が高く、様々なベンダーや機種で利用可能です。

PythonからSSH経由でコマンドを実行し、出力を取得するには、以下のようなライブラリが便利です。

Netmikoを使ったコマンド実行の基本的なコード例を示します。

from netmiko import ConnectHandler
import os

# 接続情報(環境変数や安全な設定ファイルから取得することを推奨)
device = {
    'device_type': 'cisco_ios', # ベンダー/OSタイプを指定
    'host': os.environ.get('NETWORK_DEVICE_HOST'),
    'username': os.environ.get('NETWORK_DEVICE_USER'),
    'password': os.environ.get('NETWORK_DEVICE_PASSWORD'),
    'port': 22,
}

# 実行したいコマンド
commands = [
    'show version',
    'show ip interface brief',
    'show environment all',
    'show interface status',
]

# 接続とコマンド実行
try:
    with ConnectHandler(**device) as net_connect:
        print(f"Connected to {device['host']}")
        output_data = {}
        for cmd in commands:
            print(f"Executing command: {cmd}")
            output = net_connect.send_command(cmd)
            output_data[cmd] = output
            print(f"--- Output for '{cmd}' ---")
            # print(output) # デバッグ表示
            print("---------------------------")

    # 取得したデータ(output_data)を処理する
    print("\nData collection complete.")
    # output_data ディクショナリには、コマンドとその実行結果が格納されています。

except Exception as e:
    print(f"An error occurred: {e}")

# 注: 本コードは環境変数から認証情報を取得することを想定しています。
# 実際には、パスワードマネージャーやVaultなどを利用して安全に管理してください。

このコードでは、指定したコマンドの出力を output_data ディクショナリに格納しています。しかし、この時点での出力は単なるテキスト文字列です。このままでは分析や連携に不向きなため、次に示すデータ整形(パース)が必要になります。

2. 取得データのパースと構造化

CLIコマンドの出力は、人間が読むためには適していますが、プログラムで扱うには非構造化データであり扱いにくい形式です。これをJSONやPythonの辞書のような構造化データに変換するプロセスを「パース」と呼びます。

パースにはいくつかの方法があります。

ここでは、Netmikoで取得したCLI出力をTTPを使ってパースする例を示します。

from netmiko import ConnectHandler
from ttp import ttp
import os

# 接続情報(前述の例と同様)
device = {
    'device_type': 'cisco_ios',
    'host': os.environ.get('NETWORK_DEVICE_HOST'),
    'username': os.environ.get('NETWORK_DEVICE_USER'),
    'password': os.environ.get('NETWORK_DEVICE_PASSWORD'),
    'port': 22,
}

# 実行したいコマンド
command = 'show interface status'

# TTPテンプレート(例: show interface status 用の簡易テンプレート)
# このテンプレートは機器の実際の出力に合わせて記述する必要があります。
# 詳細なテンプレートはTTPのドキュメントやコミュニティを参照してください。
ttp_template = """
interface status:
{{ result | macro('column') }}
Port      Name               Status       Vlan       Duplex  Speed   Type
--------- ------------------ ------------ ---------- ------  ------  ----
{{ port }} {{ name | OR(' ') }} {{ status }} {{ vlan }} {{ duplex }} {{ speed }} {{ type }}
"""

# 接続とコマンド実行、パース
try:
    with ConnectHandler(**device) as net_connect:
        print(f"Connected to {device['host']}")
        print(f"Executing command: {command}")
        output = net_connect.send_command(command)
        print(f"--- Raw Output for '{command}' ---")
        print(output)
        print("---------------------------")

        # TTPパーサーインスタンスを作成
        parser = ttp(data=output, template=ttp_template)
        # データをパース
        parser.parse()

        # パース結果を取得
        parsed_data = parser.result(structure="flat_list") # flat_list形式で取得
        print("\n--- Parsed Data ---")
        import json
        print(json.dumps(parsed_data, indent=2))
        print("---------------------")

except Exception as e:
    print(f"An error occurred: {e}")

# 注: TTPテンプレートは対象機器の出力形式に厳密に合わせる必要があります。
# 上記テンプレートはあくまで例です。

この例では、show interface status コマンドの出力をTTPテンプレートを使ってパースし、リスト形式の構造化データとして取得しています。このように構造化されたデータは、後の処理(フィルタリング、集計、外部システムへの書き込みなど)が格段に容易になります。

3. APIの利用 (RESTConf, NETConf, ベンダー独自API)

最近のネットワーク機器は、CLIだけでなくAPI(Application Programming Interface)を提供しているものも増えています。RESTConfやNETConfは標準化されたネットワーク管理プロトコルであり、これらを利用することで構造化されたデータを直接取得したり、設定変更を行ったりできます。ベンダー独自のREST APIを提供している場合もあります。

APIを利用するメリットは、CLI出力のパースが不要であること、プログラムからの操作に適したデータ形式(JSONやXMLなど)で情報が得られることです。

PythonからAPIを利用するには、requests ライブラリなどが一般的です。RESTConfやNETConfに対応したライブラリ(例: ncclient for NETConf)もあります。

APIを利用したデータ収集の例は、機器やAPIの種類によって大きく異なるため、ここでは一般的なREST APIを利用する際の考慮事項に留めます。

APIが利用可能な場合は、CLIよりもAPIの利用を検討するのが現代的なアプローチと言えます。ただし、すべての情報がAPIで取得できるとは限らず、依然としてCLIが必要な場面も多いです。

収集したデータを時系列データベースに連携する

構造化されたデータを収集したら、次にこれをどのように活用するかが重要です。監視や可視化の目的で、時系列データを扱うのに適したデータベース(時系列データベース)に書き込むのが一般的なパターンです。

代表的な時系列データベースには、InfluxDB, Prometheus, OpenTSDB などがあります。これらのデータベースには、Pythonからデータを書き込むためのクライアントライブラリが提供されています。

ここでは、収集したインターフェースの統計データ(例: エラーカウント、廃棄パケット数など)をInfluxDBに書き込む場合のコード例を示します。InfluxDBのPythonクライアントライブラリ influxdb-client を使用します。

事前に influxdb-client ライブラリをインストールしておく必要があります。 pip install influxdb-client

from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
import os
import time
from datetime import datetime, timezone

# InfluxDB 接続情報(環境変数から取得することを推奨)
url = os.environ.get("INFLUXDB_URL")
token = os.environ.get("INFLUXDB_TOKEN")
org = os.environ.get("INFLUXDB_ORG")
bucket = os.environ.get("INFLUXDB_BUCKET")

# ネットワーク機器から取得・パースしたデータ(サンプル)
# 実際には前述の方法で動的に取得・パースしたデータを使用します
# ここでは、show interface status や show interface counters の結果を
# パースして得られたようなデータを想定しています。
# 例: [{'port': 'GigabitEthernet1/0/1', 'status': 'connected', ... , 'input_errors': 10, 'output_discards': 5}, ...]
parsed_interface_data = [
    {'port': 'GigabitEthernet1/0/1', 'status': 'connected', 'vlan': '10', 'speed': '1000', 'input_errors': 10, 'output_discards': 5},
    {'port': 'GigabitEthernet1/0/2', 'status': 'notconnect', 'vlan': '1', 'speed': 'auto', 'input_errors': 0, 'output_discards': 0},
    # ... 他のインターフェースデータ
]

# InfluxDBクライアントの初期化
client = InfluxDBClient(url=url, token=token, org=org)
write_api = client.write_api(write_options=SYNCHRONOUS)

print(f"Connecting to InfluxDB at {url}, writing to bucket '{bucket}' in org '{org}'")

try:
    points_to_write = []
    current_time = datetime.now(timezone.utc) # タイムスタンプはUTCを推奨

    for iface_data in parsed_interface_data:
        port_name = iface_data.get('port')
        if not port_name:
            continue # ポート名がないデータはスキップ

        # Pointオブジェクトを作成
        # Measurement: データの種類(例: interface_metrics)
        # Tags: データを識別・フィルタリングするためのキーバリューペア(例: hostname, port, status)
        # Fields: 計測値(例: input_errors, output_discards, speed_mbps)
        point = Point("interface_metrics") \
            .tag("hostname", device.get('host', 'unknown_host')) \
            .tag("port", port_name) \
            .tag("status", iface_data.get('status', 'unknown')) \
            .tag("vlan", iface_data.get('vlan', 'unknown')) \
            .field("input_errors", int(iface_data.get('input_errors', 0))) \
            .field("output_discards", int(iface_data.get('output_discards', 0))) \
            .field("speed_mbps", int(iface_data.get('speed', '0').replace('auto', '0').replace('a-', '0'))) \
            .time(current_time, WritePrecision.NS) # ナノ秒精度でタイムスタンプを設定

        points_to_write.append(point)

    # データをInfluxDBに書き込み
    if points_to_write:
        write_api.write(bucket=bucket, org=org, record=points_to_write)
        print(f"Successfully wrote {len(points_to_write)} data points to InfluxDB.")
    else:
        print("No data points to write.")

except Exception as e:
    print(f"An error occurred while writing to InfluxDB: {e}")
finally:
    client.close()
    print("InfluxDB client closed.")

# 注: このコードはサンプルデータを使用しており、実際には前述のNetmiko+TTPなどを使って
# ネットワーク機器から取得したデータをパースして `parsed_interface_data` を生成する必要があります。
# また、InfluxDBの接続情報、バケット名、組織名などは適切に設定してください。
# Fieldの値の型に注意し、適切に変換(例: int())してください。

このスクリプトは、サンプルとして用意した構造化されたインターフェースデータを取得し、各インターフェースの情報(ポート名、ステータスなどをタグとして、エラー数や速度などをフィールドとして)をPointオブジェクトに変換し、InfluxDBに書き込んでいます。InfluxDBにデータが格納されれば、Grafanaなどの可視化ツールと連携して、グラフやダッシュボードを作成し、ネットワーク機器の状態を視覚的に把握できるようになります。

実践的な考慮事項

現場でこれらのスクリプトを利用する際には、いくつかの重要な考慮事項があります。

IaC/CI/CDパイプラインにおける位置づけ

Pythonによるネットワークデータ収集と可視化基盤連携は、インフラ自動化全体のライフサイクルの中で重要な役割を果たします。

これらの活動をCI/CDパイプラインに組み込むことで、ネットワークの変更管理や運用効率を大幅に向上させることが可能になります。

まとめ

本記事では、Pythonを使ってネットワーク機器から状態データを収集し、パースして構造化し、時系列データベースに連携する一連の手順とコード例を紹介しました。NetmikoによるCLIコマンド実行、TTPによる出力パース、influxdb-clientによるInfluxDBへの書き込みを取り上げましたが、これらはあくまで一例です。対象機器や目的に応じて、NAPALMや各種APIを利用したり、Prometheusや他のデータストアに連携したりすることも可能です。

Pythonスキルを活かすことで、ネットワーク機器のブラックボックス感を減らし、他のインフラコンポーネントと同様にコードによる管理・自動化の範囲に組み込むことができます。収集したデータを活用することで、ネットワーク運用はよりデータに基づいた、効率的かつプロアクティブなものへと進化します。

今後、収集したデータを元にした分析方法や、Nornirを使った大規模環境での並列処理、あるいは特定のベンダーAPIを使った高度な自動化などについても触れていきたいと思います。