第3回: ネットワーク自動化開発実践 - Jupyter Notebook で Cool なデモをしよう

# はじめに

この記事はシスコの同志による Advent Calendar の一部として投稿しています

- 2017年版: https://qiita.com/advent-calendar/2017/cisco
- 2018年版: https://qiita.com/advent-calendar/2018/cisco
- 2019年版: https://qiita.com/advent-calendar/2019/cisco (<<< 今年)

私自身は、ネットワーク自動化開発についての[連載を2017年に宣言](https://qiita.com/radiantmarch/items/9df086002ee3f4f334d2) してから 3回目の投稿となります（1年に1記事というところはどうか触れないでください）。

今回は、Jupyter Notebook を使ってネットワーク自動化のデモをすることを考えてみたいと思います。


# ネットワーク自動化をデモしたい

ネットワーク自動化を推進するためには、その手法や効果をデモしていろんな人々にみせたい。ところがネットワークエンジニアとしては言うのが悲しいことですが、ネットワークのデモというのはそもそも、地味です。

私自身は、CLI コンソール画面を映して、「ほら、Ping が通りました！」 というデモを何度やってきたことでしょうか。

現場のエンジニアの方は見た目よりも本質的な技術のところを理解してくださることが多いのですが、ビジネス部門の方へのデモや、イベントなどで一般の方へのデモとなると、どうしても見た目のインパクトに欠けてしまいます。

かといって、デモのために専用の Web ポータルを自分で Javascript を書いて用意する、というのもネットワークエンジニアにとっては辛い作業です（最近はコーディング能力的にはできる方も多いですが、本質的な作業でない割に手がかかる、という意味で）。もう少し手軽に見栄えのよいデモをすることはできないものでしょうか？

そんなときに、ネットワーク自動化を社内や業界でアピール・推進したい我々にとってありがたい武器になるのが今回使う Jupyter Notebook です。もちろん、自動化に限らずいろんなネットワーク製品のデモに活用できます。


# Jupyter Notebook とは

Jupyter Notebook はオープンソースの [Project Jupyter](https://jupyter.org/) の成果物の一つです。最近はデータサイエンスや機械学習の分野でもデータ分析の一次的なツールとして活用されている人気のソフトウェアです。

簡単に説明すると、Pythonの対話環境である iPython をブラウザ上で実行しようという目的で開発され、その後改良が加えられてきました。現在では Python だけでなくほかのプログラミング言語にも対応しています。

今回はこの Jupyter Notebook と、[前回](https://qiita.com/radiantmarch/items/936b43f32210e4689179)ご紹介した PyATS ライブラリを使ってネットワーク自動化のデモをするところまでを目標にします。


# Jupyter Notebook のインストール

Jupyter Notebook は pypi から簡単にインストールして利用可能です。

今回私が利用している環境は以下の通りです。お手元の環境が異なる場合は、適宜変更しながら実施してみてください。

- Macbook Pro 10.14.6
- Python 3.6.8
- Jupyter-notebook 5.7.4

pip コマンドで jupyter モジュール一式をインストールします

```bash
$ python -V
3.6.8
$ pip install jupyter
```

pip 完了後、以下のコマンドで jupyter-notebook を起動します

```
jupyter-notebook
```

デフォルトのブラウザが開き、Jupyter Notebook の操作画面が表示されます

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/95c3c914-931e-76e1-214e-f92ab6dee3d4.png)

まずは右側の New ボタンを押して Python3 を選択します

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/c35fef5b-b50a-7b38-0206-021fadbb1d1e.png)

これで Python3 がカーネルとして動く Notebook が開きます。この画面上でインタラクティブに Python を実行することができるのです！

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/04eae86c-b9b5-b193-34f0-a4327aec411a.png)


# Jupyter Notebook の操作

この記事のゴールはこの Jupyter Notebook の画面の上で自動化のデモを実行しようというものになります。が、その前に Jupyter Notebook 自体の操作に簡単に慣れておきましょう。

画面上には In [] などと書かれた、ユーザが文字入力可能なエリア（=セル）が表示されています。ここに Python のコマンドを書いて、メニューから Run コマンド (または Shift + Enter) を押すと、画面上に Python コードの実行結果が表示される仕組みです

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/f9e6c712-8d2c-31da-1aef-e3a3b7644019.png)

上の通り、`import` コマンドでモジュールを読み込んだり、数値に代入したりできます。そして `print()` コマンドの結果が出力として画面に表示されていることがわかります。

