📚

ansible で ios機器の設定の練習

2023/02/07に公開

ansible

ansible はエージェントレスの構成管理ツールです。
puppet のように、エージェントをインストールする必要がありません。

参考

https://docs.ansible.com/ansible/latest/collections/cisco/ios/index.html
https://qiita.com/rat-engineer755/items/7b4d07b8759a745d7404
https://gblogs.cisco.com/jp/2016/11/ansible-ios-related/

sshサーバ設定

r1(config)#int f0/0
r1(config-if)#ip address 10.2.0.254 255.255.255.0
r1(config-if)#no shut
r1(config)#ip domain-name localdomain.local
r1(config)#crypto key generate rsa
The name for the keys will be: r1.localdomain.local
Choose the size of the key modulus in the range of 360 to 2048 for your
  General Purpose Keys. Choosing a key modulus greater than 512 may take
  a few minutes.

How many bits in the modulus [512]: 2048
% Generating 2048 bit RSA keys, keys will be non-exportable...[OK]

r1(config)#ip ssh version 2
r1(config)#username ccna password cisco

(ログを残していなかったのですが、line vty で login localを実行する必要があるはずです。)

ansible を動かす端末から疎通確認します。

$ ping 10.2.0.254 -c 5
PING 10.2.0.254 (10.2.0.254) 56(84) bytes of data.
64 bytes from 10.2.0.254: icmp_seq=1 ttl=255 time=2.39 ms
64 bytes from 10.2.0.254: icmp_seq=2 ttl=255 time=8.41 ms
64 bytes from 10.2.0.254: icmp_seq=3 ttl=255 time=6.33 ms
64 bytes from 10.2.0.254: icmp_seq=4 ttl=255 time=3.81 ms
64 bytes from 10.2.0.254: icmp_seq=5 ttl=255 time=10.2 ms

--- 10.2.0.254 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 10ms
rtt min/avg/max/mdev = 2.385/6.221/10.169/2.861 ms
admin@ip-10-0-0-144:~/zenn$

仮想ラボで利用しているios では古いアルゴリズムにしか対応していません。
ssh 接続テストを実施するにあたり、古いアルゴリズムを許可するように設定します。

$ cat ~/.ssh/config
Host 10.2.0.254
        KexAlgorithms diffie-hellman-group1-sha1
        Ciphers aes192-cbc

ssh 接続テストをします。

$ ssh ccna@10.2.0.254
Password:

r1>
r1>exit
Connection to 10.2.0.254 closed.

ansible の準備

hosts ファイル

$ cat hosts
[r1]
10.2.0.254

[r1:vars]
ansible_ssh_user=ccna
ansible_connection = network_cli
ansible_network_os = ios

ansible のテスト

疎通確認

$ ansible -v -i hosts -m ping r1 --ask-pass
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0]. This feature will be removed from ansible-core in version
 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
No config file found; using defaults
SSH password:
10.2.0.254 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

実行テスト

$ ansible -v -i hosts -m ios_command -a "commands='show ip int bri'" r1 --ask-pass
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0]. This feature will be removed from ansible-core in version
 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
No config file found; using defaults
SSH password:
10.2.0.254 | SUCCESS => {
    "changed": false,
    "stdout": [
        "Interface                  IP-Address      OK? Method Status                Protocol\nFastEthernet0/0            10.2.0.254      YES manual up                    up      \nFastEthernet0/1            unassigned      YES unset  administratively down down    \nSerial1/0                  unassigned      YES unset  administratively down down    \nSerial1/1                  unassigned      YES unset  administratively down down    \nSerial1/2                  unassigned      YES unset  administratively down down    \nSerial1/3                  unassigned      YES unset  administratively down down"
    ],
    "stdout_lines": [
        [
            "Interface                  IP-Address      OK? Method Status                Protocol",
            "FastEthernet0/0            10.2.0.254      YES manual up                    up      ",
            "FastEthernet0/1            unassigned      YES unset  administratively down down    ",
            "Serial1/0                  unassigned      YES unset  administratively down down    ",
            "Serial1/1                  unassigned      YES unset  administratively down down    ",
            "Serial1/2                  unassigned      YES unset  administratively down down    ",
            "Serial1/3                  unassigned      YES unset  administratively down down"
        ]
    ]
}

