てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] netconf_get モジュールで Cisco IOS XE の情報を NETCONF で取得する

はじめに

以前、restconf_get モジュールで Cisco IOS XE のインターフェース情報を取得してみる という記事で、Ansible で RESTCONF 経由で情報取得する Playbook をご紹介しました。

この記事では RESTCONF ではく、netconf_get モジュールCisco IOS XE の情報を取得する Playbook をご紹介します。

サンプルは、 running-config、インターフェース状態、ルーティングテープルの取得です。

環境


■ 準備

Playook 実行に必要なファイルを作成しますす。

インベントリファイル

ホスト情報を示すインベントリファイルを作成します。

  • inventory.ini
[ios]
ios1 ansible_host=ios-xe-mgmt.cisco.com

変数定義ファイル

インベントリファイルで定義したグループ ios が利用する変数を定義するファイルを作成します。

  • group_vars/ios.yml
# ansible_network_os: ios  # 不要
ansible_connection: netconf
ansible_port: 10000
ansible_user: developer
ansible_password: dummy
変数名 説明
ansible_connection 一番のポイントです。netconf コネクションプラグインを利用するために netconf を指定します。
ansible_port NETCONF 接続に利用するポートです。netconf コネクションプラグインの場合のデフォルトは 830 です。ここでは環境の都合で 10000 を指定します。
ansible_user ユーザー名を指定します。
ansible_password ここではダミーの値を記載しています。必要に応じて ansible-vault で暗号化します。


■ Playbookサンプル1: running-config を取得

source オプションで running を指定すると、running-config を NETCONF / YANG 形式で取得できます。

Playbook

- hosts: ios
  gather_facts: no

  tasks:
    - name: get runnnig-config
      netconf_get:
        source: running
      register: result

    - name: debug result
      debug:
        msg: "{{ result.stdout }}"

実行結果

TASK [get runnnig-config] *******************************************************************************************************************************
unable to load netconf plugin for network_os ios, falling back to default plugin
ok: [ios1]