セルの内容はあとから自由に書き換えて再実行することができます。こうして試行錯誤しながらインタラクティブにコーディングができるのが Jupyter Notebook の強みです。

画像をインライン表示させることもできます。

例:
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/056be610-ff81-5981-5af5-e8744c205a29.png)

実は Jupyter Notebook は Markdown 方式での表記もサポートされていて、ノートという名前の通り、コードに加えて様々な文章を記載することができます。セルを選択してメニューから Markdown を選択します。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/16c64d01-c8d0-332f-0c88-ba4e532ad4f3.png)

例えば、セルに以下のように記述して Shift-Enter を押すと

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/b0f28ec3-126b-1fc0-ba45-7aa263f732df.png)

以下のように画面に表示されます

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/9df98ded-af5b-c431-1535-d5e7d3fa7f7c.png)


# Jupyter Notebook 上からネットワーク機器を操作する

いよいよネットワーク機器の操作を行ってみましょう。[前回の記事](https://qiita.com/radiantmarch/items/936b43f32210e4689179)で、 PyATS/Genie を使って Python コードでネットワーク機器にアクセスする方法を紹介しました。これを応用します。

先に、Jupyter Notebook を動かしているのと同じ PC で pyats & genie をインストールしておきましょう。これも pip でインストール可能です

```
$ pip install pyats genie
```

これで pyats/genie の各種ライブラリが使えるようになります。これを使って実際にルータに接続してみます。今回は IOS ルータを用意し、telnet アクセスができるようにしておきます (Appendix 参照)。

Jupyter Notebook に戻ってコードを書いてみます。まずルータに接続するためのテストベッド情報を記述して読み込ませます。以下では telnet で接続していますが ssh でも問題ありません。

```python
from pyats.topology import loader

testbed_file = "testbed.yaml"

testbed = """
devices:
    CSR1kv-R1:
        type: router
        os: iosxe
        credentials:
            default:
                password: admin
                username: admin
            enable:
                password: admin
        connections:
            cli:
                protocol: telnet
                ip: 10.x.x.x
                port: 10021
"""

with open(testbed_file, 'w') as f:
    f.write(testbed)

tb = loader.load(testbed_file)
r1 = tb.devices['CSR1kv-R1']
```

これを Jupyter Notebook 上のセルに記述して実行します。

これで、`r1` というオブジェクトをデバイスとして操作できるようになりました。r1 に接続してコマンドを実行してみましょう。

```python
r1.connect(learn_hostname=True)
out = r1.execute('show ip int br')
```

Jupyter Notebook での実行結果は、以下のようになります。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/26f103af-66cb-f8c3-09ae-979eea581f2a.png)
（中略）
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/f8552967-dba4-d69c-b91b-e4ed287879ba.png)

このように PyATS の Unicon モジュールを使ってデバイスに接続して、CLI セッション上でコマンドを実行することができました。標準出力の結果が上記のように実行と同時に出力されます。上のコードでは `out` 変数に出力結果を代入しました。

```
print(out)
```

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/1b5d42a8-6054-ddd3-f2c5-0f710c7e363a.png)


# Jupyter Notebook 上での結果の整形

結果が変数として手に入ってしまえば、あとは Python を使って結果をさまざまな形で処理することができます。

デモや検証データの整理といった用途のためには、結果をできるだけ美しく表示したいところです。

たとえば前のセクションで `out` 変数に入ったコマンド出力結果をテーブル形式で表示したいとします。統計処理でよく使われる pandas モジュールを使えば以下のように書けます。

```
import pandas as pd

table = [ x.split() for x in out.splitlines() ]

df = pd.DataFrame(table[1:], columns=table[0])
df
```

Jupyter Notebook では、Pandas のデータフレームを自動的に HTML テーブルに表示することができます。実行結果は、以下のようになります。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/1ac1a166-1981-8a30-508b-d9871435dcea.png)

このような表示ができると、単にターミナル上のテキストを見せるよりも少しは見栄えのするデモになりそうな気がしてきませんか？

同様に pandas を使って、もう少し検証で使いそうな処理を試してみたいと思います。今度は `show interface` の出力を PyATS のツールである Genie を使ってパースしてみます。

```python
from genie.conf import Genie

tb = Genie.init(testbed_file)
r1 = tb.devices['CSR1kv-R1']

r1.connect()
out = r1.parse('show interfaces')

from pprint import pprint 
pprint(out)
```