playbook

show ip int briを実行するプレイブックを用意します。

$ cat r1.yaml
---
- hosts: r1
  gather_facts: false
  connection: local

  tasks:
    - name: run show version on remote devices
      cisco.ios.ios_command:
        commands: show ip int bri

事前確認

$ ansible-playbook -v -i hosts r1.yaml --check --ask-pass
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0]. This feature will be removed from ansible-core in version
 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
No config file found; using defaults
SSH password:
[WARNING]: An error occurred while calling ansible.utils.display.initialize_locale (unsupported locale setting). This may result in incorrectly calculated text widths that can cause Display to print incorrect line lengths

PLAY [r1] ***********************************************************************************************************************************************************************************************************************************

TASK [run show version on remote devices] ***************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"changed": false, "stdout": ["Interface                  IP-Address      OK? Method Status                Protocol\nFastEthernet0/0            10.2.0.254      YES manual up                    up      \nFastEthernet0/1            unassigned      YES unset  administratively down down    \nSerial1/0                  unassigned      YES unset  administratively down down    \nSerial1/1                  unassigned      YES unset  administratively down down    \nSerial1/2                  unassigned      YES unset  administratively down down    \nSerial1/3                  unassigned      YES unset  administratively down down"], "stdout_lines": [["Interface                  IP-Address      OK? Method Status                Protocol", "FastEthernet0/0            10.2.0.254      YES manual up                    up      ", "FastEthernet0/1            unassigned      YES unset  administratively down down    ", "Serial1/0                  unassigned      YES unset  administratively down down    ", "Serial1/1                  unassigned      YES unset  administratively down down    ", "Serial1/2                  unassigned      YES unset  administratively down down    ", "Serial1/3                  unassigned      YES unset  administratively down down"]]}

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

適用します。(show コマンドしか実行されないので、事実上、ルータでの設定変更は発生しません)

$ ansible-playbook -v -i hosts r1.yaml --ask-pass
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0]. This feature will be removed from ansible-core in version
 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
No config file found; using defaults
SSH password:
[WARNING]: An error occurred while calling ansible.utils.display.initialize_locale (unsupported locale setting). This may result in incorrectly calculated text widths that can cause Display to print incorrect line lengths

PLAY [r1] ***********************************************************************************************************************************************************************************************************************************

TASK [run show version on remote devices] ***************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"changed": false, "stdout": ["Interface                  IP-Address      OK? Method Status                Protocol\nFastEthernet0/0            10.2.0.254      YES manual up                    up      \nFastEthernet0/1            unassigned      YES unset  administratively down down    \nSerial1/0                  unassigned      YES unset  administratively down down    \nSerial1/1                  unassigned      YES unset  administratively down down    \nSerial1/2                  unassigned      YES unset  administratively down down    \nSerial1/3                  unassigned      YES unset  administratively down down"], "stdout_lines": [["Interface                  IP-Address      OK? Method Status                Protocol", "FastEthernet0/0            10.2.0.254      YES manual up                    up      ", "FastEthernet0/1            unassigned      YES unset  administratively down down    ", "Serial1/0                  unassigned      YES unset  administratively down down    ", "Serial1/1                  unassigned      YES unset  administratively down down    ", "Serial1/2                  unassigned      YES unset  administratively down down    ", "Serial1/3                  unassigned      YES unset  administratively down down"]]}

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

各種モジュールの利用

ios 機器側では enable パスワードの設定が必要です。
ansible 側では ansible_become_method = enable の指定が必要です。

$ cat hosts
[r1]
10.2.0.254

