てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] ios_config モジュールで意図せず chaneged になってしまうバグが 2.5.3 で修正された

はじめに

Ansible 2.5.0 - 2.5.2 の ios_configモジュールには、コンフィグが変更されていないにもかかわらず意図せず changed になってしまうバグがありました。2018/05/18 にリリースされた Ansible 2.5.3 で修正されましたので、簡単な動作確認をします。

バグの詳細

Ansible 2.5.0 - 2.5.2 の ios_config では、lines オプションで指定したコンフィグが、すでに入っているかどうかを比較する際に、デフォルトコンフィグも明示的に表示させるる、show running-config all または、 show running-config full コマンド(以降 show run all)の結果を取得していました。

本来は、 defaults: yes なら show run all の結果と、 defaults: no なら show run の結果と比較するのですが、defaults オプションが無視され、常にshow run all の結果と比較する挙動となっていました。

問題(意図せず changed)になってしまうのは、show run で表示したときと show run all で表示したときとで、表記が変わるコマンドを投入しようとした場合です。

例えば、snmp-server community XXXXX RO というコンフィグを投入すると、

  • show run では snmp-server community XXXXX RO
  • show run all では snmp-server community XXXXXv1default RO

のように、コンフィグ表記が異なります。(私の環境調べ)

このため、実際はすでに設定されている snmp-server community XXXXX RO (show run上の表記) を ios_config モジュールで投入しようとする際、 snmp-server community XXXXX ROshow run all の結果に含まれない(snmp-server community XXXXXv1default ROとは一致しない)ため、snmp-server community XXXXX RO を再度を投入します。 結果としては、コンフィグの実質的な内容には変更ありません。

関連issue/PR

ios_config module: Always "changed" will come out. "server community" and "snmp-server community" · Issue #37550 · ansible/ansible · GitHub

Fetch ios default config is defaults is enabled by ganeshrn · Pull Request #39741 · ansible/ansible · GitHub

Fix fetching ios default running config by ganeshrn · Pull Request #39475 · ansible/ansible · GitHub

Ansible 2.5.3 CHANGELOG

ansible/CHANGELOG-v2.5.rst at stable-2.5 · ansible/ansible · GitHub


■ バグの再現(Ansible 2.5.2)

Ansible 2.5.2 でバグの再現を試みます。

事前状態確認

ネットワーク機器側の事前状態としては、show run の結果に snmp-server community XXXXX RO がすでに含まれる状態です。

csr1# show run | inc snmp-server
snmp-server community XXXXX RO

なお、show run all だと以下のように、以下のような表記になります。

csr1# show run all | inc snmp-server 
snmp-server community XXXXXv1default RO

Playbook

以下のPlaybookを用意します。

- hosts: ios
  gather_facts: no

  tasks:
    - name: config test
      ios_config:
        lines:
          - snmp-server community XXXXX RO

実行結果

(ansible252) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory ios_test.yml

PLAY [ios] *******************************************************************

TASK [config_test] ***********************************************************
changed: [10.0.8.232]

PLAY RECAP *******************************************************************
10.0.8.232               : ok=1    changed=1    unreachable=0    failed=0

このように、show run の結果に snmp-server community XXXXX RO がすでに含まれているのに changed となってしまいました。 前述のように、show run ではなく show run all の結果と比較してしまうためです。


■ バグ修正の確認(Ansible 2.5.3)

本バグが修正されたことを確認します。

Playbook

以下のPlaybookを用意します。(先ほどの 2.4.2 のときと同じです。)

- hosts: ios
  gather_facts: no

  tasks:
    - name: config test
      ios_config:
        lines:
          - snmp-server community XXXXX RO

実行結果

(ansible253) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory ios_test.yml

PLAY [ios] *******************************************************************

TASK [config_test] ***********************************************************
ok: [10.0.8.232]

PLAY RECAP *******************************************************************
10.0.8.232               : ok=1    changed=0    unreachable=0    failed=0

今度は changed になりませんでした。意図通りの結果です。


■ まとめ

ios_config モジュールで意図せず chaneged になってしまうバグの再現と修正確認を行いました。 Ansible 2.5.0 - 2.5.2 をお使いでこのような現象でお困りの場合、2.5.3 へのアップデートを検討してみてはいかがでしょうか。

Ansible でNW機器のインターフェースが意図通りのVLANに所属しているかチェックする(associated_interfaces)

■ はじめに

Ansible 2.4 から、ネットワークモジュールに、設定だけでなく「意図した状態か」チェックする Declarative Intent という機能が追加されました。