結果は以下のように show interfaces の結果が自動的にパースされディクショナリ形式で保存されました。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/a87b4fe4-77fe-9edb-3bfe-75a512253ef7.png)

インターフェイスごとの input packets / output packets を読み取ってグラフにしてみましょう。上記で使った pandas モジュールに加えて matplotlib モジュールも利用します。

サンプルコード:

```
%matplotlib inline

import matplotlib
import pandas as pd

table = []

intf_list = ['GigabitEthernet1', 'GigabitEthernet2', 'GigabitEthernet3']
headers = ['intf', 'in_pkts', 'out_pkts']

for k,v in out.items():
    for intf in intf_list:
        if intf == k:
            line = [
                k,
                v.get('counters').get('in_pkts'),
                v.get('counters').get('out_pkts'),
            ]
            table.append(line)

df = pd.DataFrame(table, columns=headers)
df
```

結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/dadaa48a-5175-d485-cc5c-645314300909.png)

これをグラフにプロットしてみましょう

```
%matplotlib inline
df.plot()
```

結果:

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/5fc2c018-8efd-85b8-09fa-583b05df6b40.png)

このように Jupyter Notebook を使えば、ネットワークデバイスから取得したデータを Pandas で処理して、テーブルデータは HTML で表示、数値データは matplotlib でグラフ表示、など工夫次第で様々な表現ができることがわかります。


# ネットワーク自動化のデモ

本記事のゴールである、ネットワーク自動化デモを Jupyter Notebook 上に書いてみます。

シナリオとして以下のような簡単なものを考えてみました

- r1, r2 の２台の IOS ルータが接続されている
- r1, r2 の２台の疎通性があることを確認する
- ２台に OSPF 設定を投入する
- OSPF ネイバーが Up することを確認する

以下がJupyter Notebook 上のコードです。これを記述した上でメニューから Cell > Run All を実行すると、自動的にコードが上から実行され、結果が画面上に表示されていきます。

## r1, r2 の２台の IOS ルータが接続されている

```python
testbed_file = "testbed.yaml"

testbed = """
devices:
    CSR1kv-R1:
        type: router
        os: iosxe
        credentials:
            default:
                password: admin
                username: admin
            enable:
                password: admin
        connections:
            cli:
                protocol: telnet
                ip: 10.71.157.163
                port: 10021
    CSR1kv-R2:
        type: router
        os: iosxe
        credentials:
            default:
                password: admin
                username: admin
            enable:
                password: admin
        connections:
            cli:
                protocol: telnet
                ip: 10.71.157.163
                port: 10022
"""

with open(testbed_file, 'w') as f:
    f.write(testbed)
    
```



```python
from genie.conf import Genie

tb = Genie.init(testbed_file)
r1 = genie_tb.devices['CSR1kv-R1']
r2 = genie_tb.devices['CSR1kv-R2']

r1.connect()
r2.connect()
```


## r1, r2 の２台の疎通性があることを確認する


```python
r1.connect()
r2.connect()

r1_out = r1.execute('ping 10.0.12.2')
r2_out = r2.execute('ping 10.0.12.1')

print('----------------------')
print('Results')
print('----------------------')

if '!!!!' in r1_out.splitlines()[2]:
    print('r1 to r2: pass')
    
if '!!!!' in r2_out.splitlines()[2]:
    print('r2 to r1: pass')
```


## ２台に OSPF 設定を投入する

### 事前確認

```python
r1_ospf_out = r1.execute('show ip ospf neighbor')
r2_ospf_out = r2.execute('show ip ospf neighbor')

table = [ x.split() for x in r1_ospf_out.splitlines() ][1:]
table[0][0:2] = [' '.join(table[0][0:2])]
table[0][3:5] = [' '.join(table[0][3:5])]
table[1][2:4] = [' '.join(table[1][2:4])]

df = pd.DataFrame(table[1:], columns=table[0])
df
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/97c77baa-724a-ee8b-d3d3-fcdb3c49f614.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/682fbf9b-f80f-ca8d-fc9e-8e72faae2a05.png)



### 設定実行

```python
r1_ospf_config = """
!
router ospf 1
 router-id 10.0.255.1
 network 10.0.12.0 0.0.0.255 area 0
!
interface Gi2
 ip ospf network point-to-point
"""