[r1:vars]
ansible_ssh_user=ccna
ansible_connection = network_cli
ansible_network_os = ios
ansible_become_method = enable
$ cat r1.yaml
---
- hosts: r1
  vars:
    - address: 1.1.1.1/32
  gather_facts: false
  connection: local
  become: true

  tasks:
    - name: configure motd banner message
      cisco.ios.ios_banner:
        banner: motd
        text: |
          This is the 1st line of the motd message.
          This is the 2nd line of the motd message.
        state: present

    - name: configure interface address
      cisco.ios.ios_l3_interfaces:
        config:
          - name: Loopback0
            ipv4:
              - address: "{{ address }}"
        state: merged

    - name: configure interface description
      cisco.ios.ios_interfaces:
        config:
          - name: Loopback0
            description: Configured and Merged by Ansible Network
        state: merged

    - name: configure timezone
      cisco.ios.ios_config:
        commands:
          -  clock timezone JST +9

事前確認

$ ansible-playbook -v -i hosts r1.yaml --check --diff --ask-pass --ask-become-pass
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0]. This feature will be removed from ansible-core in version
 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
No config file found; using defaults
SSH password:
BECOME password[defaults to SSH password]:
[WARNING]: An error occurred while calling ansible.utils.display.initialize_locale (unsupported locale setting). This may result in incorrectly calculated text widths that can cause Display to print incorrect line lengths

PLAY [r1] ***********************************************************************************************************************************************************************************************************************************

TASK [configure motd banner message] ********************************************************************************************************************************************************************************************************
changed: [10.2.0.254] => {"changed": true, "commands": ["banner motd @\nThis is the 1st line of the motd message.\nThis is the 2nd line of the motd message.\n@"]}

TASK [configure interface address] **********************************************************************************************************************************************************************************************************
changed: [10.2.0.254] => {"after": [{"ipv4": [{"address": "10.2.0.254/24"}], "name": "FastEthernet0/0"}, {"name": "FastEthernet0/1"}, {"name": "Serial1/0"}, {"name": "Serial1/1"}, {"name": "Serial1/2"}, {"name": "Serial1/3"}], "before": [{"ipv4": [{"address": "10.2.0.254/24"}], "name": "FastEthernet0/0"}, {"name": "FastEthernet0/1"}, {"name": "Serial1/0"}, {"name": "Serial1/1"}, {"name": "Serial1/2"}, {"name": "Serial1/3"}], "changed": true, "commands": ["interface loopback0", "ip address 1.1.1.1 255.255.255.255"]}

TASK [configure interface description] ******************************************************************************************************************************************************************************************************
changed: [10.2.0.254] => {"after": [{"duplex": "auto", "enabled": true, "name": "FastEthernet0/0", "speed": "auto"}, {"duplex": "auto", "enabled": false, "name": "FastEthernet0/1", "speed": "auto"}, {"enabled": false, "name": "Serial1/0"}, {"enabled": false, "name": "Serial1/1"}, {"enabled": false, "name": "Serial1/2"}, {"enabled": false, "name": "Serial1/3"}], "before": [{"duplex": "auto", "enabled": true, "name": "FastEthernet0/0", "speed": "auto"}, {"duplex": "auto", "enabled": false, "name": "FastEthernet0/1", "speed": "auto"}, {"enabled": false, "name": "Serial1/0"}, {"enabled": false, "name": "Serial1/1"}, {"enabled": false, "name": "Serial1/2"}, {"enabled": false, "name": "Serial1/3"}], "changed": true, "commands": ["interface loopback0", "description Configured and Merged by Ansible Network", "no shutdown"]}

TASK [configure timezone] *******************************************************************************************************************************************************************************************************************
[WARNING]: To ensure idempotency and correct diff the input configuration lines should be similar to how they appear if present in the running configuration on device
changed: [10.2.0.254] => {"banners": {}, "changed": true, "commands": ["clock timezone JST +9"], "updates": ["clock timezone JST +9"]}

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
10.2.0.254                 : ok=4    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

適用

$ ansible-playbook -v -i hosts r1.yaml --diff --ask-pass --ask-become-pass
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0]. This feature will be removed from ansible-core in version
 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
No config file found; using defaults
SSH password:
BECOME password[defaults to SSH password]:
[WARNING]: An error occurred while calling ansible.utils.display.initialize_locale (unsupported locale setting). This may result in incorrectly calculated text widths that can cause Display to print incorrect line lengths

