[Python][Network] NAPALMを使ってネットワーク機器を制御する

結局触ってみたNAPALM

これまでPythonのpexpectやNetmikoなどを触ってみて、ネットワーク機器の制御がスクリプトベースで出来るんだなぁということを学んだんですが、やっぱり気になってしょうがなくて今回napalmも触ってみました。

当初は参照系の関数を使って遊んでたのですが、出力されるデータ形式がリスト変数だったりして、ちょっと理解するに時間がかかりそうな気がしたので、今回はまぁ、コンフィグを弄る関連の流れを掴んでみようということでそれを先に内容としてまとめました。

やってみたこと

以前、pexpectを使ってDELLスイッチ向けに実装したものを、今回napalmで実装してみました。当時はDELLスイッチが相手で、napalmでは対応してなかったのですが、今回そこはWS-C3750-24TS-Eに変わってますので、バッチリ対応しています。参考までに、pexpectで組んでいたやつの処理概要図を再掲します。

NAPALMのインストール

pipを使用してインストールします。

# pip3 install napalm

依存関係モジュールとして、IOS関連のnapalmモジュールもダウンロードされ、組み込まれます。

実際組んだコード

今回組んだコードは以下のとおりです。

まずはUTM9に対する静的経路を削除する処理(何だかファイル名が変だけどそこはご愛嬌)

■テストコード1(test_napalm_21.py)
import napalm
import sys
from pprint import pprint

driver = napalm.get_network_driver('ios')
device = driver(
    hostname='192.168.100.254',
    username='********',
    password='********',
    optional_args={'secret':'********'} )

print ('Open session: ')
device.open()
print ('OK')

device.load_merge_candidate(filename='addconfig.cfg')
pprint(device.compare_config())
device.commit_config()

print ('Close session: ')
device.close()
print ('OK')

■addconfig.cfgの中身
no ip route 0.0.0.0 0.0.0.0 192.168.100.4

続いて、削除した静的経路を元に戻す処理

■テストコード2(test_napalm_22.py)
import napalm
import sys
from pprint import pprint

driver = napalm.get_network_driver('ios')
device = driver(
    hostname='192.168.100.254',
    username='********',
    password='********',
    optional_args={'secret':'********'} )

print ('Open session: ')
device.open()
print ('OK')

device.load_merge_candidate(filename='reverseconfig.cfg')
pprint(device.compare_config())
device.commit_config()

print ('Close session: ')
device.close()
print ('OK')

■reverseconfig.cfgの中身
ip route 0.0.0.0 0.0.0.0 192.168.100.4

見比べてみれば分かるんですが、ほとんど書いてるPythonスクリプトの中身は同じで、違うのはdevice.load_merge_candidateで指定しているファイル名だけです(てへ)

接続処理

接続処理は以下のようになっています。IOSの場合はTELNET/SSHどちらでも行けるみたいです。このあたりはNetmikoの処理を使っているようなんですが、基本的に一度NAPALMがラッパーとして動いており、定型的なパラメータは「hostname」「username」「password」の3つで、それ以外のパラメータは「optional_args」を追加し、「<パラメータ名>:<パラメータ」と言う形式で列挙していくようです。

driver = napalm.get_network_driver('ios')
device = driver(
    hostname='192.168.100.254',
    username='********',
    password='********',
    optional_args={'secret':'********'} )

ドライバの指定としては、

  • Cisco IOSの場合:ios
  • Juniper EXの場合:junos
  • Routerboard RouterOSの場合:ros

と言う感じで行います(取り敢えず接続確認が取れたものだけ記載しました)。なお、デフォルトではrosモジュールは入ってないので、pipで別途インストールする必要があります。

# pip3 install napalm-ros

書き込み処理については以下の流れで記載していくようです。これは現状IOSしか確認できていません。ドライバによって出来る処理とできない処理があるようです。RouterOSに関してはほとんど実装されていないようです・・・

■接続し、Enable状態にする
device.open()

■指定されたファイルに記載された設定をマージする(実質設定を突っ込む処理)
device.load_merge_candidate(filename='<設定ファイル名>')

■設定を確定する
device.commit_config()

■開いたセッションを閉じる(切断する)
device.close()

実際に実行してみる

実際に実行してみるとこうなりました。まずは元の経路情報です。

Gateway of last resort is 192.168.100.4 to network 0.0.0.0
 103.0.0.0/29 is subnetted, 1 subnets
    :
 (中略)
  :
