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

現場で役立つPythonネットワーク自動化:機器設定のバックアップとリストア

Tags: Python, ネットワーク自動化, Netmiko, バックアップ, リストア, CLI

はじめに

システムの安定稼働において、ネットワーク機器の設定管理は非常に重要です。設定変更の誤りや機器の障害が発生した場合、迅速に正常な状態へ復旧させるためには、最新かつ正確な設定バックアップが不可欠となります。しかし、多くの機器を手動でバックアップする作業は、時間と手間がかかり、ヒューマンエラーのリスクも伴います。

このような課題に対し、Pythonを用いたネットワーク自動化は非常に有効な解決策となります。Pythonの高い記述性、豊富なライブラリ、そして既存のインフラ自動化ワークフローとの親和性は、ネットワーク設定のバックアップおよびリストア作業を効率化し、信頼性を向上させます。

この記事では、Pythonを活用してネットワーク機器の設定を自動でバックアップし、必要に応じてリストアするための具体的な手法を解説します。特に、Pythonに馴染みがあるものの、ネットワーク機器への直接的な操作に慣れていない方を対象に、現場で役立つ実践的なスクリプト例とともに、そのポイントをご紹介します。

Pythonによるネットワーク機器への接続

ネットワーク機器の設定を操作するには、まず機器に接続する必要があります。一般的に、ネットワーク機器へのCLI(Command Line Interface)接続にはSSH(Secure Shell)またはTelnetが用いられます。現代ではセキュリティの観点からSSHが主流です。

PythonからSSH接続を確立するためのライブラリとしては、paramikoがよく知られています。paramikoは純粋なPython実装のSSHv2プロトコルライブラリであり、SSHクライアントやサーバー、SFTPクライアントとしての機能を提供します。

一方、netmikoparamikoなどのライブラリを内部で使用しつつ、多数のネットワーク機器ベンダー(Cisco, Juniper, Aristaなど)やOSタイプに対応した接続・コマンド実行を抽象化してくれる高レベルライブラリです。機器タイプに応じたプロンプトの判定やページャー(--More--表示)の自動処理など、CLI操作における煩雑な部分を吸収してくれるため、ネットワーク機器自動化スクリプトの開発効率を大幅に向上させます。

この記事では、幅広い機器タイプに対応し、より実践的なnetmikoを中心に解説を進めます。

Netmikoの基本的な使い方

netmikoを使用するには、まずライブラリをインストールします。

pip install netmiko

基本的な接続とコマンド実行は以下のようになります。

from netmiko import ConnectHandler
import yaml # 接続情報を外部ファイルから読み込む場合

# 接続情報を定義(実際には安全な方法で管理してください)
# device = {
#     'device_type': 'cisco_ios', # 機器タイプを指定
#     'host': '192.168.1.1',
#     'username': 'admin',
#     'password': 'password',
#     # 'secret': 'enable_password', # 特権EXECモードへの移行が必要な場合
#     # 'port': 22, # デフォルトは22
#     # 'verbose': True, # デバッグ情報を表示
# }

# 例:接続情報をYAMLファイルから読み込む場合 (devices.yaml)
# ---
# - device_type: cisco_ios
#   host: 192.168.1.1
#   username: admin
#   password: password
# - device_type: juniper_junos
#   host: 192.168.1.2
#   username: admin
#   password: password

try:
    # 複数の機器情報をファイルから読み込む
    with open('devices.yaml', 'r') as f:
        devices = yaml.safe_load(f)

    for device in devices:
        print(f"--- 機器 {device['host']} に接続中 ---")
        # ConnectHandlerを使用して機器に接続
        with ConnectHandler(**device) as net_connect:
            # 特権EXECモードに移行する必要がある場合
            # net_connect.enable()

            # コマンドを実行
            output = net_connect.send_command('show version')
            print(f"--- {device['host']} のバージョン情報 ---")
            print(output)

except Exception as e:
    print(f"接続またはコマンド実行中にエラーが発生しました: {e}")

上記の例では、ConnectHandlerのインスタンスを作成し、send_commandメソッドで任意のCLIコマンドを実行しています。device辞書内のdevice_typeには、機器に応じた正確な値を指定する必要があります(cisco_ios, juniper_junos, arista_eosなど)。対応している機器タイプはnetmikoのドキュメントを参照してください。