PLAY [r1] ***********************************************************************************************************************************************************************************************************************************

TASK [configure motd banner message] ********************************************************************************************************************************************************************************************************
changed: [10.2.0.254] => {"changed": true, "commands": ["banner motd @\nThis is the 1st line of the motd message.\nThis is the 2nd line of the motd message.\n@"]}

TASK [configure interface address] **********************************************************************************************************************************************************************************************************
changed: [10.2.0.254] => {"after": [{"ipv4": [{"address": "10.2.0.254/24"}], "name": "FastEthernet0/0"}, {"name": "FastEthernet0/1"}, {"ipv4": [{"address": "1.1.1.1/32"}], "name": "Loopback0"}, {"name": "Serial1/0"}, {"name": "Serial1/1"}, {"name": "Serial1/2"}, {"name": "Serial1/3"}], "before": [{"ipv4": [{"address": "10.2.0.254/24"}], "name": "FastEthernet0/0"}, {"name": "FastEthernet0/1"}, {"name": "Serial1/0"}, {"name": "Serial1/1"}, {"name": "Serial1/2"}, {"name": "Serial1/3"}], "changed": true, "commands": ["interface loopback0", "ip address 1.1.1.1 255.255.255.255"]}

TASK [configure interface description] ******************************************************************************************************************************************************************************************************
changed: [10.2.0.254] => {"after": [{"description": "Configured and Merged by Ansible Network", "enabled": true, "name": "loopback0"}, {"duplex": "auto", "enabled": true, "name": "FastEthernet0/0", "speed": "auto"}, {"duplex": "auto", "enabled": false, "name": "FastEthernet0/1", "speed": "auto"}, {"enabled": false, "name": "Serial1/0"}, {"enabled": false, "name": "Serial1/1"}, {"enabled": false, "name": "Serial1/2"}, {"enabled": false, "name": "Serial1/3"}], "before": [{"enabled": true, "name": "loopback0"}, {"duplex": "auto", "enabled": true, "name": "FastEthernet0/0", "speed": "auto"}, {"duplex": "auto", "enabled": false, "name": "FastEthernet0/1", "speed": "auto"}, {"enabled": false, "name": "Serial1/0"}, {"enabled": false, "name": "Serial1/1"}, {"enabled": false, "name": "Serial1/2"}, {"enabled": false, "name": "Serial1/3"}], "changed": true, "commands": ["interface loopback0", "description Configured and Merged by Ansible Network"]}

TASK [configure timezone] *******************************************************************************************************************************************************************************************************************
[WARNING]: To ensure idempotency and correct diff the input configuration lines should be similar to how they appear if present in the running configuration on device
changed: [10.2.0.254] => {"banners": {}, "changed": true, "commands": ["clock timezone JST +9"], "updates": ["clock timezone JST +9"]}

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
10.2.0.254                 : ok=4    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

ansible では同じプレイブックを再実行しても同じ状態になる冪等性を持っています。
なにも設定変更しなければ、再実行したときは changed=0 となるはずです。

$ ansible-playbook -v -i hosts r1.yaml --check --diff --ask-pass --ask-become-pass
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0]. This feature will be removed from ansible-core in version
 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
No config file found; using defaults
SSH password:
BECOME password[defaults to SSH password]:
[WARNING]: An error occurred while calling ansible.utils.display.initialize_locale (unsupported locale setting). This may result in incorrectly calculated text widths that can cause Display to print incorrect line lengths

PLAY [r1] ***********************************************************************************************************************************************************************************************************************************

TASK [configure motd banner message] ********************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"changed": false, "commands": []}

TASK [configure interface address] **********************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"before": [{"ipv4": [{"address": "10.2.0.254/24"}], "name": "FastEthernet0/0"}, {"name": "FastEthernet0/1"}, {"ipv4": [{"address": "1.1.1.1/32"}], "name": "Loopback0"}, {"ipv4": [{"address": "10.2.1.254/24"}], "name"
: "Serial1/0"}, {"name": "Serial1/1"}, {"name": "Serial1/2"}, {"name": "Serial1/3"}], "changed": false, "commands": []}