O E2 192.168.239.0/24 [110/1] via 192.168.100.1, 02:53:03, Vlan10
C 192.168.220.0/24 is directly connected, Vlan20
C 192.168.100.0/24 is directly connected, Vlan10
S* 0.0.0.0/0 [1/0] via 192.168.100.4

test_napalm21.pyを実行

# python3 test_napalm_21.py
Open session:
OK
'+!\n-no ip route 0.0.0.0 0.0.0.0 192.168.100.4'
Close session:
OK

スイッチに入って経路を確認するとこうなっていました。

Gateway of last resort is 192.168.100.1 to network 0.0.0.0
 103.0.0.0/29 is subnetted, 1 subnets
   :
 (中略)
  :
O E2 192.168.239.0/24 [110/1] via 192.168.100.1, 02:54:48, Vlan10
C 192.168.220.0/24 is directly connected, Vlan20
C 192.168.100.0/24 is directly connected, Vlan10
O*E2 0.0.0.0/0 [110/1] via 192.168.100.1, 00:00:51, Vlan10

末尾の行を見るとわかるのですが、デフォルトゲートウェイが切り替わっています。

test_napalm_22.pyを実行

# python3 test_napalm_22.py
Open session:
OK
'+!\n+ip route 0.0.0.0 0.0.0.0 192.168.100.4'
Close session:
OK

経路確認結果は以下のとおりです。

Gateway of last resort is 192.168.100.4 to network 0.0.0.0
 103.0.0.0/29 is subnetted, 1 subnets
   :
 (中略)
  :
O E2 192.168.239.0/24 [110/1] via 192.168.100.1, 02:53:03, Vlan10
C 192.168.220.0/24 is directly connected, Vlan20
C 192.168.100.0/24 is directly connected, Vlan10
S* 0.0.0.0/0 [1/0] via 192.168.100.4

どういう仕組なのか?

処理としては、ソースを見るのが手っ取り早いと思うんですが、ここにそのソースが転がっているようで、その他の処理に関連する関数なんかも確認できます。

また、device.commit_configを外してスクリプトを実行した所、

  • running_configにもstartup_configにも反映がなされていない
  • 経路情報にも変化がない

ということと、なんとかソースから読み取った部分から、恐らくこんな感じで処理が推移しているんだろうと推察しています。

設定が即時反映されるIOS環境に対して、Juniperの様にCandidateとCommitの概念をもってきてるのは非常に面白いなぁと。例えば自動設定投入スクリプトを組んだ場合に、最後の最後で「はい/いいえ」を確認させることが出来るのは面白いなぁと感じました。

もちろん、revertする関数やcompareする関数も準備されており、オペレーションの正確性をより確実なものにする仕組みが、少なくともIOS用ドライバには実装されているといえるんじゃないでしょうか。

ちょいと気になったこと

ちなみに、コレを実装して動かしてる間に、L3スイッチの監視発報が出まして。内容は以下のとおりです。

Jan 22 16:40:38.105: %PARSER-4-BADCFG: Unexpected end of configuration file.

どうやらscpで転送したファイルのEOFがちゃんと入ってないようなメッセージなんですが、実際の所どーなんでしょうかね・・・。これ、バグなのかなぁ?

コレを踏まえてやってみたいこと

現状、インターネットゲートウェイは全部UTM9仮想アプライアンスが賄ってる状態です。インターネット上位回線としてはHomenoc Operator’s GroupとInterlinkの2系統があるんですが、いずれもUTM9が両方を掴んでおり、これが止まると手動でデフォルトゲートウェイを切り替えないと代替経路に切り替わりません。

よって、HinemosでこのUTMを監視し、それがダウンしたら検知してジョブとして上記切替処理を実行するようにすることで、UTM9メンテナンス時の手間を少し省くことが出来るのかなぁなんておもったりしておりまして。

もう少し処理を作り込んで、スクリプトを一つにまとめたり、ログ出力処理などを入れたりして、使用に耐えうる形にすれば、運用に役立つんじゃないかとおもってまして。引き続き処理系統の勉強を進めていきたいなと思う次第です。

あと、まだ記事にできてませんが、参照系の処理も色々叩いてみてる状況です。出力はJSON形式に近いんですが、何故か引用符が全部シングルクォートなんですよね。だからそのままだとブラウザでJSON形式として読めなかったり、ちょいと「?」なところがあったり。実際どのように活用されているのか、もう少し踏み込んで勉強してみようかなとおもってます。

これまで完全にアレルギー状態だった、プログラム言語ですが、実際に手を出してみると、色んな方向性が見えてきて面白いなぁと感じています。やってみてよかった、Python。