参考 https://www.ansible.com/blog/networking-features-in-ansible-2-4

Ansible 2.4 時点では、 例えば ios_interfaeや、junos_interfaceモジュールなどの、以下のオプションによりチェックすることができます。

  • neighbors: CDP/LLDPのホスト、ポート情報
  • state: インターフェースの状態(presentabsentupdown
  • rx_rate: 受信レート(bps)がとの程度か
  • tx_rate: 送信レート(bps)がとの程度か

あくまで、チェックするためのオプションであるため、指定した通りに設定するという機能ではりません。

この記事では、Ansible 2.5 でios_vlanモジュールに追加された、associated_interfaces オプションを利用し、想定したインターフェースが特定の VLAN に所属されているかチェックする方法をご紹介します。


■ ネットワーク機器側の状態

ネットワーク機器側の VLAN と インターフェースの所属関係は以下の状態とします。

Switch13(config)#do sh vlan

VLAN Name                             Status    Ports
---- -------------------------------- --------- -------------------------------
1    default                          active    Gi1/0/1, Gi1/0/2, Gi1/0/3, Gi1/0/4
                                                Gi1/0/5, Gi1/0/6, Gi1/0/7, Gi1/0/8
                                                Gi1/0/9, Gi1/0/12, Gi1/0/14
                                                Gi1/0/15, Gi1/0/16, Gi1/0/17
                                                Gi1/0/18, Gi1/0/19, Gi1/0/20
                                                Gi1/0/21, Gi1/0/22, Gi1/0/23
13   VLAN0013                         active    Gi1/0/13
99   VLAN0099                         active    Gi1/0/10, Gi1/0/11
192  VLAN0192                         active    Gi1/0/24

(コンフィグ抜粋)

interface GigabitEthernet1/0/9
!
interface GigabitEthernet1/0/10
 switchport access vlan 99
 switchport mode access
!
interface GigabitEthernet1/0/11
 switchport access vlan 99
 switchport mode access
!
interface GigabitEthernet1/0/12
!

今回は、vlan99(Gi1/0/10, Gi1/0/11)をチェック対象とします。


■ 意図通りの状態の場合

チェックが成功するような Playbook で試します。

Playbook

先程ネットワーク機器側で確認したように、vlan99 に GigabitEthernet1/0/10GigabitEthernet1/0/11 が所属していることをチェックします。 所属インターフェースの指定には、 associated_interfaces オプションを利用します。

- hosts: ios
  gather_facts: no

    - name: check vlan
      ios_vlan:
        vlan_id: 99
        associated_interfaces:
            - GigabitEthernet1/0/10
            - GigabitEthernet1/0/11

実行結果

以下のように、ok となります。

(ansible25) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory ios_test.yml

PLAY [ios] **************************************************************************************************************
TASK [check vlan] *******************************************************************************************************
ok: [192.168.1.13]

PLAY RECAP **************************************************************************************************************
192.168.1.13               : ok=1    changed=0    unreachable=0    failed=0


■ 意図していない状態の場合

今度は、チェックが失敗するような Playbook で試します。

Playbook

先程とは異なり、 GigabitEthernet1/0/12 という vlan99 に所属しないインターフェースを追加で指定してみます。

- hosts: ios
  gather_facts: no

    - name: check vlan
      ios_vlan:
        vlan_id: 99
        associated_interfaces:
            - GigabitEthernet1/0/10
            - GigabitEthernet1/0/11
            - GigabitEthernet1/0/12    # not vlan99

実行結果

今度は、failed となります。"Interface GigabitEthernet1/0/12 not configured on vlan 99" というていねいなエラーメッセージが確認できます。

(ansible25) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory ios_test.yml

PLAY [ios] **************************************************************************************************************

TASK [check vlan] *******************************************************************************************************
fatal: [192.168.1.13]: FAILED! => {"changed": false, "msg": "Interface GigabitEthernet1/0/12 not configured on vlan 99"}
        to retry, use: --limit @/vagrant/ios_test.retry

PLAY RECAP **************************************************************************************************************
192.168.1.13               : ok=0    changed=0    unreachable=0    failed=1


■ 注意点

2点ほど注意点をあげます。

インターフェース名の指定はコンフィグ上の表記通りに

associated_interfaces オプションで指定するインターフェース名は、コンフィグ上の表記通りにする必要があります。

  • 正しい表記の指定例
        associated_interfaces:
            - GigabitEthernet1/0/10
            - GigabitEthernet1/0/11
  • 誤った表記の指定例(インターフェース名を省略してしまっている)
        associated_interfaces:
            - Gi1/0/10
            - Gi1/0/11

誤った表記の指定の仕方をすると、正しくチェックができませんので注意が必要です。

インターフェース名の指定が不足していても ok となる

例えば、vlan99に GigabitEthernet1/0/10GigabitEthernet1/0/11 が所属している状態で、以下のように associated_interfaces オプションで GigabitEthernet1/0/10 のみを指定した場合でも、チェック結果は ok となります。

    - name: check vlan
      ios_vlan:
        vlan_id: 99
        associated_interfaces:
            - GigabitEthernet1/0/10

そのため、チェックの意味合いとしては、

  • associated_interfaces オプションで指定したインターフェースが vlan_id オプションで指定した vlan に所属していること

になります。

  • vlan_id オプションで指定した vlan に associated_interfaces オプションで指定したインターフェースが過不足なく所属していること

ではないので注意が必要です。


■ まとめと補足

現段階ではチェックできるオプションは多くないですが、今後増えてくるかもしれません。 チェックしたいオプションがある場合は、利用を検討してみてはいかがでしょうか。

また、チェック(テスト)するという観点では、napalm_validate というサードパーティモジュールを利用する方法もあります。詳細は、AnsibleとNAPALMでネットワークをテストする をご参照下さい。

Ansible ネットワークモジュールにおける facts の扱い

■ はじめに

Ansible のネットワークモジュールは、通常のモジュールとは facts の扱いが異なります。 この記事では、ネットワークモジュール固有の facts の扱い方についてご紹介します。 想定バージョンは Ansible 2.5.2 です。


■ 通常の facts は ネットワーク機器側ではなく Ansible ホスト側

ios_commandjunos_config などのネットワークモジュールは、Ansible ホスト側で Python スクリプトが実行されます。 そのため、 gather_facts: yes (デフォルト) で取得される facts は、操作対象のネットワーク機器側ではなく、Ansible ホスト側の facts になります。


■ ネットワーク機器側の facts は専用モジュールで取得

ネットワーク機器側のホスト名や、インターフェース情報などの facts を取得するにためには、 ios_facts や junos_facts など、ネットワークプラットフォームごとに用意された専用の *_facts モジュールを利用します。

サンプルPlaybook

Junosの facts を取得するjunos_facts モジュールを利用するサンプルです。

gather_subset オプションを省略した場合は、コンフィグ以外の facts を取得します。

- hosts: junos
  connection: netconf
  gather_facts: no

  tasks:
    - name: facts
      junos_facts:

    - name: debug
      debug:
        msg: "{{ ansible_facts }}"

ここでは、Ansible ホスト側の facts は必要ないとしているので、 gather_facts: no にしています。 また、msg: "{{ ansible_facts }}" と指定しているように、Ansible 2.5 から facts は ansible_facts.* という名前空間配下でも参照可能です

実行結果

(ansible25) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory junos_test.yml --tag=d

PLAY [junos] *************************************************************************************************

TASK [facts] *************************************************************************************************
ok: [172.16.0.1]

TASK [debug] *************************************************************************************************
ok: [172.16.0.1] => {
    "msg": {
        "net_filesystems": [
            "/dev/ad0s1a",
            "devfs",
            "/dev/md0",
            "/cf",
            "devfs",
            "procfs",
            "/dev/bo0s1e",
            "/dev/md1",
            "/cf/var/jail",
            "/cf/var/log",
            "devfs",
            "/dev/md2"
        ],
        "net_gather_subset": [
            "hardware",
            "default",
            "interfaces"
        ],
        "net_has_2RE": false,
        "net_hostname": "vsrx1",
        "net_interfaces": {
            ".local.": {
                "admin-status": "up",
                "macaddress": "Unspecified",
                "mtu": "Unlimited",
                "oper-status": "up",
                "speed": "Unlimited",
                "type": "Loopback"
            },
            "dsc": {
                "admin-status": "up",
                "macaddress": "Unspecified",
                "mtu": "Unlimited",
                "oper-status": "up",
                "speed": "Unspecified",
                "type": "Software-Pseudo"
            },
            "ge-0/0/0": {
                "admin-status": "up",
                "macaddress": "08:00:27:ae:**:**",
                "mtu": "1514",
                "oper-status": "up",
                "speed": "1000mbps",
                "type": null
            },
            "ge-0/0/1": {
                "admin-status": "up",
                "macaddress": "08:00:27:af:**:**",
                "mtu": "1514",
                "oper-status": "up",
                "speed": "1000mbps",
                "type": null
            },
            (略)
            }
        },
        "net_memfree_mb": 76448,
        "net_memtotal_mb": 1031572,
        "net_model": "firefly-perimeter",
        "net_modules": [
            {
                "name": "Midplane"
            },
            {
                "name": "System IO"
            },
            {
                "description": "FIREFLY-PERIMETER RE",
                "name": "Routing Engine"
            },
            {
                "description": "Virtual FPC",
                "name": "FPC 0"
            },
            {
                "name": "Power Supply 0"
            }
        ],
        "net_routing_engines": {
            "null": {
                "cpu_background": "0",
                "cpu_idle": "100",
                "cpu_interrupt": "0",
                "cpu_system": "0",
                "cpu_user": "0",
                "last_reboot_reason": "Router rebooted after a normal shutdown.",
                "load_average_fifteen": "0.00",
                "load_average_five": "0.02",
                "load_average_one": "0.00",
                "memory_control_plane": "594",
                "memory_control_plane_used": "321",
                "memory_control_plane_util": "54",
                "memory_data_plane": "430",
                "memory_data_plane_used": "211",
                "memory_data_plane_util": "49",
                "memory_system_total": "1024",
                "memory_system_total_used": "532",
                "memory_system_total_util": "52",
                "model": "FIREFLY-PERIMETER RE",
                "slot": null,
                "start_time": "2018-04-11 13:23:07 UTC",
                "status": "Testing",
                "up_time": "1 day, 17 hours, 40 minutes, 58 seconds"
            }
        },
        "net_serialnum": "a9039f******",
        "net_version": null
    }
}

