Pythonで作るネットワーク自動化基盤:Nornirの活用法
はじめに
システム開発やクラウドインフラの運用において、自動化は不可欠な要素となっています。Infrastructure as Code(IaC)やCI/CDパイプラインの導入が進む中、ネットワーク機器の設定や状態管理も自動化の対象とすることが求められています。
しかし、ネットワーク機器の操作には特有のプロトコル(SSH/CLI, NETCONF, RESTCONFなど)や設定構文があり、開発経験が豊富でもネットワーク機器に不慣れな方にとってはハードルを感じるかもしれません。また、単一のスクリプトで特定のタスクを自動化することは可能ですが、管理対象機器の増加や自動化タスクの複雑化に伴い、メンテナンスが困難になるケースも少なくありません。
このような課題に対し、Pythonでネットワーク自動化を体系的に行うためのフレームワークとして「Nornir」が注目されています。Nornirは、ネットワーク機器のインベントリ管理、並列実行、プラグインによる拡張性などを提供し、より堅牢で再利用可能な自動化スクリプトの開発を支援します。
本記事では、Pythonスキルを活かしてネットワーク自動化をさらに一歩進めたいと考えるエンジニアの皆様に向けて、Nornirの基本的な考え方、環境構築、そして簡単な活用方法について解説します。
Nornirとは
Nornirは、Pythonで書かれたネットワーク自動化フレームワークです。その最大の特長は、自動化対象となるネットワーク機器の情報を一元管理し、定義されたタスクを柔軟に実行できる構造にあります。単なるライブラリとしてではなく、自動化ワークフロー全体を管理するための「基盤」としての機能を提供します。
Nornirを導入する主なメリットは以下の通りです。
- インベントリ管理: ネットワーク機器の接続情報、OSタイプ、グループ分けなどの情報をYAMLファイルや他のソースから一元的に管理できます。
- 並列実行: 複数の機器に対するタスク実行を効率的に並列化できます。
- プラグインアーキテクチャ: 機器への接続方法(SSH, NETCONFなど)、Inventoryソース、タスクの種類(コマンド実行、設定投入、API呼び出しなど)、タスクの実行方法(Runner)などがプラグインとして提供・拡張可能です。
nornir-netmiko
やnornir-napalm
といった人気のプラグインを利用することで、様々な機器やプロトコルに対応できます。 - Pythonネイティブ: 全てPythonコードとして記述するため、既存のPythonライブラリや開発ツールとの親和性が高く、高度なロジックやデータ処理を組み込みやすいです。
Nornirの基本要素
Nornirは主に以下の要素で構成されます。
- Inventory: 管理対象のネットワーク機器に関する情報です。ホスト名、IPアドレス、認証情報、OSタイプ、所属グループなどを定義します。通常、
hosts.yaml
,groups.yaml
,defaults.yaml
といったYAMLファイルで構成されますが、データベースなど他のソースから取得するプラグインもあります。 - Configuration: Nornir自体の動作設定です。Inventoryファイルの場所、Runnerの種類、プラグイン設定などを定義します。通常は
config.yaml
というファイルを使用します。 - Tasks: ネットワーク機器に対して実行したい具体的な処理です。例えば、「特定のコマンドを実行する」「設定ファイルを投入する」「インターフェースの状態を取得する」といった操作がタスクとして定義されます。Nornirのプラグインによって様々なタスクが提供されています。
- Runners: 定義したタスクをどのように実行するかを制御します。タスクを直列に実行するか、並列に実行するかなどを指定できます。デフォルトでは並列実行を行うRunnerが使用されます。
- Result: タスク実行後の結果オブジェクトです。各機器でのタスクの成否や、取得したデータなどが含まれます。
環境構築と最小構成
Nornirを使い始めるための基本的な環境構築手順を説明します。
まず、Pythonがインストールされていることを確認してください。NornirはPython 3.6以上が必要です。次に、pipを使ってNornirとSSH接続を行うためのnornir-netmiko
プラグインをインストールします。
pip install nornir nornir-netmiko pyyaml
pyyaml
はNornirの設定ファイルやInventoryファイルを扱うために必要です。
次に、Nornirの実行に必要な設定ファイルとInventoryファイルを作成します。プロジェクトのルートディレクトリに以下の3つのファイルを作成します。
config.yaml
:
---
inventory:
plugin: HostYaml # YAMLファイルからInventoryをロードするプラグインを指定
options:
host_file: "hosts.yaml"
group_file: "groups.yaml"
defaults_file: "defaults.yaml"
# Runnerの設定(ここではデフォルトの並列Runnerを使用)
# runner:
# plugin: threaded # 並列実行runner
# options:
# num_workers: 20 # 最大並列実行数
# Logging設定(任意)
# logging:
# log_file: "nornir.log"
# level: INFO
hosts.yaml
:
---
device1: # ホスト名
hostname: 192.168.1.10 # 接続先IPアドレスまたはホスト名
platform: cisco_ios # プラットフォームタイプ (Netmikoがサポートするもの)
groups: # 所属グループ
- routers
data: # ホスト固有の追加情報(任意)
location: "Tokyo"
device2:
hostname: 192.168.1.20
platform: juniper_junos
groups:
- routers
- production
# 実際の認証情報はdefaults.yamlまたはgroups.yamlに記述することが推奨されます
# username: your_username
# password: your_password
groups.yaml
:
---
routers: # グループ名
username: your_ssh_username # グループ共通のユーザー名
password: your_ssh_password # グループ共通のパスワード
# port: 22 # グループ共通のポート(任意)
production: # 別のグループ
data:
environment: "prod"
defaults.yaml
:
---
# hosts.yaml や groups.yaml で指定されていない場合に適用されるデフォルト値
username: default_ssh_username
password: default_ssh_password
port: 22
platform: generic # デフォルトのプラットフォーム(必要であれば)
# data:
# some_default_setting: "value"
これらの設定ファイルにより、Nornirはどの機器に対して、どのような認証情報や設定で接続すればよいかを認識します。認証情報はdefaults.yaml
やgroups.yaml
に記述することで、hosts.yaml
に直接書くよりも安全性を高められます。(ただし、本番環境では環境変数やシークレット管理ツールとの連携を検討すべきです。)
Nornirを使った簡単なコマンド実行
環境構築ができたら、実際にNornirを使ってネットワーク機器にコマンドを実行してみましょう。ここでは、すべてのルーター機器に対してshow ip interface brief
コマンドを実行する例を示します。
以下のPythonスクリプトを作成します。
run_command.py
:
from nornir import InitNornir
from nornir_netmiko.tasks import netmiko_send_command
from nornir.core.filter import F # Filterをインポート
def get_interface_status(task):
"""
対象ホストに対して 'show ip interface brief' コマンドを実行し、結果を表示するタスク関数
"""
# netmiko_send_command は nornir-netmiko プラグインによって提供されるタスク
# task.host は現在のタスクが実行されている対象ホストオブジェクト
result = task.run(task=netmiko_send_command, command="show ip interface brief")
# 結果は Result オブジェクトとして返される
# result[0] は通常、タスク関数内の最初の task.run() の結果
# .result に実際のコマンド実行結果(文字列)が格納されている
print(f"--- {task.host.name} のインターフェース状態 ---")
print(result[0].result)
print("-" * 30)
def main():
# config.yaml を読み込んで Nornir オブジェクトを初期化
# auto_enforce_subset=True で、失敗したホストは後続のタスクから自動的に除外される
nr = InitNornir(config_file="config.yaml", auto_enforce_subset=True)
# 特定のグループに属するホストのみを対象とするフィルター
# F() を使用して条件を指定
routers = nr.filter(F(groups__contains="routers"))
# フィルターされたホストに対して、get_interface_status タスク関数を実行
# task=get_interface_status で、この関数が各ホストに対して並列実行される
# name は Nornir の結果出力で見やすくするためのタスク名(任意)
results = routers.run(task=get_interface_status, name="Get Interface Status")
# タスク全体の実行結果を処理
# results オブジェクトには各ホストの結果が含まれる
# for host, result in results.items():
# if result.failed:
# print(f"--- {host} でタスクが失敗しました ---")
# print(result.exception) # 失敗した場合は例外情報などを参照可能
# print("-" * 30)
if __name__ == "__main__":
main()
コードの解説:
InitNornir(config_file="config.yaml")
:config.yaml
を読み込み、Nornirオブジェクト(nr
)を初期化します。これにより、Inventory情報などがロードされます。nr.filter(F(groups__contains="routers"))
: Inventoryから、routers
グループに属するホストのみを抽出します。F()
はフィルタリング条件を構築するためのNornirの機能です。routers.run(task=get_interface_status, name="Get Interface Status")
: フィルターされたホストに対して、get_interface_status
関数をタスクとして実行します。Nornirは内部的にRunner(デフォルトでは並列実行)を使って、対象の各ホストでこの関数を実行します。get_interface_status
関数:- Nornirによって呼び出されるタスク関数は、引数として
task
オブジェクトを受け取ります。このtask
オブジェクトを通じて、対象ホストの情報(task.host
)や、別のNornirタスク(task.run()
)を実行できます。 task.run(task=netmiko_send_command, command="show ip interface brief")
:netmiko_send_command
というNornirタスクプラグインを実行しています。このタスクはNetmikoを使用して、対象ホストに指定されたコマンドを実行します。result = ...
:task.run()
の戻り値はResult
オブジェクトです。ここでは、タスク関数内でtask.run()
を1回だけ呼び出しているので、その結果はresult[0]
に格納されます。実際のコマンド出力はresult[0].result
で取得できます。print(...)
: 各ホストのコマンド出力を標準出力に表示しています。
- Nornirによって呼び出されるタスク関数は、引数として
このスクリプトを実行するには、前述のconfig.yaml
, hosts.yaml
, groups.yaml
(適切な認証情報に置き換えてください)、そしてこのrun_command.py
を同じディレクトリに置き、以下のコマンドを実行します。
python run_command.py
設定が正しければ、定義したルーター機器にSSHで接続し、コマンドを実行した結果がホストごとに表示されるはずです。
実践的な活用と発展
上記の例は非常にシンプルですが、Nornirはこれを基盤として、より複雑な自動化を実現できます。
- 設定のバックアップ:
netmiko_send_command
でshow running-config
などを実行し、その結果をファイルに書き出すタスクを作成すれば、複数機器の設定バックアップを簡単に自動化できます。 - 設定投入:
nornir-netmiko
のnetmiko_send_config
タスクや、nornir-napalm
のconfigure
タスクを使えば、設定ファイルを機器に投入できます。 - 構造化データの活用:
nornir-napalm
プラグインを使用すると、get_interfaces
,get_facts
などの抽象化されたタスクを実行し、ベンダーやOSに依存しない構造化されたデータ(Pythonオブジェクト)を取得できます。これにより、取得したデータを解析してレポートを作成したり、設定の現状をチェックしたりといった処理が容易になります。 - エラーハンドリング:
results
オブジェクトは各ホストの実行成否情報を含んでいます。スクリプト内でif result.failed:
のように条件分岐させることで、失敗したホストに対するリカバリ処理や、エラーレポートの生成などを実装できます。 - IaCツールとの連携: Nornir自体がネットワーク設定のDesired State Configuration(DSC)ツールというよりは、Automation Engineとしての性格が強いです。AnsibleやTerraformのような他のIaCツールと連携し、全体の自動化ワークフローの一部としてNornirを活用する構成も考えられます。例えば、TerraformでクラウドやVMをプロビジョニングした後、Nornirでネットワーク機器間の接続設定を行う、といった連携が可能です。
まとめ
本記事では、Pythonを使ったネットワーク自動化のための強力なフレームワークであるNornirの基本について解説しました。Nornirを使うことで、ネットワーク機器のInventory管理、タスクの並列実行、プラグインによる多様な機器/プロトコル対応などが可能になり、スケーラブルでメンテナンス性の高い自動化基盤を構築できます。
開発経験が豊富で、ネットワーク機器の操作に慣れていない方も、Pythonコードで直感的に自動化タスクを記述できるNornirは非常に扱いやすいツールです。まずは小規模な環境でInventoryファイルと設定ファイル、簡単なタスクスクリプトを作成し、実際にコマンド実行や情報取得から試してみることをお勧めします。
ネットワーク自動化は、IaCやCI/CDを実現する上でますます重要になります。Nornirを学び、実践することで、システムの自動化領域をさらに広げることが可能となるでしょう。今後は、より具体的なタスク例や他のプラグインの活用方法についてもご紹介していく予定です。