TASK [debug result] *************************************************************************************************************************************
ok: [ios1] => {
    "msg": "<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><native xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-native\"><version>16.11</version><boot-start-marker/><boot-end-marker/><banner><motd><banner>^C</banner></motd></banner><memory><free><low-watermark><processor>80557</processor></low-watermark></free></memory><call-home><contact-email-addr xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-call-home\">sch-smart-licensing@cisco.com</contact-email-addr><profile xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-call-home\"><profile-name>CiscoTAC-1</profile-name><active>true</active></profile></call-home><service><timestamps><debug><datetime><msec/></datetime></debug><log><datetime><msec/></datetime></log></timestamps><call-home/></service><platform><console xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-platform\"><output>virtual</output></console></platform><hostname>csr1000v-1</hostname><enable><secret><type>9</type><secret>$9$PvdeTeuxxh0ygk$PSg0GTG2I7bluI51e.IvfEu2uxy56e/9/PgqzFUklso</secret></secret></enable><username><name>cisco</name><privilege>15</privilege><secret><encryption>9</encryption><secret>$9$COf3Q4xfzT.JxE$L3hvSkDv874Qrh8Hzdv/rPQjLNOjreYG2ocffHG7rls</secret></secret></username><username><name>developer</name><privilege>15</privilege><secret><encryption>9</encryption><secret>$9$fhUXi6Xg438iAE$..VhXRCHiDQy3K2zmZUl9iZLbQJ9wpUtQZwQxSRESc2</secret></secret></username><username><name>root</name><privilege>15</privilege><secret><encryption>9</encryption><secret>$9$FfUDIPBFcs03AE$MyLIWEuiRle8p3yGflAGTcrJA6BUUh/oWtyyfoMQXSI</secret></secret></username><ip><domain><name>abc.inc</name></domain><forward-protocol><protocol>nd</protocol></forward-protocol><route><ip-route-interface-forwarding-list><prefix>0.0.0.0</prefix><mask>0.0.0.0</mask><fwd-list><fwd>GigabitEthernet1</fwd><interface-next-hop><ip-address>10.10.20.254</ip-address></interface-next-hop></fwd-list></ip-route-interface-forwarding-list></route><scp><server><enable/></server></scp><ssh><rsa><keypair-name>ssh-key</keypair-name></rsa><version>2</version></ssh><access-list><extended xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-acl\"><name>meraki-fqdn-dns</name></extended></access-list><http xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-http\"><authentication><local/></authentication><server>true</server><secure-server>true</secure-server></http></ip><interface><GigabitEthernet><name>1</name><description>MANAGEMENT INTERFACE - DON'T TOUCH ME</description><ip><address><primary><address>10.10.20.48</address><mask>255.255.255.0</mask></primary></address></ip><mop><enabled>false</enabled><sysid>false</sysid></mop><negotiation xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet\"><auto>true</auto></negotiation></GigabitEthernet><GigabitEthernet><name>2</name><description>Network Interface</description><shutdown/><mop><enabled>false</enabled><sysid>false</sysid></mop><negotiation xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet\"><auto>true</auto></negotiation></GigabitEthernet><GigabitEthernet><name>3</name><description>Network Interface</description><shutdown/><mop><enabled>false</enabled><sysid>false</sysid></mop><negotiation xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet\"><auto>true</auto></negotiation></GigabitEthernet></interface><control-plane/><login><on-success><log/></on-success></login><multilink><bundle-name xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-ppp\">authenticated</bundle-name></multilink><redundancy/><spanning-tree><extend xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-spanning-tree\"><system-id/></extend></spanning-tree><subscriber><templating/></subscriber><crypto><pki xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-crypto\"><certificate><chain><name>SLA-TrustPoint</name><certificate><serial>01</serial><certtype>ca</certtype></certificate></chain><chain><name>TP-self-signed-1059130051</name><certificate><serial>01</serial><certtype>self-signed</certtype></certificate></chain></certificate><trustpoint><id>SLA-TrustPoint</id><enrollment><pkcs12/></enrollment><revocation-check>crl</revocation-check></trustpoint><trustpoint><id>TP-self-signed-1059130051</id><enrollment><selfsigned/></enrollment><revocation-check>none</revocation-check><subject-name>cn=IOS-Self-Signed-Certificate-1059130051</subject-name></trustpoint></pki></crypto><license><udi><pid>CSR1000V</pid><sn>9MKH579CKHN</sn></udi><boot><level><ax/></level></boot></license><line><console><first>0</first><exec-timeout><minutes>0</minutes><seconds>0</seconds></exec-timeout><stopbits>1</stopbits></console><vty><first>0</first><last>4</last><login><local/></login><transport><input><input>ssh</input></input></transport></vty></line><diagnostic xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XE-diagnostics\"><bootup><level>minimal</level></bootup></diagnostic></native><licensing xmlns=\"http://cisco.com/ns/yang/cisco-smart-license\"><config><enable>false</enable><privacy><hostname>false</hostname><version>false</version></privacy><utility><utility-enable>false</utility-enable></utility></config></licensing><acl xmlns=\"http://openconfig.net/yang/acl\"><acl-sets><acl-set><name>meraki-fqdn-dns</name><type>ACL_IPV4</type><config><name>meraki-fqdn-dns</name><type>ACL_IPV4</type></config></acl-set></acl-sets></acl><interfaces xmlns=\"http://openconfig.net/yang/interfaces\"><interface><name>GigabitEthernet1</name><config><name>GigabitEthernet1</name><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><description>MANAGEMENT INTERFACE - DON'T TOUCH ME</description><enabled>true</enabled></config><subinterfaces><subinterface><index>0</index><config><index>0</index><description>MANAGEMENT INTERFACE - DON'T TOUCH ME</description><enabled>true</enabled></config><ipv4 xmlns=\"http://openconfig.net/yang/interfaces/ip\"><addresses><address><ip>10.10.20.48</ip><config><ip>10.10.20.48</ip><prefix-length>24</prefix-length></config></address></addresses></ipv4><ipv6 xmlns=\"http://openconfig.net/yang/interfaces/ip\"><config><enabled>false</enabled></config></ipv6></subinterface></subinterfaces><ethernet xmlns=\"http://openconfig.net/yang/interfaces/ethernet\"><config><mac-address>00:50:56:bb:e9:9c</mac-address><auto-negotiate>true</auto-negotiate></config></ethernet></interface><interface><name>GigabitEthernet2</name><config><name>GigabitEthernet2</name><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><description>Network Interface</description><enabled>false</enabled></config><subinterfaces><subinterface><index>0</index><config><index>0</index><description>Network Interface</description><enabled>false</enabled></config><ipv6 xmlns=\"http://openconfig.net/yang/interfaces/ip\"><config><enabled>false</enabled></config></ipv6></subinterface></subinterfaces><ethernet xmlns=\"http://openconfig.net/yang/interfaces/ethernet\"><config><mac-address>00:50:56:bb:77:1a</mac-address><auto-negotiate>true</auto-negotiate></config></ethernet></interface><interface><name>GigabitEthernet3</name><config><name>GigabitEthernet3</name><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><description>Network Interface</description><enabled>false</enabled></config><subinterfaces><subinterface><index>0</index><config><index>0</index><description>Network Interface</description><enabled>false</enabled></config><ipv6 xmlns=\"http://openconfig.net/yang/interfaces/ip\"><config><enabled>false</enabled></config></ipv6></subinterface></subinterfaces><ethernet xmlns=\"http://openconfig.net/yang/interfaces/ethernet\"><config><mac-address>00:50:56:bb:eb:1e</mac-address><auto-negotiate>true</auto-negotiate></config></ethernet></interface></interfaces><lldp xmlns=\"http://openconfig.net/yang/lldp\"><config><enabled>false</enabled></config><interfaces><interface><name>GigabitEthernet1</name><config><name>GigabitEthernet1</name><enabled>true</enabled></config></interface><interface><name>GigabitEthernet2</name><config><name>GigabitEthernet2</name><enabled>true</enabled></config></interface><interface><name>GigabitEthernet3</name><config><name>GigabitEthernet3</name><enabled>true</enabled></config></interface></interfaces></lldp><network-instances xmlns=\"http://openconfig.net/yang/network-instance\"><network-instance><name>default</name><config><name>default</name><type xmlns:oc-ni-types=\"http://openconfig.net/yang/network-instance-types\">oc-ni-types:DEFAULT_INSTANCE</type><description>default-vrf [read-only]</description></config><tables><table><protocol xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:DIRECTLY_CONNECTED</protocol><address-family xmlns:oc-types=\"http://openconfig.net/yang/openconfig-types\">oc-types:IPV4</address-family><config><protocol xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:DIRECTLY_CONNECTED</protocol><address-family xmlns:oc-types=\"http://openconfig.net/yang/openconfig-types\">oc-types:IPV4</address-family></config></table><table><protocol xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:DIRECTLY_CONNECTED</protocol><address-family xmlns:oc-types=\"http://openconfig.net/yang/openconfig-types\">oc-types:IPV6</address-family><config><protocol xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:DIRECTLY_CONNECTED</protocol><address-family xmlns:oc-types=\"http://openconfig.net/yang/openconfig-types\">oc-types:IPV6</address-family></config></table><table><protocol xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:STATIC</protocol><address-family xmlns:oc-types=\"http://openconfig.net/yang/openconfig-types\">oc-types:IPV4</address-family><config><protocol xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:STATIC</protocol><address-family xmlns:oc-types=\"http://openconfig.net/yang/openconfig-types\">oc-types:IPV4</address-family></config></table><table><protocol xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:STATIC</protocol><address-family xmlns:oc-types=\"http://openconfig.net/yang/openconfig-types\">oc-types:IPV6</address-family><config><protocol xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:STATIC</protocol><address-family xmlns:oc-types=\"http://openconfig.net/yang/openconfig-types\">oc-types:IPV6</address-family></config></table></tables><protocols><protocol><identifier xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:STATIC</identifier><name>DEFAULT</name><config><identifier xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:STATIC</identifier><name>DEFAULT</name></config><static-routes><static><prefix>0.0.0.0/0</prefix><config><prefix>0.0.0.0/0</prefix></config><next-hops><next-hop><index>GigabitEthernet1_10.10.20.254</index><config><index>GigabitEthernet1_10.10.20.254</index><next-hop>10.10.20.254</next-hop></config><interface-ref><config><interface>GigabitEthernet1</interface></config></interface-ref></next-hop></next-hops></static></static-routes></protocol><protocol><identifier xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:DIRECTLY_CONNECTED</identifier><name>DEFAULT</name><config><identifier xmlns:oc-pol-types=\"http://openconfig.net/yang/policy-types\">oc-pol-types:DIRECTLY_CONNECTED</identifier><name>DEFAULT</name></config></protocol></protocols></network-instance></network-instances><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"><interface><name>GigabitEthernet1</name><description>MANAGEMENT INTERFACE - DON'T TOUCH ME</description><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><enabled>true</enabled><ipv4 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ip\"><address><ip>10.10.20.48</ip><netmask>255.255.255.0</netmask></address></ipv4><ipv6 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ip\"/></interface><interface><name>GigabitEthernet2</name><description>Network Interface</description><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><enabled>false</enabled><ipv4 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ip\"/><ipv6 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ip\"/></interface><interface><name>GigabitEthernet3</name><description>Network Interface</description><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><enabled>false</enabled><ipv4 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ip\"/><ipv6 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ip\"/></interface></interfaces><nacm xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-acm\"><enable-nacm>true</enable-nacm><read-default>deny</read-default><write-default>deny</write-default><exec-default>deny</exec-default><enable-external-groups>true</enable-external-groups><rule-list><name>admin</name><group>PRIV15</group><rule><name>permit-all</name><module-name>*</module-name><access-operations>*</access-operations><action>permit</action></rule></rule-list></nacm><routing xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\"><routing-instance><name>default</name><description>default-vrf [read-only]</description><routing-protocols><routing-protocol><type>static</type><name>1</name><static-routes><ipv4 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ipv4-unicast-routing\"><route><destination-prefix>0.0.0.0/0</destination-prefix><next-hop><outgoing-interface>GigabitEthernet1</outgoing-interface></next-hop></route></ipv4></static-routes></routing-protocol></routing-protocols></routing-instance></routing></data>"
}