TASK [configure interface description] ******************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"before": [{"description": "Configured and Merged by Ansible Network", "enabled": true, "name": "loopback0"}, {"duplex": "auto", "enabled": true, "name": "FastEthernet0/0", "speed": "auto"}, {"duplex": "auto", "enabl
ed": false, "name": "FastEthernet0/1", "speed": "auto"}, {"enabled": false, "name": "Serial1/0"}, {"enabled": false, "name": "Serial1/1"}, {"enabled": false, "name": "Serial1/2"}, {"enabled": false, "name": "Serial1/3"}], "changed": fals
e, "commands": []}

TASK [configure timezone] *******************************************************************************************************************************************************************************************************************
[WARNING]: To ensure idempotency and correct diff the input configuration lines should be similar to how they appear if present in the running configuration on device
changed: [10.2.0.254] => {"banners": {}, "changed": true, "commands": ["clock timezone JST +9"], "updates": ["clock timezone JST +9"]}

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
10.2.0.254                 : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

が、ここでは changed=1 となりました。
running-config にある通りに、 clock timezone JST 9 でプレイブックを記載することで、差分が生じなくなります。
コンフィグ内容レベルで同じ状態になるようなプレイブックを記載してあげる必要があります。

$ cat r1.yaml
---
- hosts: r1
  vars:
    - address: 1.1.1.1/32
  gather_facts: false
  connection: local
  become: true

  tasks:
    - name: configure motd banner message
      cisco.ios.ios_banner:
        banner: motd
        text: |
          This is the 1st line of the motd message.
          This is the 2nd line of the motd message.
        state: present

    - name: configure interface address
      cisco.ios.ios_l3_interfaces:
        config:
          - name: Loopback0
            ipv4:
              - address: "{{ address }}"
        state: merged

    - name: configure interface description
      cisco.ios.ios_interfaces:
        config:
          - name: Loopback0
            description: Configured and Merged by Ansible Network
        state: merged

    - name: configure timezone
      cisco.ios.ios_config:
        commands:
          -  clock timezone JST 9

確認

$ ansible-playbook -v -i hosts r1.yaml --check --diff --ask-pass --ask-become-pass
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0]. This feature will be removed from ansible-core in version
 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
No config file found; using defaults
SSH password:
BECOME password[defaults to SSH password]:
[WARNING]: An error occurred while calling ansible.utils.display.initialize_locale (unsupported locale setting). This may result in incorrectly calculated text widths that can cause Display to print incorrect line lengths

PLAY [r1] ***********************************************************************************************************************************************************************************************************************************

TASK [configure motd banner message] ********************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"changed": false, "commands": []}

TASK [configure interface address] **********************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"before": [{"ipv4": [{"address": "10.2.0.254/24"}], "name": "FastEthernet0/0"}, {"name": "FastEthernet0/1"}, {"ipv4": [{"address": "1.1.1.1/32"}], "name": "Loopback0"}, {"ipv4": [{"address": "10.2.1.254/24"}], "name": "Serial1/0"}, {"name": "Serial1/1"}, {"name": "Serial1/2"}, {"name": "Serial1/3"}], "changed": false, "commands": []}

TASK [configure interface description] ******************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"before": [{"description": "Configured and Merged by Ansible Network", "enabled": true, "name": "loopback0"}, {"duplex": "auto", "enabled": true, "name": "FastEthernet0/0", "speed": "auto"}, {"duplex": "auto", "enabled": false, "name": "FastEthernet0/1", "speed": "auto"}, {"enabled": false, "name": "Serial1/0"}, {"enabled": false, "name": "Serial1/1"}, {"enabled": false, "name": "Serial1/2"}, {"enabled": false, "name": "Serial1/3"}], "changed": false, "commands": []}

TASK [configure timezone] *******************************************************************************************************************************************************************************************************************
ok: [10.2.0.254] => {"changed": false}

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
10.2.0.254                 : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

GitHubで編集を提案

Discussion