PLAY RECAP ***************************************************************************************************
172.16.0.1                 : ok=2    changed=0    unreachable=0    failed=0

(ansible25) [vagrant@centos7 vagrant]$

ホスト名 vsrx1 や、インターフェースの情報などが facts として取得できたことが分かります。


■ 補足

https://www.ansible.com/blog/coming-soon-networking-features-in-ansible-2.5 上記の Ansible 2.5 正式リリース前の公式ブログに、

Fact gathering for network_cli and netconf connection methods are not turned on by default.

という記述がありますが、実際は異なるようです。ネットワークモジュールのベストプラクティスはあくまでも、必要でない限りは、gather_facts: no を指定しておくことのようです。

Ansible 2.5 から facts は ansible_facts.* という名前空間配下でも参照可能

はじめに

Ansible 2.5 から facts は ansible_facts.* という名前空間配下でも参照可能になりました。

  • ansilbe_distribution であれば
    • ansible_facts.distribution でも参照可能
  • ansilbe_nodename であれば
    • ansible_facts.nodename でも参照可能

そのため、ansible_facts を参照すると、配下の facts の一覧を取得できます。

サンプル Playbook

- hosts: webservers
  gather_facts: yes
  
  tasks:
    - name: facts
      debug:      
        msg: "{{ ansible_facts }}"   