ネットワーク機器設定のバックアップ

ネットワーク機器の現在の設定は、「running-config」(実行中の設定)としてメモリ上に保持されています。これを取得し、ファイルとして保存することでバックアップが完了します。多くの機器では、show running-configというコマンドでこの情報を表示できます。

netmikoを使えば、このコマンドの実行と結果の取得は非常に容易です。取得した設定内容はただのテキストデータなので、ファイルとして保存する処理を組み合わせれば、バックアップスクリプトが完成します。

バックアップスクリプト例

import os
from datetime import datetime
from netmiko import ConnectHandler
import yaml

# 接続情報ファイル
DEVICE_LIST_FILE = 'devices.yaml'
# バックアップ保存ディレクトリ
BACKUP_DIR = 'config_backups'

# バックアップディレクトリが存在しない場合は作成
if not os.path.exists(BACKUP_DIR):
    os.makedirs(BACKUP_DIR)
    print(f"ディレクトリ '{BACKUP_DIR}' を作成しました。")

try:
    # 機器リストを読み込み
    with open(DEVICE_LIST_FILE, 'r') as f:
        devices = yaml.safe_load(f)

    for device in devices:
        host = device['host']
        print(f"--- 機器 {host} の設定バックアップを開始 ---")
        try:
            # 機器に接続
            with ConnectHandler(**device) as net_connect:
                # 機器タイプに応じた設定取得コマンド
                # 一般的には 'show running-config' ですが、機器によっては異なる場合があります
                if device['device_type'].startswith('juniper'):
                     command = 'show configuration'
                else:
                     command = 'show running-config'

                print(f"コマンド '{command}' を実行...")
                config_output = net_connect.send_command(command)
                print("コマンド実行完了。")

                # ファイル名を生成 (例: 192.168.1.1_cisco_ios_20231027_103000.cfg)
                timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                # ホスト名の代わりにIPアドレスを使用することが多い
                filename = f"{host}_{device.get('device_type', 'unknown')}_{timestamp}.cfg"
                filepath = os.path.join(BACKUP_DIR, filename)

                # ファイルに設定内容を書き込み
                with open(filepath, 'w') as f:
                    f.write(config_output)

                print(f"設定を '{filepath}' に保存しました。")

        except Exception as e:
            print(f"機器 {host} のバックアップ中にエラーが発生しました: {e}")
        finally:
            print(f"--- 機器 {host} の設定バックアップ終了 ---")

except FileNotFoundError:
    print(f"エラー: 機器リストファイル '{DEVICE_LIST_FILE}' が見つかりません。")
except Exception as e:
    print(f"スクリプト実行中にエラーが発生しました: {e}")

このスクリプトは、devices.yamlというYAMLファイルから接続情報を読み込み、各機器に対してshow running-config(またはshow configuration)コマンドを実行し、その出力をファイルに保存します。ファイル名には機器のIPアドレス、機器タイプ、タイムスタンプを含めることで、管理しやすくしています。

保存された設定ファイルは、Gitなどのバージョン管理システムで管理することで、設定変更履歴の追跡や差分確認が可能になります。

ネットワーク機器設定のリストア

バックアップした設定は、障害発生時や設定変更の取り消しが必要な場合にリストア(復旧)するために使用します。リストア方法は機器によっていくつかの方法がありますが、一般的なのは以下のいずれかです。

  1. CLIから設定をペーストする: 設定ファイルをPC上で開き、SSHセッション経由で機器のコンフィグレーションモードに貼り付けます。設定量が多いと手動では困難です。
  2. SCP/TFTPなどで設定ファイルを転送し、それを読み込む: 設定ファイルを機器本体または外部ストレージに転送し、機器のCLIからそのファイルを読み込むコマンドを実行します。
  3. CLIから設定投入コマンドを使う: copy running-config tftp://server/file の逆のようなコマンドや、一部の機器ではnetmikosend_config_from_fileなどのメソッドが利用できます。

ここでは、netmikoを使用してCLIから設定ファイルを機器に投入する基本的な方法と、ファイル転送して機器側で読み込ませる方法について触れます。

