ターミナルサーバで見るAnsible NW系プラグイン/モジュールの挙動

# 1. はじめに

以下2つの機能を組み合わせて、Ansible実行時、Cisco IOS機器に対しどのような操作が行われているか、試した結果をメモしておきます。

- ターミナルサーバを利用すると、対象機器へSSH経由でコンソール接続可能
- コンソール接続の場合、他のコンソール接続ユーザの実行内容が画面表示される

# 2. 検証構成
Ansibleは2.10.0、NW機器はCisco 1812Jを使用しています。

![無題1205-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/c70019c7-0a2f-fa6a-84d3-5bc0e05eb74c.png)

# 3. Inventoryファイル

以下の通り作成しました。Ansible2.9以降でサポートされているAnsible Collectionの記法（FQCN）に従っています。
Connectionプラグインは`network_cli`を使用。ユーザ名/パスワードは通常のSSH接続と異なりターミナルサーバ側を指定しています。

```inventory_con.ini
[cisco]
Router ansible_host=192.168.100.33 ansible_port=3001

[cisco:vars]
ansible_connection=ansible.netcommon.network_cli
ansible_network_os=cisco.ios.ios
ansible_user=(ターミナルサーバのユーザ名)
ansible_password=(ターミナルサーバのパスワード)
ansible_become=yes
ansible_become_method=ansible.netcommon.enable
ansible_become_pass=(ルータのenableパスワード)
```

# 4. showコマンド取得（ios_commandモジュール）

## 4-1. Playbook
Routerに対し、`show users`コマンドを2回実行してみます。

```playbook_shuser.yml
---

- hosts: cisco
  gather_facts: no

  tasks:
    - name: run show command 1
      cisco.ios.ios_command:
        commands:
          - show users

    - name: run show command 2
      cisco.ios.ios_command:
        commands:
          - show users
```

## 4-2. 実行結果（Ansible）
2つのタスクが問題なく完了しています。

```
$ ansible-playbook -i inventory_con.ini playbook_shuser.yml

PLAY [cisco] **********************************************************************************************************************************

TASK [run show command 1] *********************************************************************************************************************
ok: [Router]

TASK [run show command 2] *********************************************************************************************************************
ok: [Router]

PLAY RECAP ************************************************************************************************************************************
Router                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0    
```

## 4-3. 実行結果（NW機器）
#### タスク`run show command 1`実行時

- ログイン、`teminal lengh/width`コマンドで出力行/幅の制限を外す
- `show version`コマンド実行
- `show users`コマンド実行

#### タスク`run show command 2`実行時
- `show version`コマンド実行
- `show users`コマンド実行

タスク`run show command 2`実行時は、Persistent Connections機能（SSH接続が一定時間保持され、タスク毎のログインが不要になる。デフォルトは30秒）により、1点目の処理が省略されていました。

```
******************************** [run show command 1 実行時] ********************************
Router>enable
Password:
Router#terminal length 0
Router#terminal width 512
Router#terminal width 0
Router#show version
（省略）
Router#show users
    Line       User       Host(s)              Idle       Location
*  0 con 0                idle                 00:00:00

  Interface    User               Mode         Idle     Peer Address

******************************** [run show command 2 実行時] ********************************
Router#show version
（省略）
Router#show users
    Line       User       Host(s)              Idle       Location
*  0 con 0                idle                 00:00:00

  Interface    User               Mode         Idle     Peer Address
```

ちなみに`pause`モジュールで2つのタスクの実行間隔を開けたところ、1点目の処理が実行されました。


# 5. Facts取得（gather_facts、ios_factsモジュール）

## 5-1. Playbook
`gather_facts`ディレクティブを`yes`（デフォルト設定）に指定し、各種システム情報を事前に収集します。

後続のタスクで、`ios_facts`モジュールも実行してみます。本Playbookのように`gather_subset`オプションを指定しない場合は、デフォルトで`!config`が選択され、Configを除くハードウェア、インターフェース情報が取得されます。
参考：[Ansible Documentation - cisco.ios.ios_facts](https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_facts_module.html)

```playbook_facts.yml
---

- hosts: cisco
  gather_facts: yes

  tasks:
    - name: collect facts
      cisco.ios.ios_facts:
#        gather_subset:
#          - all
#          - min
#          - hardware
#          - config
#          - interfaces
      register: facts

    - name: debug
      debug:
        msg: "{{ facts }}"
```

## 5-2. 実行結果（Ansible）

`gather_facts`は、一つのタスク`Gathering Facts`として最初に実行されています。
その後タスク`collect facts`が実行され、結果がタスク`debug`で出力されています。

<details><summary>実行結果</summary><div>

