NSO でネットワーク自動化デモ



# この記事の目標

Cisco NSO (Network Services Orchestrator) を使ったネットワーク自動化のデモをご紹介します

# NSO って？

シスコが提供しているマルチベンダ対応の商用ネットワーク自動化ソフトウェアです。サポート付きの有償製品ということもあってオープンソースのツール群 (Ansible, Netmiko, etc) と比べると、試したことがあるユーザの数はまだ少ないかもしれませんが、じわじわとユーザが広がっています。

実は Cisco NSO は試用版を無償で利用できます。シスコ本家のブログに以下の記事がありますのでこちらから始めてみてください。

| Date | Author | Title  |
|-----------------|------------------|---|
| ２０１８/11 | Hirotsugu Takahashi | [評価版 Cisco NSO を使ってみよう](https://gblogs.cisco.com/jp/2018/11/getting-nso/) |


# これまでの NSO の紹介記事をまとめてみる

Cisco NSO (Network Services Orchestrator) についてはすでに過去の Qiita の記事でも少なからず取り上げられています。

| Date | Author | Title |
|------|--------|-------|
| 2018/12 | @tkasakak | [MAC に Free NSO を入れて Network 自動化の心の準備をしてみる - Qiita](https://qiita.com/tkasakak/items/91d93da9cb061f285042) |
| 2018/12 | @akiraiwamoto | [NSO を使って、サーバに公開APIを実装してみる - Qiita](https://qiita.com/akiraiwamoto/items/ecbc14b0914532ba4ab0) |
| 2017/12 | @akiraiwamoto | [Cisco Network Services Orchestrator (NSO) ができること - Qiita](https://qiita.com/akiraiwamoto/items/0816f523d24a21917d51)

私自身は企業向け・データセンタ向け・通信事業者向け、それぞれのドメインでネットワークエンジニアを経験したのち、ネットワーク自動化ソフトウェアの製品担当に専門を移し、さまざまなツールやプログラミングスキルの習得に日々苦闘しています。昨年、ONIC 2018 で講演し、NSO や他のツールを使ったデモをしました。よろしければご覧ください。

| Date | Author | Title  |
|-----------------|------------------|---|
| 2018/10 | @radiantmarch | [「検証テストを自動化せよ！〜NSO と pyATS の組み合わせでここまでできる〜」 ](http://onic.jp/archive/2018/program-detail/#s05) |


# NSO の使い方

この記事では NSO の使い方は悪しからず、説明しませんので、上の記事を参考にしていただければと思います。そのうち、連載したいと思います。


# NSO のデモ

## 0. デモシナリオ

この記事では NSO を使って、シンプル化された仮想的な企業ネットワークを管理することを考えてみます。ネットワーク構成図として図1のような構成を想定します。なおデモ用の環境は Cisco VIRL を使ったシミュレーション環境です (VIRL についての記事は[こちら](https://qiita.com/radiantmarch/items/a3c0685d8a4c7468e878))

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175796/c4907208-5014-6ff3-b72e-6f4a6c8b0d61.png)
(図1)

図1 では2つの企業拠点 site1, site2 が WAN ルータ (Cisco IOS) とファイアウォール (Cisco ASA) を介して接続されている状況です。それぞれの拠点には WAN ルータの下に L2 スイッチ (Cisco Catalyst) が接続されています。

デモシナリオとしては以下のようなものです

- site1 に新しいネットワークセグメントを追加します。
- すると LAN スイッチと WAN ルータには新しく VLAN を作成すると同時に、WAN ルータにゲートウェイ IP アドレスを設定する必要があります。
- また、WAN ルータは DHCP サーバも兼任しているため、DHCP の設定も追加する必要があります
- 新セグメントが既存の site2 拠点と通信するためにはルーティング設定 (OSPF) を追加する必要があります
- さらにファイアウォールに通信許可のルールを追加する必要があります

こうした設定作業をそれぞれ個別に、人手で行うことはなかなか骨が折れますし設定ミスのリスクもあります。そこで NSO を使って機器を集中管理し、設定を一斉に投入しようというわけです。

なお、NSO は GUI 操作、CLI 操作、API (NETCONF, RESTCONF) 操作に対応していますが、以下ではすべて CLI 操作でお見せします。**どのインターフェイスを使っても同じ操作が実施可能です**。


## 1. NSO でデバイスのコンフィグを管理

NSO はデバイスを管理対象に登録し、コンフィグを同期して使います。

```
admin@ncs# show devices list
NAME                ADDRESS       DESCRIPTION  NED ID              ADMIN STATE
------------------------------------------------------------------------------
lanmgr-internet-fw  10.71.157.93  -            cisco-asa-cli-6.7   unlocked
lanmgr-router1      10.71.157.93  -            cisco-ios-cli-6.30  unlocked
lanmgr-router2      10.71.157.93  -            cisco-ios-cli-6.30  unlocked
lanmgr-switch1      10.71.157.93  -            cisco-ios-cli-6.30  unlocked
lanmgr-switch2      10.71.157.93  -            cisco-ios-cli-6.30  unlocked
```

デバイス登録時には、デバイスの OS ごとに異なるドライバ (NED, Network Element Driver) を使用します。このデモの例では、ファイアウォール (lanmgr-internet-fw) には ASA NED を、WAN ルータと LAN スイッチには IOS NED を使っています。

コンフィグの同期を済ませると、NSO 上からデバイスのコンフィグを表示・操作できるようになります。

以下は running コンフィグを表示させた例です

```nso:コンフィグ表示
admin@ncs# show running-config devices device lanmgr-router1 config
devices device lanmgr-router1
 config
  ios:tailfned police cirmode
  ios:version   15.7
  ios:service timestamps debug datetime msec localtime show-timezone year
  ios:service timestamps log datetime msec localtime show-timezone year
  no ios:service password-encryption
(snip)
```

以下はコンフィグを変更する例です。commit dry-run で実際に変更するまえに投入されるコンフィグを確認することが可能です。

```nso:コンフィグ投入
admin@ncs# config
Entering configuration mode terminal
admin@ncs(config)# devices device lanmgr-router1 config ios:interface GigabitEthernet 0/2
admin@ncs(config-if)# shutdown
admin@ncs(config-if)# commit dry-run
cli {
    local-node {
        data  devices {
                  device lanmgr-router1 {
                      config {
                          ios:interface {
                              GigabitEthernet 0/2 {
             +                    shutdown;
                              }
                          }
                      }
                  }
              }
    }
}
admin@ncs(config-if)# commit dry-run outformat native
native {
    device {
        name lanmgr-router1
        data interface GigabitEthernet0/2
              shutdown
             exit
    }
}
admin@ncs(config-if)# commit
Commit complete.
```

## 2. NSO からコマンドを実行

NSO からデバイスに対して show コマンドなどを実行して結果を取得することができます。

```nso:コマンド実行
admin@ncs# devices device lanmgr-router1 live-status exec show ip int br
result
Load for five secs: 8%/0%; one minute: 9%; five minutes: 12%
Time source is hardware calendar, *01:04:22.040 JST Sat Dec 7 2019

Interface                  IP-Address      OK? Method Status                Protocol
GigabitEthernet0/0         172.16.1.53     YES DHCP   up                    up
GigabitEthernet0/1         10.100.10.1     YES TFTP   up                    up
GigabitEthernet0/2         unassigned      YES TFTP   administratively down down
GigabitEthernet0/2.11      192.168.11.254  YES TFTP   administratively down down
Loopback0                  10.100.0.1      YES TFTP   up                    up
router1#
```

1 と 2 で、通常デバイスに対して行うオペレーションのほとんどが NSO から実行できるようになることがわかります。


## 3. NSO で新規ネットワークセグメントの追加 = 抽象化されたサービスの設定

1 で見たようなのデバイスコンフィグの変更は、CLI で1台だけを設定する分には、単体デバイスを手動やスクリプトで設定するのとそれほど違いがありません（注： API でも操作できるという点はメリットとしてあります）。

デモシナリオで述べたような、「新規ネットワークセグメントを追加する」 という操作を NSO 上のサービス (名前: lanmgr) として定義することで、最小限のパラメータで複数のデバイスを一斉に設定できるようになります。

サービスの定義方法はあとにして、まずは結果をデモでお見せします。

入力するサービスコンフィグは以下だけです。

```nso:service&nbsp;config
lanmgr-demo net20
 site   site1
 vlan   20
 fw     lanmgr-internet-fw
 router lanmgr-router1
 switchport lanmgr-switch1 0/3
 !
!
```

入力している内容は、直感的に理解できる通りで、net20 という名前の新規セグメントを、site1 に vlan id 20 を使って設定する。設定対象の機器は 3台です。

このサービスコンフィグから生成されるデバイスコンフィグは以下の通りです。

```nso:commit&nbsp;dry-run&nbsp;outformat&nbsp;native
admin@ncs(config-switchport-lanmgr-switch1/0/3)# commit dry-run outformat native
native {
    device {
        name lanmgr-internet-fw
        data object network network-20
              subnet 192.168.20.0 255.255.255.0
             exit
             object-group network site1-network-group
              network-object object network-20
             !
    }
    device {
        name lanmgr-router1
        data ip dhcp pool network-20
              network 192.168.20.0 255.255.255.0
              default-router 192.168.20.254
             exit
             interface GigabitEthernet0/2.20
              no shutdown
              no switchport
              encapsulation dot1Q 20
              ip address 192.168.20.254 255.255.255.0
             exit
             router ospf 1
              passive-interface GigabitEthernet0/2.20
              network 192.168.20.0 0.0.0.255 area 0
             exit
    }
    device {
        name lanmgr-switch1
        data vlan 20
             !
             interface GigabitEthernet0/1
              switchport trunk allowed vlan add 20
             exit
             interface GigabitEthernet0/3
              switchport
              switchport mode access
              switchport access vlan 20
             exit
    }
}
```

このように少数のインプットパラメータから、ファイアウォール、WANルータ、LAN スイッチすべてのコンフィグを整合的に生成できていることがわかります。

逆に、このセグメントを削除する場合も簡単です。サービスコンフィグを削除するだけです。

```
admin@ncs(config)# no lanmgr-demo net20
```

これで設定を消すためのコンフィグが生成されます。

```
admin@ncs(config)# commit dry-run outformat native
native {
    device {
        name lanmgr-internet-fw
        data object-group network site1-network-group
              no network-object object network-20
             !
             no object network network-20
    }
    device {
        name lanmgr-router1
        data no ip dhcp pool network-20
             router ospf 1
              no passive-interface GigabitEthernet0/2.20
             exit
             no interface GigabitEthernet0/2.20
             router ospf 1
              no network 192.168.20.0 0.0.0.255 area 0
             exit
    }
    device {
        name lanmgr-switch1
        data interface GigabitEthernet0/1
              switchport trunk allowed vlan remove 20
             exit
             interface GigabitEthernet0/3
              no switchport mode access
              no switchport access vlan 20
              no switchport
             exit
             no vlan 20
    }
}
```

## 4. サービスを定義する方法

上のようにネットワークサービスの抽象化を簡単に定義する手法を、NSO は提供しています。

NSO をインストールした Linux または Mac 上で、以下のコマンドを実行するとサービスのひながたを生成できます。

```
$ ncs-make-package --service-skeleton python-and-template lanmgr-demo
```

サービスのインプットパラメータを定義するのに用いるのが YANG データモデルファイルです。イチから書く必要はなく、ひながたファイルにパラメータ変数 (leaf や list 等) を YANG の作法に従って記述していくだけです

今回のデモで使った YANG モデルを参考までに貼り付けておきます

<details><summary>YANG モデルサンプル</summary><div>

```yang:yang
module lanmgr-demo {

  namespace "http://example.com/lanmgr-demo";
  prefix lanmgr-demo;

  import ietf-inet-types {
    prefix inet;
  }
  import tailf-common {
    prefix tailf;
  }
  import tailf-ncs {
    prefix ncs;
  }

  description
    "demo lanmgr service";

  revision 2019-11-10 {
    description
      "Initial revision.";
  }

  list lanmgr-demo {
    description "This is demo lanmgr service";

    key name;
    leaf name {
      tailf:info "Unique network name";
      //tailf:cli-allow-range;
      type string;
    }

    uses ncs:service-data;
    ncs:servicepoint lanmgr-demo-servicepoint;

    leaf site {
      tailf:info "";
      /* type string; */
      type enumeration {
        enum site1;
        enum site2;
      }
    }

    leaf vlan {
      tailf:info "";
      type uint16 {
        range "10..4095";
      }
    }

    leaf fw {
      type leafref {
        path "/ncs:devices/ncs:device/ncs:name";
      }
      must 'contains(deref(current()), "lanmgr-internet-fw")';
    }

    leaf router {
      type leafref {
        path "/ncs:devices/ncs:device/ncs:name";
      }
      must 'contains(deref(current()), "lanmgr-router")';
    }

    list switchport {
      tailf:cli-compact-syntax;
      key "switch hostport";

      leaf switch {
        type leafref {
          path "/ncs:devices/ncs:device/ncs:name";
        }
        must 'contains(deref(current()), "lanmgr-switch")';
      }

      leaf hostport {
        tailf:info "";
        type string;
      }
    }

  }
}
```
</div></detail>


この YANG モデルで定義した変数を、実際のデバイスコンフィグのテンプレートと紐付ける必要があります。

<details><summary>コンフィグテンプレートのサンプル</summary><div>


```xml:config-template
<config-template xmlns="http://tail-f.com/ns/config/1.0">
  <devices xmlns="http://tail-f.com/ns/ncs">


<!-- ASA -->
  <device>
    <name>{fw}</name>
      <config>
      <object xmlns="http://cisco.com/ned/asa">
        <network>
          <name>network-{vlan}</name>
          <subnet>
            <address>192.168.{vlan}.0</address>
            <mask>255.255.255.0</mask>
          </subnet>
        </network>
      </object>

      <object-group xmlns="http://cisco.com/ned/asa">
        <network>
          <id>{site}-network-group</id>
          <network-object>
            <id>object network-{vlan}</id>
          </network-object>
        </network>
      </object-group>

      <access-list xmlns="http://cisco.com/ned/asa">
        <access-list-id>
          <id>acl-global</id>
          <rule>
            <id>extended permit ip object-group {site}-network-group object-group site1-network-group</id>
          </rule>
          <rule>
            <id>extended permit ip object-group site1-network-group object-group {site}-network-group</id>
          </rule>
          <rule>
            <id>extended permit ip object-group {site}-network-group object-group site2-network-group</id>
          </rule>
          <rule>
            <id>extended permit ip object-group site2-network-group object-group {site}-network-group</id>
          </rule>
        </access-list-id>
      </access-list>

      <access-group xmlns="http://cisco.com/ned/asa">
        <global-list>
          <global>global</global>
          <access-list>acl-global</access-list>
        </global-list>
      </access-group>

      </config>
  </device>



<!-- Router -->
  <device>
    <name>{router}</name>
      <config>

      <interface xmlns="urn:ios">
      <GigabitEthernet>
        <name>0/2.{vlan}</name>
        <!-- <description>Test Description - this is vlan {vlan}</description> -->
        <encapsulation>
          <dot1Q>
            <vlan-id>{vlan}</vlan-id>
          </dot1Q>
        </encapsulation>
        <ip>
          <address>
            <primary>
              <address>192.168.{vlan}.254</address>
              <mask>255.255.255.0</mask>
            </primary>
          </address>
        </ip>
      </GigabitEthernet>
      </interface>

      <ip xmlns="urn:ios">
      <dhcp>
        <pool>
          <id>network-{vlan}</id>
          <network>
            <network-number>192.168.{vlan}.0</network-number>
            <mask>255.255.255.0</mask>
          </network>
          <default-router>192.168.{vlan}.254</default-router>
        </pool>
      </dhcp>
      </ip>

      <router xmlns="urn:ios">
      <ospf>
        <id>1</id>
        <network>
          <ip>192.168.{vlan}.0</ip>
          <mask>0.0.0.255</mask>
          <area>0</area>
        </network>
        <passive-interface>
          <interface>
            <name>GigabitEthernet0/2.{vlan}</name>
          </interface>
        </passive-interface>
      </ospf>
      </router>

      </config>
  </device>


<!-- Switch -->

  <?foreach {switchport}?>

  <device>
    <name>{switch}</name>
      <config>
      <vlan xmlns="urn:ios">
        <vlan-list>
          <id>{string(/vlan)}</id>
        </vlan-list>
      </vlan>

      <interface xmlns="urn:ios">
        <GigabitEthernet>
          <name>0/1</name>
          <switchport>
            <mode>
              <trunk/>
            </mode>
            <trunk>
              <encapsulation>dot1q</encapsulation>
              <allowed>
                <vlan>
                  <vlans>{string(/vlan)}</vlans>
                </vlan>
              </allowed>
            </trunk>
          </switchport>
        </GigabitEthernet>

        <GigabitEthernet>
          <name>{hostport}</name>
          <switchport tags="create">
            <mode>
              <access/>
            </mode>
            <access>
              <vlan>{string(/vlan)}</vlan>
            </access>
          </switchport>
        </GigabitEthernet>

      </interface>

    </config>
  </device>

  <?end?>



  </devices>
</config-template>
```

</div></detail>


コンフィグテンプレートは XML で記述しますが、これもイチから XML を書く必要はありません。NSO は登録・同期したデバイスのコンフィグをいろんなフォーマットで表示させることができます。この機能を使えば、動作確認済みの実機からコンフィグを抜き出して、XML 形式のテンプレートを簡単に生成できます。

```xml:nso_display_xml
admin@ncs# show running-config devices device lanmgr-router1 config | display xml
<config xmlns="http://tail-f.com/ns/config/1.0">
  <devices xmlns="http://tail-f.com/ns/ncs">
  <device>
    <name>lanmgr-router1</name>
      <config>
        <tailfned xmlns="urn:ios">
          <police>cirmode</police>
        </tailfned>
        <version xmlns="urn:ios">15.7</version>
        <service xmlns="urn:ios">
          <timestamps>
            <debug>
              <datetime>
                <msec/>
                <localtime/>
                <show-timezone/>
                <year/>
              </datetime>
(snip)
```

デモはここまでです。


# まとめ

NSO を使えば、ネットワーク機器のオペレーション（設定、確認）だけでなく、スクリプトを作成するよりも簡単にネットワークサービスを定義することが可能になります。サービスを定義すると、最小限のサービスコンフィグ変数を入力するだけで、複数デバイスにまたがる設定の投入や読み込み、削除、変更（いわゆる CRUD オペレーション） が可能になります。

もちろん、NSO にはこの記事では紹介しきれない機能や拡張性が多くあります。Python や Java でさらに複雑なサービスを定義することも可能で、プログラミングに腕に覚えがある方であればその活用方法は無限にあります。

そろそろ、NSO でネットワークの自動化を始めてみませんか？ 仲間をお待ちしております！


# 免責事項

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