CLIからの設定投入(Netmiko)

netmikosend_config_from_fileメソッドは、指定した設定ファイルを読み込み、その内容を1行ずつ、あるいはまとめて機器のコンフィグレーションモードに投入する機能を提供します。

import os
from netmiko import ConnectHandler
import yaml
import time # 遅延のためのインポート

# 接続情報ファイル
DEVICE_LIST_FILE = 'devices.yaml'
# リストア対象の設定ファイルディレクトリ
RESTORE_CONFIG_DIR = 'restore_configs' # このディレクトリにリストアしたい設定ファイルを置く

# リストア対象の機器ホスト名とファイル名を指定 (例)
# RESTORE_TARGET = {
#     'host': '192.168.1.1',
#     'filename': '192.168.1.1_cisco_ios_20231027_103000.cfg'
# }

# 例:リストア情報をYAMLファイルから読み込む場合 (restore_targets.yaml)
# ---
# - host: 192.168.1.1
#   filename: 192.168.1.1_cisco_ios_20231027_103000.cfg
# - host: 192.168.1.2
#   filename: 192.168.1.2_juniper_junos_20231026_090000.cfg

try:
    # リストア対象リストを読み込み
    with open('restore_targets.yaml', 'r') as f:
        restore_targets = yaml.safe_load(f)

    # 機器リストを読み込み (接続情報取得のため)
    with open(DEVICE_LIST_FILE, 'r') as f:
        devices = yaml.safe_load(f)

    # リストア対象ごとに処理
    for target in restore_targets:
        host = target['host']
        filename = target['filename']
        filepath = os.path.join(RESTORE_CONFIG_DIR, filename)

        # 機器リストから対象機器の接続情報を探す
        device_info = next((item for item in devices if item['host'] == host), None)

        if not device_info:
            print(f"エラー: 機器 {host} の接続情報が '{DEVICE_LIST_FILE}' に見つかりません。スキップします。")
            continue

        if not os.path.exists(filepath):
            print(f"エラー: リストア対象ファイル '{filepath}' が見つかりません。スキップします。")
            continue

        print(f"--- 機器 {host} に設定ファイル '{filename}' をリストア開始 ---")
        try:
            # 機器に接続
            with ConnectHandler(**device_info) as net_connect:
                # コンフィグレーションモードに移行
                # net_connect.config_mode() # send_config_from_fileは自動でモード移行します

                # ファイルから設定を投入
                print(f"設定ファイル '{filepath}' を機器に投入中...")
                # config_file=... でファイルを指定
                # chunk_size=... で一度に送る行数を調整可能 (大きなファイルで有用)
                output = net_connect.send_config_from_file(filepath)
                print("設定投入完了。")
                # 投入結果の出力を表示 (成功/失敗の確認に役立つ)
                print("--- 設定投入結果 ---")
                print(output)
                print("--------------------")

                # 設定投入後、必要に応じて設定を保存 (write memory または copy running-config startup-config)
                # 機器タイプに応じて適切なコマンドを選択
                print("設定を保存...")
                save_commands = ['write memory', 'copy running-config startup-config']
                save_output = net_connect.send_config_set(save_commands)
                print("設定保存完了。")
                print(save_output)

                # 適用された設定を確認するコマンドを実行するなども検討
                # verif_output = net_connect.send_command('show ip interface brief')
                # print(verif_output)


        except Exception as e:
            print(f"機器 {host} へのリストア中にエラーが発生しました: {e}")
        finally:
            print(f"--- 機器 {host} へのリストア終了 ---")

except FileNotFoundError:
    print(f"エラー: 機器リストファイルまたはリストアターゲットファイルが見つかりません。")
except Exception as e:
    print(f"スクリプト実行中にエラーが発生しました: {e}")

このスクリプトは、restore_targets.yamlに記載された機器と設定ファイルの組み合わせを読み込み、対応する機器に接続して指定された設定ファイルを投入します。投入後、write memoryなどのコマンドで設定を保存するステップも重要です。