r2_ospf_config = """
!
router ospf 1
 router-id 10.0.255.2
 network 10.0.12.0 0.0.0.255 area 0
!
interface Gi2
 ip ospf network point-to-point
"""


genie_r1.configure(r1_ospf_config)
genie_r2.configure(r2_ospf_config)
```

## OSPF ネイバーが Up することを確認する


```python
result = False

while not result:
    r1_ospf_out = genie_r1.parse('show ip ospf neighbor')
    r2_ospf_out = genie_r2.parse('show ip ospf neighbor')
    table = []

    for k,v in r1_ospf_out.get('interfaces', {}).get('GigabitEthernet2', {}).get('neighbors', {}).items():
        print('r1', k, v.get('state'))
        if 'FULL' in v.get('state'):
            r1_result = True
            table.append(['r1', k, v.get('state')])

    for k,v in r2_ospf_out.get('interfaces', {}).get('GigabitEthernet2', {}).get('neighbors', {}).items():
        print('r2', k, v.get('state'))
        if 'FULL' in v.get('state'):
            r2_result = True
            table.append(['r2', k, v.get('state')])

    result = all([r1_result, r2_result])

df = pd.DataFrame(table, columns=['Router','Neighbor','State'])
df
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/c913a2df-5ca8-cf89-88be-ee140ca8d8a8.png)





このように Jupyter Notebook 上で一連のテストを自動的に実行し、同じ画面上に結果も出力することができました。Python コーディングを活用することで結果の表示が美しくなり、デモとしての見栄えや、結果レポートとしての見易さも向上したと言えないでしょうか。


# まとめ

本記事では、Jupyter Notebook と PyATS/Genie を使ってネットワーク自動化のデモをしてみる、という内容をご紹介しました。

Jupyter Notebook はサーバで動かして複数人で共有して編集したり、ノートのまま保存して配布したりと、チームでのネットワーク開発・運用およびナレッジの蓄積に大変優れています。みなさんの環境でも活用してみていただけたら大変幸いです。

さて次回ですが、「ネットワークデバイス・ネットワークサービスのモデル化」というテーマにしたいと考えています。本記事のように少ない台数ですと、個別にコンフィグを投入したり、確認コマンドを打ったりしてもなんとかなりますが、大規模なネットワークへの設定投入となると厳しいものがあります。次回はそういった課題へのアプローチについても考えてみたいと思います。

[目次](https://qiita.com/radiantmarch/items/9df086002ee3f4f334d2)
前回: [第2回: ネットワーク自動化開発実践 - Python でルータを操作する](https://qiita.com/radiantmarch/items/936b43f32210e4689179)
次回: 第4回: ネットワーク自動化開発実践 - デバイスのモデル化のご利益を感じよう - Cisco NSO を使う




# Appendix
## Appendix: IOS Router Configuration

```
hostname CSR1kv-R1
!
vrf definition management
 rd 1:1
 !
 address-family ipv4
 exit-address-family
!
!
interface GigabitEthernet1
 vrf forwarding management
 ip address 192.168.1.121 255.255.255.0
 negotiation auto
!
ip route vrf management 0.0.0.0 0.0.0.0 192.168.1.254
!
no aaa new-model
!
enable secret !admin
username admin secret admin
!
line vty 0 4
 exec-timeout 0 0
 login local
!
```

## Appendix: Reference

サンプルコードの作成にあたって、私が書く前にすでに PyATS/Genie を記事にされていた以下を参考にさせていただきました。素晴らしい記事をありがとうございます。

- [Genie Parser/OpsによるCisco機器のログ取得・構文解析 - Qiita](https://qiita.com/tech_kitara/items/f5c4ae6cbab8e7240f40)

また pandas と matplotlib をより活用するには以下の記事が参考になります。

- [pandas + matplotlib による多彩なデータプロッティング - Qiita](https://qiita.com/ynakayama/items/68eff3cb146181329b48)



# 免責事項
本サイトおよび対応するコメントにおいて表明される意見は、投稿者本人の個人的意見であり、シスコの意見ではありません。本サイトの内容は、情報の提供のみを目的として掲載されており、シスコや他の関係者による推奨や表明を目的としたものではありません。各利用者は、本Webサイトへの掲載により、投稿、リンクその他の方法でアップロードした全ての情報の内容に対して全責任を負い、本Web サイトの利用に関するあらゆる責任からシスコを免責することに同意したものとします。