■ Playbookサンプル2: インターフェースの状態を取得

filter オプションで、XMLで階層を指定すると取得する情報を絞れます。

インターフェースの情報は以下のようなフィルターをかけます。(参考: ietf-interfaces.yang

          <interfaces-state xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
          </interfaces-state>

コマンドでいうと、show interfaces あたりでしょうか。

Playbook

- hosts: ios
  gather_facts: no

  tasks:
    - name: get interfaces state
      netconf_get:
        filter: |-
          <interfaces-state xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
          </interfaces-state>
      register: result

    - name: debug result
      debug:
        msg: "{{ result.stdout }}"

実行結果

TASK [get interfaces state] *****************************************************************************************************************************
unable to load netconf plugin for network_os ios, falling back to default plugin
ok: [ios1]

TASK [debug result] *************************************************************************************************************************************
ok: [ios1] => {
    "msg": "<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><interfaces-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"><interface><name>GigabitEthernet1</name><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><admin-status>up</admin-status><oper-status>up</oper-status><last-change>2020-06-12T23:57:31.000876+00:00</last-change><if-index>1</if-index><phys-address>00:50:56:bb:e9:9c</phys-address><speed>1024000000</speed><statistics><discontinuity-time>2020-06-12T23:56:13.00004+00:00</discontinuity-time><in-octets>5425046053</in-octets><in-unicast-pkts>9040594</in-unicast-pkts><in-broadcast-pkts>0</in-broadcast-pkts><in-multicast-pkts>0</in-multicast-pkts><in-discards>0</in-discards><in-errors>0</in-errors><in-unknown-protos>0</in-unknown-protos><out-octets>36343921</out-octets><out-unicast-pkts>118825</out-unicast-pkts><out-broadcast-pkts>0</out-broadcast-pkts><out-multicast-pkts>0</out-multicast-pkts><out-discards>0</out-discards><out-errors>0</out-errors></statistics></interface><interface><name>GigabitEthernet2</name><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><admin-status>down</admin-status><oper-status>down</oper-status><last-change>2020-06-12T23:57:23.000416+00:00</last-change><if-index>2</if-index><phys-address>00:50:56:bb:77:1a</phys-address><speed>1024000000</speed><statistics><discontinuity-time>2020-06-12T23:56:13.00004+00:00</discontinuity-time><in-octets>300</in-octets><in-unicast-pkts>5</in-unicast-pkts><in-broadcast-pkts>0</in-broadcast-pkts><in-multicast-pkts>0</in-multicast-pkts><in-discards>0</in-discards><in-errors>0</in-errors><in-unknown-protos>0</in-unknown-protos><out-octets>0</out-octets><out-unicast-pkts>0</out-unicast-pkts><out-broadcast-pkts>0</out-broadcast-pkts><out-multicast-pkts>0</out-multicast-pkts><out-discards>0</out-discards><out-errors>0</out-errors></statistics></interface><interface><name>GigabitEthernet3</name><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:ethernetCsmacd</type><admin-status>down</admin-status><oper-status>down</oper-status><last-change>2020-06-12T23:57:23.00042+00:00</last-change><if-index>3</if-index><phys-address>00:50:56:bb:eb:1e</phys-address><speed>1024000000</speed><statistics><discontinuity-time>2020-06-12T23:56:13.00004+00:00</discontinuity-time><in-octets>120</in-octets><in-unicast-pkts>2</in-unicast-pkts><in-broadcast-pkts>0</in-broadcast-pkts><in-multicast-pkts>0</in-multicast-pkts><in-discards>0</in-discards><in-errors>0</in-errors><in-unknown-protos>0</in-unknown-protos><out-octets>0</out-octets><out-unicast-pkts>0</out-unicast-pkts><out-broadcast-pkts>0</out-broadcast-pkts><out-multicast-pkts>0</out-multicast-pkts><out-discards>0</out-discards><out-errors>0</out-errors></statistics></interface><interface><name>Control Plane</name><type xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\">ianaift:other</type><admin-status>up</admin-status><oper-status>up</oper-status><last-change>2020-06-12T23:57:20.000993+00:00</last-change><if-index>0</if-index><phys-address>00:00:00:00:00:00</phys-address><speed>10240000000</speed><statistics><discontinuity-time>2020-06-12T23:56:13.00004+00:00</discontinuity-time><in-octets>0</in-octets><in-unicast-pkts>0</in-unicast-pkts><in-broadcast-pkts>0</in-broadcast-pkts><in-multicast-pkts>0</in-multicast-pkts><in-discards>0</in-discards><in-errors>0</in-errors><in-unknown-protos>0</in-unknown-protos><out-octets>0</out-octets><out-unicast-pkts>0</out-unicast-pkts><out-broadcast-pkts>0</out-broadcast-pkts><out-multicast-pkts>0</out-multicast-pkts><out-discards>0</out-discards><out-errors>0</out-errors></statistics></interface></interfaces-state></data>"
}

netconf_get モジュールでは、display オプションで json を指定することで、表示を JSON にすることができます。前に pip install jxmlease しておく必要があります。

Playbook (JSON版)

- hosts: ios
  gather_facts: no

  tasks:
    - name: get interfaces state
      netconf_get:
        filter: |-
          <interfaces-state xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
          </interfaces-state>
        display: json
      register: result

    - name: debug result
      debug:
        msg: "{{ result.output }}"  # .stdout ではなく .output

実行結果 (JSON版)

TASK [debug result] *************************************************************************************************************************************
ok: [ios1] => {
    "msg": {
        "data": {
            "interfaces-state": {
                "interface": [
                    {
                        "admin-status": "up",
                        "if-index": "1",
                        "last-change": "2020-06-12T23:57:31.000864+00:00",
                        "name": "GigabitEthernet1",
                        "oper-status": "up",
...(略)...


■ サンプル3: ルーティングテーブルの取得

ルーティングテーブルを取得したい場合は、以下のようなフィルターをかけます、(参考: ietf-routing.yang

          <routing-state xmlns="urn:ietf:params:xml:ns:yang:ietf-routing">
          </routing-state>

先程指定した display: json をここでも指定します。

Playbook

- hosts: ios
  gather_facts: no

  tasks:
    - name: get routing table
      netconf_get:
        filter: |-
          <routing-state xmlns="urn:ietf:params:xml:ns:yang:ietf-routing">
          </routing-state>
        display: json
      register: result

    - name: debug result
      debug:
        msg: "{{ result.output }}"  # .stdout ではなく .output

実行結果

TASK [get routing table] ********************************************************************************************************************************
unable to load netconf plugin for network_os ios, falling back to default plugin
ok: [ios1]

TASK [debug result] *************************************************************************************************************************************
ok: [ios1] => {
    "msg": {
        "data": {
            "routing-state": {
                "routing-instance": [
                    {
                        "name": "default",
                        "ribs": {
                            "rib": [
                                {
                                    "address-family": "ipv4",
                                    "default-rib": "false",
                                    "name": "ipv4-default",
                                    "routes": {
                                        "route": [
                                            {
                                                "active": "",
                                                "destination-prefix": "0.0.0.0/0",
                                                "metric": "1",
                                                "next-hop": {
                                                    "next-hop-address": "10.10.20.254",
                                                    "outgoing-interface": "GigabitEthernet1"
                                                },
                                                "route-preference": "1",
                                                "source-protocol": "static"
                                            },
                                            {
                                                "active": "",
                                                "destination-prefix": "10.10.20.0/24",
                                                "metric": "0",
                                                "next-hop": {
                                                    "next-hop-address": "0.0.0.0",
                                                    "outgoing-interface": "GigabitEthernet1"
                                                },
                                                "route-preference": "0",
                                                "source-protocol": "direct"
                                            },
                                            {
                                                "active": "",
                                                "destination-prefix": "10.10.20.48/32",
                                                "metric": "0",
                                                "next-hop": {
                                                    "next-hop-address": "0.0.0.0",
                                                    "outgoing-interface": "GigabitEthernet1"
                                                },
                                                "route-preference": "0",
                                                "source-protocol": "direct"
                                            }
                                        ]
                                    }
                                },
                                {
                                    "address-family": "ipv6",
                                    "default-rib": "false",
                                    "name": "ipv6-default"
                                }
                            ]
                        },
                // ...(略)...
                ]
            }
        }
    }
}


■ おわりに

RESTCONF 同様、NETCONF は 構造化データを扱えるため、assert モジュールによる状態確認や、レポートに応用しやすいと思いました。

[Ansible] 変数名に使える文字

利用できない変数名を利用しようとしてハマったことはありませんでしょうか。

私はあります。

変数名に使える文字は公式ドキュメントに掲載されています。一度目を通しておくと、ハマらなくて済みそうです。

docs.ansible.com

上記は devel ブランチのドキュメントで、最近 _ で始まる変数名も有効であることが明記されました。

ルールを私なりにまとめると以下のとおりです。

[Ansible] 変数名を参照する際に "{{ varname }}" のようにクォーテーションで囲う理由

はじめに

2020/06/09 開催の【リモート開催】Ansibleもくもく会 (サーバ編 & NW編)2020.06 にメンターとして参加させていただきました。

いただいた質問の中に、変数名の参照の際にダブルクォーテーションで囲う場合と囲わない場合があるが必須?というものがありました。

確かにテキストの中では、囲ったり囲ってなかったりしました。

普段、癖で囲っているので、そういえばなんでだろうと思い、その場で調べた結果をこちらにも共有します。

[2020/06/10 追記] なお、公式ドキュメントを quote variable で検索しました。

{ で始まる文字列をディクショナリと認識させないため

答えはこちらのページにありました。

docs.ansible.com

{ から始まると、YAMLシンタックス的にディクショナリだと解釈しようとするため、というのが理由でした。

YAML Syntax — Ansible Documentation にも

If a value after a colon starts with a “{“, YAML will think it is a dictionary, so you must quote it

とあります。

例えば、以下のような場合は囲う必要があります。

        msg: "{{ greeting }}"    # OK

以下のように、囲わない場合は、エラーになります。

        msg: {{ greeting }}     # エラー
  • エラー
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: Expecting value: line 1 column 1 (char 0)

Syntax Error while loading YAML.
  found unacceptable key (unhashable type: 'AnsibleMapping')

The error appears to be in '/home/studentXX/ansible-files/test.yml': line 11, column 15, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

      debug:
        msg: {{ greeting }}
              ^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:

    with_items:
      - {{ foo }}

Should be written as:

    with_items:
      - "{{ foo }}"

一方で、これはエラーになりません。

        msg: msg is {{ greeting }}   # セーフだが・・

まとめ

混乱を避けるため、クォーテーションで囲うように統一するのが良いと思いました。

[Ansible] 「つまずき Ansible 【Part4】インターフェースとOSPFの設定」ふりかえり

はじめに

2020/06/06 に、YouTube Live で「つまずき Ansible 【Part4】インターフェースとOSPFの設定」という配信をしました。 実際に作業しながらエラーと戦って進めるシリーズです。

tekunabe.connpass.com

今回は、Cisco IOS の機器に、インターフェースの有効化、IPアドレスの設定、OSPFを有効化する Playbook を作りました。

つまずいたエラーと原因、対処をふりかえります。

動画

www.youtube.com


■ OSPFの設定

Unsupported parameters というエラーが発生

TASK [enalbe ospf] *********************************************************************************************** fatal: [rt01]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "Unsupported parameters for (ios_config) module: parates Supported parameters include: after, auth_pass, authorize, backup, backup_options, before, defaults, diff_against, diff_ignore_lines, host, intended_config, lines, match, multiline_delimiter, parents, password, port, provider, replace, running_config, save_when, src, ssh_keyfile, timeout, username"}

原因

parents オプションのスペルが誤っていた。

対処

parents に修正して再実行。

なんともいえないエラーが発生

An exception occurred during task execution. To see the full traceback, use -vvv. The error was: rt01(config)# fatal: [rt01]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "module_stderr": "Traceback (most recent call last):\n File \"/home/vagrant/.ansible/tmp/ansible-local-28450uu8hydjf/ansible-tmp-1591442691.3088439-28456-252030280888868/AnsiballZ_ios_config.py\", line 102, in \n ansiballz_main()\n File \"/home/vagrant/.ansible/tmp/ansible-local-28450uu8hydjf/ansible-tmp-1591442691.3088439-28456-252030280888868/AnsiballZ_ios_config.py\", line 94, in ansiballz_main\n invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n File \"/home/vagrant/.ansible/tmp/ansible-local-28450uu8hydjf/ansible-tmp-1591442691.3088439-28456-252030280888868/AnsiballZ_ios_config.py\", line 40, in invoke_module\n runpy.run_module(mod_name='ansible.modules.network.ios.ios_config', init_globals=None, run_name='main', alter_sys=True)\n File \"/usr/lib64/python2.7/runpy.py\", line 176, in run_module\n fname, loader, pkg_name)\n File \"/usr/lib64/python2.7/runpy.py\", line 82, in _run_module_code\n mod_name, mod_fname, mod_loader, pkg_name)\n

原因

ios_config モジュールの parents オプションで指定したコマンドをよく見たら、router ospf 1 と指定するべきところ route ospf 1 になっていた。

IOS 側の仕様として、グローバルコンフィギュレーションモードで route と指定しても、まだコマンドをユニークに絞りきれない(route-map? router? など )ため、何も実行されなかった 。

いっそのことコマンドが誤っていれば、エラーのなかに % Invalid input detected at '^' marker. という IOS が出力したメッセージが含まれる。しかし、今回は誤っているというより、候補が複数ある状態という中途半端なコマンドだったため、なんともいえないエラーになった。

なお、省略したコマンドでもユニークに絞り込めるレベルであれば設定は可能。しかし、冪等性担保の観点から、省略せずに指定するのが良い

対処

router ospf 1 に修正して再実行


おまけ

実行した Playbook(最終形)

---
- hosts: rt01
  gather_facts: false

  tasks:
    # - name: set route
    #   ios_static_route:
    #     prefix: 0.0.0.0
    #     mask: 0.0.0.0
    #     next_hop: 192.168.1.1

    - name: no shut  Gi0/3
      ios_interfaces:
        config:
          - name: GigabitEthernet0/3
            enabled: True

    - name: set ip address
      ios_l3_interfaces:
        config:
          - name: GigabitEthernet0/3
            ipv4:
              - address: 10.0.0.1/24

    - name: enalbe ospf
      ios_config:
        parents:
          - router ospf 1
        lines:
          - network 10.0.0.0 0.0.0.255 area 0
      tags:
        - ospf

    - name: save
      ios_config:
        save_when: modified
      tags:
        - save

全実行ログ

(ansible) [vagrant@stumble stumble]$ ll
total 20
-rw-rw-r--. 1 vagrant vagrant  34 Jun  5 13:32 ansible.cfg
drwxrwxr-x. 1 vagrant vagrant  96 May 23 01:06 group_vars
-rw-rw-r--. 1 vagrant vagrant  67 May 23 01:06 inventory.ini
-rw-rw-r--. 1 vagrant vagrant 243 Jun  5 13:55 memo.md
-rw-rw-r--. 1 vagrant vagrant 360 Jun  6 01:18 set.yml
(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini set.yml 

PLAY [rt01] *************************************************************************

TASK [Gathering Facts] **************************************************************
[WARNING]: Ignoring timeout(10) for ios_facts
[WARNING]: default value for `gather_subset` will be changed to `min` from `!config`
v2.11 onwards
ok: [rt01]

TASK [no shut  Gi0/3] ***************************************************************
changed: [rt01]

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

(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini set.yml 

PLAY [rt01] *************************************************************************

TASK [no shut  Gi0/3] ***************************************************************
ok: [rt01]

TASK [set ip address] ***************************************************************
changed: [rt01]

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

(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini set.yml  -t ospf

PLAY [rt01] ******************************************************************************************************

TASK [enalbe ospf] ***********************************************************************************************
fatal: [rt01]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "Unsupported parameters for (ios_config) module: parates Supported parameters include: after, auth_pass, authorize, backup, backup_options, before, defaults, diff_against, diff_ignore_lines, host, intended_config, lines, match, multiline_delimiter, parents, password, port, provider, replace, running_config, save_when, src, ssh_keyfile, timeout, username"}

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

(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini set.yml  -t ospf

PLAY [rt01] ***************************************************************************************************

TASK [enalbe ospf] ********************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: rt01(config)#
fatal: [rt01]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "module_stderr": "Traceback (most recent call last):\n  File \"/home/vagrant/.ansible/tmp/ansible-local-28450uu8hydjf/ansible-tmp-1591442691.3088439-28456-252030280888868/AnsiballZ_ios_config.py\", line 102, in <module>\n    _ansiballz_main()\n  File \"/home/vagrant/.ansible/tmp/ansible-local-28450uu8hydjf/ansible-tmp-1591442691.3088439-28456-252030280888868/AnsiballZ_ios_config.py\", line 94, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/home/vagrant/.ansible/tmp/ansible-local-28450uu8hydjf/ansible-tmp-1591442691.3088439-28456-252030280888868/AnsiballZ_ios_config.py\", line 40, in invoke_module\n    runpy.run_module(mod_name='ansible.modules.network.ios.ios_config', init_globals=None, run_name='__main__', alter_sys=True)\n  File \"/usr/lib64/python2.7/runpy.py\", line 176, in run_module\n    fname, loader, pkg_name)\n  File \"/usr/lib64/python2.7/runpy.py\", line 82, in _run_module_code\n    mod_name, mod_fname, mod_loader, pkg_name)\n
(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini set.yml  -t ospf

PLAY [rt01] ***************************************************************************************************

TASK [enalbe ospf] ********************************************************************************************
changed: [rt01]

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

(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini set.yml  -t ospf,save

PLAY [rt01] *********************************************************************************************

TASK [enalbe ospf] **************************************************************************************
ok: [rt01]

TASK [save] [f:id:akira6592:20200606210830p:plain][f:id:akira6592:20200606210830p:plain]*********************************************************************************************
changed: [rt01]

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

(ansible) [vagrant@stumble stumble]$ 

補足

開発中のようですが、ios_ospfv2 モジュールはこちらです。 cisco.ios/ios_ospfv2.py at master · ansible-collections/cisco.ios · GitHub

Ansible 実践ガイド 第3版のサンプル(P282)でも同様のことをしています(宣伝)。

ただし、配信時に handler を使ったコンフィグの保存の説明をしていますが「blockで囲って〜」は誤りです。handler を使う上で、特に block で囲う要はありません。

Part 5 にむけて

次回はまだやることを決められていません。

ansible-galaxy ?

ご意見(ありがとうございます!)

ご参加グログ(ありがとうございます!)

note.com

CML(VIRL2)のエラー「 Failed to start node XX: Unable to define node (Unable to clone image)」の対処

エラー内容

CML でラボ上に機器を追加して起動すると以下のエラーに遭遇しました。

Failed to start node XX: Unable to define node (Unable to clone image)

XXcsr1000v-0 などのノード名です。

f:id:akira6592:20200606124514p:plain
エラーのポップアップ

f:id:akira6592:20200606124547p:plain
エラーのログ

原因

各種機器のイメージISOファイルの読み込みに失敗したためです。

私の場合は、ISOファイルをうっかり削除してしまっていました。

対処

再度ダウンロードサイトから、Cisco Modeling Labs - Personal reference platform .iso file. をダウンロードします。

f:id:akira6592:20200606124937p:plain
ISOのダウンロード

仮想マシンにマウントします。

f:id:akira6592:20200606130747p:plain
ISOファイルのマウント

再度ノードを起動したところ、エラーが解消されて正常に起動しました。

直前に実行したコマンドの最後の引数を取得する方法3つ

はじめに

よく忘れてしまうのですが、直前に実行したコマンドの最後の引数を取得する方法があります。

例えば、

mkdir hoge

したあとに、hogecd したい時に便利です。

最近、tiwtter でいくつか方法があることを知りました。

忘れる自信があるので書き留めておきます

動作確認環境: CentOS 8 / bash

その1: $_

[vagrant@stumble stumble]$ mkdir hoge
[vagrant@stumble stumble]$ cd $_
[vagrant@stumble hoge]$ pwd
/vagrant/stumble/hoge

その2: !$

[vagrant@stumble stumble]$ mkdir hoge2
[vagrant@stumble stumble]$ cd !$
cd hoge2
[vagrant@stumble hoge2]$ pwd
/vagrant/stumble/hoge2

その3: ESC .

[vagrant@stumble stumble]$ mkdir hoge
[vagrant@stumble stumble]$ cd hoge
[vagrant@stumble hoge3]$ pwd
/vagrant/stumble/hoge

この方法だけ、ログだとわかりにくいので動画で補足します。

挙動としてはこちらが一番好みです。

[Ansible] つまずきながら進める Ansible 【Part3】ふりかえり

はじめに

2020/05/30 に、YouTube Live でつまずいきながら進める Ansible 【Part3】という配信をしました。 実際に作業しながらエラーと戦って進めるシリーズです。

tekunabe.connpass.com

前回までは、Ansible のインストールとインベントリファイルの作成、Cisco IOS の機器に show コマンドを実行する Playbook を作りました。

今回は、設定編として、スタティックルートを追加する Playbook を作成しました。

つまずいたエラーと原因、対処をふりかえります。

動画

www.youtube.com


ios_static_route の実行

2回目も changed になってしまった。

1回目の実行で設定が入れば、2回目以降は、ok となるはずのところ、 changed になってしまった。

原因

以下のように mask: 0.0.0.00 と指定していたため

    - name: set route
      ios_static_route:
        prefix: 0.0.0.0
        mask: 0.0.0.00        # ここ
        next_hop: 192.168.1.1

これにより

ip route 0.0.0.0 0.0.0.00 192.168.1.1

が実行される。

しかし、コンフィグ上はあくまでも 0.0.0.0 となる。

ip route 0.0.0.0 0.0.0.0 192.168.1.1

そのため、再度 Playbook を実行し、

ip route 0.0.0.0 0.0.0.00 192.168.1.1

を実行するかどうかの判断の時に、文字列比較の結果「まだ無い設定だ」と判断され、再度実行される。

結局は、コンフィグとしては、

ip route 0.0.0.0 0.0.0.0 192.168.1.1

に落ち着くので、無意味で混乱を招く changed だった。

対処

mask: 0.0.0.0.00mask: 0.0.0.0.0 に修正。

再実行したところ ok になった。

「show running-config」で表示される形式に合わせるのがポイント。

    • 省略しない(shut ではなく shutdonw など)
    • 大文字小文字はあわせる(GigabitEthernet など)
    • スペースを入れすぎない

参考: Ansible Network FAQ — Ansible Documentation

実行されたコマンドがなんなのか気になって仕方がない

モジュールのオプションで指定した値が、実際にどのようなコマンドになって実行されたかが気になる。

対処

ios_static_route モジュールの戻り値commands の中身を確認する。

- hosts: rt01
  
  tasks:
    - name: set route
      ios_static_route:
        prefix: 0.0.0.0
        mask: 0.0.0.0
        next_hop: 192.168.1.1
      register: result   # 結果を変数 result に保存
    
    - name: debug
      debug:
        msg: "{{ result.commands }}"  # 表示

実行結果抜粋

TASK [set route] ***************************************************************************************
changed: [rt01]

TASK [debug] *******************************************************************************************
ok: [rt01] => {
    "msg": [
        "ip route 0.0.0.0 0.0.0.0 192.168.1.1"
    ]
}

ためしたところ、実行され「た」コマンドのよう。すでに設定されていて、実行する必要がなかった場合は空となる。

TASK [debug] *******************************************************************************************
ok: [rt01] => {
    "msg": []
}


Part 4 にむけて

次回の Part 4 では、Playbook のちょっとした改善や、別の設定変更をやってみたいと思います。