注意点: 設定のリストアは非常にデリケートな操作であり、ネットワーク全体に影響を及ぼす可能性があります。事前に影響範囲を十分に把握し、検証環境で十分にテストを行ってから本番環境に適用することが強く推奨されます。また、機器によってはCLIからの設定ペーストでは予期せぬ挙動(例: 設定の差分ではなく全体を上書きしてしまうなど)をする場合があるため、機器のマニュアルを確認してください。

SCP/TFTPなどによるファイル転送と読み込み

netmikoはSCPやTFTPといったファイル転送プロトコルを直接サポートしていませんが、paramikoはSCP機能を持っています。設定ファイルをSCPで機器に転送し、その後netmikoを使ってCLIから「このファイルを読み込んで実行中の設定にマージ(または上書き)する」というコマンドを実行するアプローチも考えられます。

import os
from netmiko import ConnectHandler
import paramiko # SCP転送のために使用
import yaml

# 接続情報ファイル
DEVICE_LIST_FILE = 'devices.yaml'
# リストア対象の設定ファイルディレクトリ
RESTORE_CONFIG_DIR = 'restore_configs'
# リストアターゲットファイル
RESTORE_TARGETS_FILE = 'restore_targets_scp.yaml' # SCPでのリストアターゲットリスト

# リストアターゲットファイル例 (restore_targets_scp.yaml)
# ---
# - host: 192.168.1.1
#   filename: 192.168.1.1_cisco_ios_20231027_103000.cfg
#   remote_path: flash:/restore_config.cfg # 機器上の保存先パス

try:
    with open(RESTORE_TARGETS_FILE, 'r') as f:
        restore_targets = yaml.safe_load(f)

    with open(DEVICE_LIST_FILE, 'r') as f:
        devices = yaml.safe_load(f)

    for target in restore_targets:
        host = target['host']
        filename = target['filename']
        local_filepath = os.path.join(RESTORE_CONFIG_DIR, filename)
        remote_path = target['remote_path']

        device_info = next((item for item in devices if item['host'] == host), None)

        if not device_info:
            print(f"エラー: 機器 {host} の接続情報が '{DEVICE_LIST_FILE}' に見つかりません。スキップします。")
            continue

        if not os.path.exists(local_filepath):
            print(f"エラー: リストア対象ファイル '{local_filepath}' が見つかりません。スキップします。")
            continue

        print(f"--- 機器 {host} に設定ファイル '{filename}' をSCP転送開始 ---")

        # SCP転送
        try:
            transport = paramiko.Transport((host, device_info.get('port', 22)))
            transport.connect(username=device_info['username'], password=device_info['password'])
            sftp = paramiko.SFTPClient.from_transport(transport)

            sftp.put(local_filepath, remote_path)
            print(f"ファイル '{filename}' を機器上の '{remote_path}' に転送しました。")

            sftp.close()
            transport.close()

        except Exception as e:
            print(f"機器 {host} へのSCP転送中にエラーが発生しました: {e}")
            continue # 転送失敗したら次のターゲットへ

        print(f"--- 機器 {host} で転送した設定ファイルの読み込みを開始 ---")
        # NetmikoでCLIコマンドを実行して設定を適用
        try:
            with ConnectHandler(**device_info) as net_connect:
                # 機器タイプに応じた設定ファイル読み込みコマンド
                # 例: Cisco IOS 'copy flash:/restore_config.cfg running-config'
                # 例: Juniper Junos 'load override terminal' + 設定内容ペースト or 'load replace terminal' + 設定内容ペースト
                #     SCPの場合は 'load replace flash:/restore_config.cfg' など
                # 機器によってコマンドが大きく異なるため注意が必要です。
                # ここでは例としてCisco IOSライクなコマンドを使用します。
                if device_info['device_type'].startswith('cisco'):
                    load_command = f'copy {remote_path} running-config'
                    print(f"コマンド '{load_command}' を実行中...")
                    output = net_connect.send_command(load_command, expect_string=r'Destination filename \[.*?\]\?', strip_prompt=False, strip_command=False)
                    # コピー先のファイル名入力を求められた場合の応答
                    if 'Destination filename' in output:
                        output += net_connect.send_command('\n', strip_prompt=False, strip_command=False) # Enterキーでデフォルトを受け入れる
                    print("設定ファイル読み込み完了。")
                    print("--- 設定読み込み結果 ---")
                    print(output)
                    print("--------------------")

                    # 設定保存
                    print("設定を保存...")
                    save_commands = ['write memory']
                    save_output = net_connect.send_config_set(save_commands)
                    print("設定保存完了。")
                    print(save_output)

                elif device_info['device_type'].startswith('juniper'):
                     # Juniperの場合はloadコマンドが異なる
                     # 例: load replace flash:/restore_config.cfg
                     load_command = f'load replace {remote_path}'
                     print(f"コマンド '{load_command}' を実行中...")
                     output = net_connect.send_command(load_command)
                     print("設定ファイル読み込み完了。")
                     print("--- 設定読み込み結果 ---")
                     print(output)
                     print("--------------------")

                     # commitコマンドで設定を確定
                     print("設定をcommit...")
                     commit_output = net_connect.send_command('commit')
                     print("設定commit完了。")
                     print(commit_output)

                else:
                     print(f"エラー: 機器タイプ '{device_info['device_type']}' に対するファイル読み込みコマンドは定義されていません。")


        except Exception as e:
            print(f"機器 {host} でのファイル読み込み/設定適用中にエラーが発生しました: {e}")
        finally:
            print(f"--- 機器 {host} でのリストア処理終了 ---")