実行結果

$ ansible-playbook -i inventory junos_test.yml --tag=d

PLAY [webservers] ***************************************************

TASK [Gathering Facts] *******************************************************
ok: [172.16.0.1]

TASK [facts list] ************************************************************
ok: [172.16.0.1] => {
    "msg": {
        "all_ipv4_addresses": [
            "172.17.0.1",
            "172.16.0.9",
            "10.0.2.15"
        ],
        "all_ipv6_addresses": [
            "fe80::42:75ff:fe85:****",
            "fe80::a00:27ff:fe74:****",
            "fe80::5054:ff:feda:****"
        ],
        "ansible_local": {},
        "apparmor": {
            "status": "disabled"
        },
        "architecture": "x86_64",
        "bios_date": "12/01/2006",
        "bios_version": "VirtualBox",
    
(略)
        "userspace_bits": "64",
        "virtualization_role": "guest",
        "virtualization_type": "virtualbox"
    }
}

PLAY RECAP *****************************************************************
172.16.0.1                 : ok=2    changed=0    unreachable=0    failed=0

まとめ

既存の ansible_ というプレフィックよりも ansible_facts という名前空間で整理されているほうが、まとめて処理がしやすいので使いやすいと感じました。

  • 参考リンク

www.ansible.com

Ansible Network Team によってメンテナンスされているネットワークモジュール一覧

Ansible Network Team によってメンテナンスされているネットワークモジュール一覧は以下のページにまとめられています。

Modules Maintained by the Ansible Network Team — Ansible Documentation

(参考リンク)

なお、サポートされないものも含めたネットワークモジュールは以下のページにまとめられています。

Network modules — Ansible Documentation

Ansible ネットワークモジュールの aggregate オプションによる繰り返し処理の特徴