```
$ ansible-playbook -i inventory_con.ini playbook_facts.yml

PLAY [cisco] **********************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************
[WARNING]: Ignoring timeout(10) for cisco.ios.ios_facts
ok: [Router]

TASK [collect facts] **************************************************************************************************************************
ok: [Router]

TASK [debug] **********************************************************************************************************************************
ok: [Router] => {
    "msg": {
        "ansible_facts": {
            "ansible_net_all_ipv4_addresses": [
                "XX.XX.XX.XX",
                "XX.XX.XX.XX"
            ],
            "ansible_net_all_ipv6_addresses": [],
            "ansible_net_api": "cliconf",
            "ansible_net_filesystems": [
                "flash:"
            ],
            "ansible_net_filesystems_info": {
                "flash:": {
                    "spacefree_kb": 12152.0,
                    "spacetotal_kb": 31192.0
                }
            },
            "ansible_net_gather_network_resources": [],
            "ansible_net_gather_subset": [
                "hardware",
                "interfaces",
                "default"
            ],
            "ansible_net_hostname": "Router",
            "ansible_net_image": "flash:c181x-advipservicesk9-mz.XX.XX.bin",
            "ansible_net_interfaces": {
                "FastEthernet0": {
                    "bandwidth": 100000,
                    "description": "<< To Router >>",
                    "duplex": null,
                    "ipv4": [
                        {
                            "address": "10.1.1.1",
                            "subnet": "30"
                        }
                    ],
                    "lineprotocol": "down",
                    "macaddress": "0022.55XX.XXXX",
                    "mediatype": null,
                    "mtu": 1500,
                    "operstatus": "up",
                    "type": "PQ3_TSEC"
                },
                （省略）
            },
            "ansible_net_iostype": "IOS",
            "ansible_net_memfree_mb": 45478.98828125,
            "ansible_net_memtotal_mb": 63340.53515625,
            "ansible_net_model": "1812-J",
            "ansible_net_neighbors": {},
            "ansible_net_python_version": "3.6.8",
            "ansible_net_serialnum": "FHKXXXXXXXX,",
            "ansible_net_system": "ios",
            "ansible_net_version": "XX.XX",
            "ansible_network_resources": {}
        },
        "changed": false,
        "failed": false
    }
}

PLAY RECAP ************************************************************************************************************************************
Router                     : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
```
</div></details>

## 5-3. 実行結果（NW機器）
以下の通り、タスク`Gathering Facts`とタスク`collect facts`で同じshowコマンドが実行されました。

```
******************************** [Gathering Facts 実行時] ********************************
Router>enable
Password:
Router#terminal length 0
Router#terminal width 512
Router#terminal width 0
Router#show version
（省略）
Router#show version
（省略）
Router#dir
（省略）
Router#show memory statistics
（省略）
Router#show interfaces
（省略）
Router#show ip interface
（省略）
Router#show ipv6 interface
（省略）
Router#show lldp
（省略）
Router#show cdp
（省略）
Router#show cdp neighbors detail
（省略）
Router#show version
（省略）
******************************** [collect facts 実行時] ********************************
Router#show version
（省略）
Router#show version
（省略）
Router#dir
（省略）
Router#show memory statistics
（省略）
Router#show interfaces
（省略）
Router#show ip interface
（省略）
Router#show ipv6 interface
（省略）
Router#show lldp
（省略）
Router#show cdp
（省略）
Router#show cdp neighbors detail
（省略）
Router#show version
（省略）
```

`ios_facts`モジュールの`gather_subset`オプションを変えた結果も記載しておきます。

| gather_subset | 取得コマンド |
|:-----------|:------------|
| 指定なし (!config)       | show version, dir, show memory statistics, show interfaces, show ip interface, show ipv6 interface, show lldp, show cdp, show cdp neighbors detail |
| all       | 指定なし + show running-config |
| min       | show version |
| hardware       | show version, dir, show memory statistics |
| config       | show version, show running-config |
| interfaces       | show version, show interfaces, show ip interface, show ipv6 interface, show lldp, show cdp, show cdp neighbors detail |


# 6. NTPサーバ設定追加（ios_configモジュール）

## 6-1. Playbook
冪等性が担保されているか確認するため、NTPサーバ`1.1.1.1`（設定済み）とNTPサーバ`9.9.9.9`（未設定）を追加してみます。`save_when`オプションで、設定変更後にwriteも行います。

```playbook_ntp.yml
---

- hosts: cisco
  gather_facts: no

  tasks:
    - name: add ntp server
      cisco.ios.ios_config:
        lines:
          - ntp server 1.1.1.1
          - ntp server 9.9.9.9
        match: line
        save_when: modified
```

## 6-2. 実行結果（Ansible）
`changed=1`となり、設定変更が行われました。

```
$ ansible-playbook -i inventory_con.ini playbook_ntp.yml

PLAY [cisco] **********************************************************************************************************************************

TASK [add ntp server] *************************************************************************************************************************
changed: [Router]

PLAY RECAP ************************************************************************************************************************************
Router                     : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

## 6-3. 実行結果（NW機器）
大まかな流れは以下の通りでした。

- 設定変更前のRunning Configを取得（おそらく設定しようとしているコマンドとの差分比較のため）
- 未設定のコマンド`ntp server 9.9.9.9`のみ適用
- 設定変更後のRunning ConfigとStartup Configを取得
- コマンド`copy running-config startup-config`で設定保存

```
Router>enable
Password:
Router#terminal length 0
Router#terminal width 512
Router#terminal width 0
Router#show version
（省略）
Router#show running-config
（省略）
ntp server 1.1.1.1
（省略）
Router#configure terminal
Enter configuration commands, one per line.  End with CNTL/Z.
Router(config)#ntp server 9.9.9.9
Router(config)#end

******************************** [save_when: modified 指定時] ********************************
Router#show running-config
（省略）
ntp server 1.1.1.1
ntp server 9.9.9.9
（省略）

Router#show startup-config
（省略）
ntp server 1.1.1.1
（省略）
Router#copy running-config startup-config
Destination filename [startup-config]?
Building configuration...
[OK]
```

別パターンで、Running Configに`ntp server 9.9.9.9`はないものの、Startup Configには既に設定がある場合（＝設定変更後にRunning ConfigとStartup Configが同じになる場合）、4点目の設定保存は行われませんでした。2つのConfigを比較して、保存が必要か判定しているようです。