except FileNotFoundError:
    print(f"エラー: 機器リストファイルまたはリストアターゲットファイルが見つかりません。")
except Exception as e:
    print(f"スクリプト実行中にエラーが発生しました: {e}")

この例では、まずparamikoを使って設定ファイルを機器のストレージ(例: flashメモリ)にSCP転送し、その後netmikoを使って機器にSSH接続し、転送したファイルをrunning-configにコピーまたは読み込むコマンドを実行しています。機器タイプによってファイルパスや読み込みコマンドが異なるため、スクリプトを修正・拡張する必要があります。

この方法の利点は、大量の設定でもCLIセッションのタイムアウトを気にせずファイルを転送できることですが、ファイル転送プロトコル(SCP, TFTPなど)が機器で有効になっている必要があります。

実践的な考慮点と高度なテクニック

ネットワーク設定のバックアップ・リストア自動化を現場で運用する上で、いくつかの実践的な考慮点があります。

エラーハンドリング

ネットワーク機器への接続は、機器の負荷、ネットワークの遅延、認証情報の誤りなど、様々な要因で失敗する可能性があります。スクリプトには適切なエラーハンドリングを実装することが不可欠です。try...exceptブロックを使用して、接続エラー、認証エラー、コマンド実行のタイムアウトなどを捕捉し、エラーメッセージを記録したり、リトライ処理を実装したりすることが望ましいです。

セキュリティ

機器への認証情報はスクリプト中にハードコードせず、環境変数、設定ファイル(アクセス制限をかける)、あるいは専用のパスワード管理ツール(HashiCorp Vaultなど)を使用して安全に管理してください。

差分管理とバージョン管理

バックアップした設定ファイルをGitなどのバージョン管理システムで管理することで、いつ、誰が、どのような設定変更を行ったかを追跡できるようになります。新しいバックアップを取得するたびにリポジトリにコミットすることで、設定の差分を容易に確認でき、問題発生時の原因特定やロールバックに役立ちます。

自動化ワークフローへの組み込み

Pythonスクリプトは単体で実行するだけでなく、他のツールやプロセスと連携させることで真価を発揮します。

まとめ

本記事では、Pythonとnetmikoを中心としたライブラリを活用し、ネットワーク機器の設定を自動でバックアップおよびリストアする基本的な手法について解説しました。

手動でのバックアップ・リストア作業は、多くの機器を管理する現場では大きな負担となり、設定ミスや復旧遅延のリスクを増大させます。Pythonによる自動化は、これらの課題を解決し、運用効率と信頼性を大幅に向上させる有効な手段です。

今回ご紹介したスクリプトは基本的なものですが、これを基盤として、エラー通知機能の追加、複数の機器タイプへの対応強化、設定差分の自動通知、Webインターフェースからの実行など、現場のニーズに合わせて機能を拡張していくことが可能です。

Pythonはネットワーク自動化の強力なツールです。本記事が、皆様の現場におけるネットワーク運用効率化の一助となれば幸いです。