■ はじめに

Ansible 2.4 から、ネットワークモジュールの一部で、繰り返しを伴う設定を効率的に投入できる aggregate というオプションが利用できるようになりました。 この記事では、既存の with_items による繰り返しとの比較を通じて、動作の紹介をします。 検証環境は Ansible 2.5.2 です。


■ aggregate オプションの利用例

aggregate オプションは以下のような利用の仕方になります。(junos_static_route モジュールでの例)

サンプル

- name: set static route
  junos_static_route:
    aggregate:
      - { address: 172.16.1.0/24, next_hop: 10.0.0.1 }
      - { address: 172.16.2.0/24, next_hop: 10.0.0.2 }
      - { address: 172.16.3.0/24, next_hop: 10.0.0.3 }

雰囲気である程度予想できるかもしれませんが、上記の場合3つのスタティックルートが追加されます。

junos_static_routeモジュールにはもともと、 addressnext_hop などのオプションがあり、それらを繰り返し指定するために、 aggregate オプション配下に指定する形になります。

実行結果

(ansible25) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory junos_test.yml

PLAY [junos] ****************************************************************************

TASK [set static route (aggregate)] *****************************************************
changed: [172.16.0.1]

PLAY RECAP ******************************************************************************
172.16.0.1                 : ok=1    changed=1    unreachable=0    failed=0

(ansible25) [vagrant@centos7 vagrant]$

ポイントは、スタティックルートを追加する処理を3回繰り返すのではなく、1回の処理で完了 している点です。 これは、繰り返し分のコンフィグを生成してから、1つのタスクとしていっぺんにコンフィグ投入するためです。 仮に、特定の1つのスタティックルートにパラメータエラーがあった場合は、3つのスタティックルートとも設定が入りません。

ネットワーク機器側の確認

念のため確認します。

root@vsrx1# show routing-options static | display set
set routing-options static route 172.16.1.0/24 next-hop 10.0.0.1
set routing-options static route 172.16.2.0/24 next-hop 10.0.0.2
set routing-options static route 172.16.3.0/24 next-hop 10.0.0.3

無事に追加されました。


■ aggregate オプションが利用できるモジュール例

一例ですが、以下のようなモジュールで aggregate オプションが利用できます。 公式ドキュメント内に一覧は見当たりませんでしたが、*_config*_commmand のように、コンフィグをそのまま指定するタイプのモジュールではなく、コンフィグのオプションがオプションとして抽象化されたモジュールが対象のようです。


■ ちなみに・・with_items の場合

繰り返し処理というと with_itemsloop(Asnible2.5から利用可)を思い浮かべると思います。先程のサンプルを with_itmes で記載すると以下のようになります。

サンプル

- name: set static route (with_items)
  junos_static_route:
    address: "{{item.address}}"
    next_hop: "{{item.next_hop}}"
  with_items:
    - { address: 172.16.1.0/24, next_hop: 10.0.0.1 }
    - { address: 172.16.2.0/24, next_hop: 10.0.0.2 }
    - { address: 172.16.3.0/24, next_hop: 10.0.0.3 }

実行結果

(ansible25) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory junos_test.yml

PLAY [junos] ****************************************************************************

TASK with_items) ***********************************************************************
changed: [172.16.0.1] => (item={u'next_hop': u'10.0.0.1', u'address': u'172.16.1.0/24'})
changed: [172.16.0.1] => (item={u'next_hop': u'10.0.0.2', u'address': u'172.16.2.0/24'})
changed: [172.16.0.1] => (item={u'next_hop': u'10.0.0.3', u'address': u'172.16.3.0/24'})

PLAY RECAP ******************************************************************************
172.16.0.1                 : ok=1    changed=1    unreachable=0    failed=0

(ansible25) [vagrant@centos7 vagrant]$

おなじみの with_items らしいログとなりました。 仮に、特定の1つのスタティックルートにパラメータエラーがあった場合は、そのスタティックルート以外は設定されます。


■ aggregate と with_items の比較

まとめると以下のようになります。

比較項目 aggregate with_items
利用可能バージョン 2.4以上
利用可能モジュール 一部 すべて
処理速度 早い 遅い
パラメータエラー時 (junos_static_route で確認) 1つも設定されない エラー分のみスキップ


■ まとめ

個人的には aggregate オプションのほうが早いですし、よりフェールファーストらしい挙動になるので、利用できるモジュールの場合は特徴を理解した上で、 aggregate オプションを利用したほうが良いと思いました。


参考資料(公式ブログ)

www.ansible.com