てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] user01、user02・・のように数値を任意の桁数にゼロパディングする

はじめに

なにか連番を生成する際に、user1、user2・・user10 ではなく、user01、user02・・のように 特定の桁数でゼロパディングしたいことはないでしょうか。

Jinja2 の filter でフォーマットするのが便利です。

  • 動作確認環境
    • Ansible 2.9.9
    • Jinja2 2.11.2

サンプル1: 簡単な例(単一の値)

10進数を2桁で揃えたいときは %02d です。

- hosts: localhost
  connection: local
  gather_facts: false

  tasks:
    - debug:
        msg: "{{ 'user%02d' | format(1) }}"

実行結果

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

サンプル2: 連番

- hosts: localhost
  connection: local
  gather_facts: false

  tasks:
    - debug:
        msg: "{{ 'user%02d' | format(item) }}"
      loop: "{{ range(1, 10 + 1 , 1)| list }}"

実行結果

TASK [debug] **********************************************************************************
ok: [localhost] => (item=1) => {
    "msg": "user01"
}
ok: [localhost] => (item=2) => {
    "msg": "user02"
}
ok: [localhost] => (item=3) => {
    "msg": "user03"
}
ok: [localhost] => (item=4) => {
    "msg": "user04"
}
ok: [localhost] => (item=5) => {
    "msg": "user05"
}
ok: [localhost] => (item=6) => {
    "msg": "user06"
}
ok: [localhost] => (item=7) => {
    "msg": "user07"
}
ok: [localhost] => (item=8) => {
    "msg": "user08"
}
ok: [localhost] => (item=9) => {
    "msg": "user09"
}
ok: [localhost] => (item=10) => {
    "msg": "user10"
}

CentOS 8.2 で dnf install python38 すると Python 3.8 がインストールできる

はじめに

Red Hat Enterprise Linux 8.2 リリースノートに、yum install python38Python 3.8 がするっとインストールできる旨の記述をみかけました。

access.redhat.com

新しいモジュール python38 が導入されました。

先日、CentOS 8.2 もリリースされたので、CentOS 8.2 で試してみます。

python38 のインストール

環境確認

[root@59e0611b5e49 /]# 
[root@59e0611b5e49 /]# cat /etc/redhat-release 
CentOS Linux release 8.2.2004 (Core) 
[root@59e0611b5e49 /]# 

インストール

[root@59e0611b5e49 /]# dnf install python38 -y
Failed to set locale, defaulting to C.UTF-8
Last metadata expiration check: 0:03:44 ago on Wed Jun 17 13:29:06 2020.
Dependencies resolved.
...(略)...

Installed:
  python38-3.8.0-6.module_el8.2.0+317+61fa6e7d.x86_64                                                                    
  python38-libs-3.8.0-6.module_el8.2.0+317+61fa6e7d.x86_64                                                               
  python38-pip-19.2.3-5.module_el8.2.0+317+61fa6e7d.noarch                                                               
  python38-pip-wheel-19.2.3-5.module_el8.2.0+317+61fa6e7d.noarch                                                         
  python38-setuptools-41.6.0-4.module_el8.2.0+317+61fa6e7d.noarch                                                        
  python38-setuptools-wheel-41.6.0-4.module_el8.2.0+317+61fa6e7d.noarch                                                  

Complete!
[root@59e0611b5e49 /]# 

確認

[root@59e0611b5e49 /]# python3
python3    python3.8  
[root@59e0611b5e49 /]# python3 --version
Python 3.8.0
[root@59e0611b5e49 /]# 
[root@59e0611b5e49 /]# python3.8 --version
Python 3.8.0
[root@59e0611b5e49 /]# 
[root@59e0611b5e49 /]# 

Python 3.8.0 です。

補足1: python3Python 3.6

従来からインストールできたパッケージ python3Python 3.6 です。

[root@59e0611b5e49 /]# dnf install python3 -y
Failed to set locale, defaulting to C.UTF-8
Last metadata expiration check: 0:07:45 ago on Wed Jun 17 13:29:06 2020.
Dependencies resolved.
...(略)...             
Installed:
  platform-python-pip-9.0.3-16.el8.noarch               python3-pip-9.0.3-16.el8.noarch                                  
  python3-setuptools-39.2.0-5.el8.noarch                python36-3.6.8-2.module_el8.1.0+245+c39af44f.x86_64   

Complete!
[root@59e0611b5e49 /]# 
[root@59e0611b5e49 /]# 
[root@59e0611b5e49 /]# python3
python3     python3.6   python3.6m  python3.8   
[root@59e0611b5e49 /]# python3
python3     python3.6   python3.6m  python3.8   
[root@59e0611b5e49 /]# python3 --version
Python 3.6.8
[root@59e0611b5e49 /]# 
[root@59e0611b5e49 /]# python3.6 --version
Python 3.6.8
[root@59e0611b5e49 /]# 

python38python3 の順番でインストールしたら、 python3 コマンドは Python 3.6 になりました。

補足2: CentOS 8.1 でも

CentOS 8.1 でも python38 インストールできました。

[Ansible] Network Resource Module で実際に実行されるコマンドを確認する方法

はじめに

Ansible のネットワークモジュールの中には、コンフィグを直接指定しないタイプのものがあります。

中でも Ansible 2.9 から追加された Network Resource Module の機能は強力です。

とはいえ、ネットワークエンジニアとしては「実際どういうコマンドが実行されたの?」が気になるのではないでしょうか。

この記事では、ios_interfaces モジュールを例に、実行コマンドを確認する方法をご紹介します。

サンプル Playbook

GigabitEthernet1GigabitEthernet2description を設定する Playbook です。

大まかな流れは、最初にチェックモードでタスクを実行し、戻り値内の commands で実行コマンドを表示して、実際に設定変更、です。

---
- hosts: rt01
  gather_facts: false

  vars:   # 設定内容を変数で定義
    config:
      - name: GigabitEthernet1
        description: desc1
        enabled: True
      - name: GigabitEthernet2
        description: desc2
        enabled: True

  tasks:
    - name: 1. check mode   # チェックモードで実行
      ios_interfaces:
        config: "{{ config }}"
      register: result
      check_mode: true      # タスク単位でチェックモードを有効
    
    - name: 2. debug execute commands   # 実行コマンドを確認
      debug:
        msg: "{{ result.commands }}"

    - name: 3. set         # 実際に設定変更
      ios_interfaces:
        config: "{{ config }}"
        state: merged

補足

このサンプルでは、実際に設定変更を行う処理の前に、投入コマンドを表示させる意図で 1. check mode のタスクをチェックモードに指定しています。本来、特にチェックモードにしなくても commands は返ってきますが、その場合は実際に設定変更したあとの事後報告となります。

チェックモード実行後にコマンドを確認して、人による処理の中止や継続の判断をはさみたい場合、2 と 3 の間に、pause モジュールを挟むという方法があります。


実行

事前のネットワーク機器側のコンフィグ(抜粋)は以下の状態とします。

interface GigabitEthernet1
 ip address 192.168.1.11 255.255.255.0
 negotiation auto
!
interface GigabitEthernet2
 description desc2
 ip address 10.0.0.1 255.255.255.0

Playbook で指定した、GigabitEthernet1 には description が設定されておらず、GigabitEthernet2 は Playbook で指定したものが設定されています。

この状態で、Playbook を実行します。

$ ansible-playbook -i inventory.ini ios_config.yml 

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

TASK [1. check mode] ***************************************************************************
changed: [rt01]

TASK [2. debug execute commands] ***************************************************************
ok: [rt01] => {
    "msg": [
        "interface GigabitEthernet1",
        "description desc1"
    ]
}

TASK [3. set] **********************************************************************************
changed: [rt01]

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

2. debug execute commands のタスクで、実行コマンドが表示されたことが分かります。 GigabitEthernet2 には既に意図する description が設定されていたことを Ansible が認識したため、 GigabitEthernet2 への設定コマンドは含まれていません。

なお、事後はこうなります。無事に設定されました。

interface GigabitEthernet1
 description desc1
 ip address 192.168.1.11 255.255.255.0
!
interface GigabitEthernet2
 description desc2
 ip address 10.0.0.1 255.255.255.0

もう一度 Playbook を実行すると、実行するコマンドがないので、msg の表示は以下のようにあります。

ok: [rt01] => {
    "msg": []
}


[nornir] NETCONF で Cisco IOS XE 機器のコンフィグや状態の情報を取得する

はじめに

Python 製自動化フレームワーク nornir は、接続方法として netmikoや、NAPALM の他に、NETCONF にも対応しています。

この記事では NETCONF 経由で、Cisco IOS XE 機器の runnin-config、インターフェース、ルーティングテーブを取得するサンプルをご紹介します。

環境


■ 準備

Python スクリプト本体を作成する前の準備として、以下の2つのファイルを作成します。

config.yml    # 設定ファイル(インベントリの形式、ファイルのパスなどを定義)
inventory/
  hosts.yaml  # インベントリファイル(対象機器の情報を定義)

インベントリ

対象機器の情報を定義するインベントリファイルを作成します。

インベントリに利用できる形式は、Ansible や NetBox などがあるようですが、ここでは一番シンプルなインベントリである Simple を利用します。

  • inventory/hosts.yaml
---
ios01:
  hostname: ios-xe-mgmt-latest.cisco.com
  port: 10000       # netconf 利用時のデフォルトは 830
  username: developer
  password: dummy
  platform: ios     # netconf の場合はなんでも良い模様
  connection_options:
    netconf:
        extras:
            allow_agent: False
            hostkey_verify: False

コンフィグファイル

インベントリの形式やファイルのパスを指定します。

  • config.yaml
---
inventory:
  plugin: nornir.plugins.inventory.simple.SimpleInventory
  options:
      host_file: "inventory/hosts.yaml"  # ホストインベントリファイルのパス


■ サンプル1: running-config の取得と表示

netconf_get_config を利用して、running-confg を取得します。

コード

from nornir import InitNornir
from nornir.plugins.tasks.networking import netconf_get_config
from xml.dom import minidom   # 表示用

nr = InitNornir(config_file="config.yaml")  # 初期化
result = nr.run(task=netconf_get_config, source="running")  # source は running

result_xml = minidom.parseString(result["ios01"].result)
print(result_xml.toprettyxml())

実行結果

$ python get_confg.py 
<?xml version="1.0" ?>
<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>
                <!-- 略 -->
                <hostname>csr1000v-1</hostname>
                <!-- 略 -->
                <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>
                <!-- 略 -->
                </interface>
                <!-- 略 -->
</data>

$ 


■ サンプル2: インターフェース情報の取得と表示

netconf_get を利用して、インターフェース情報を取得します。

コード

from nornir import InitNornir
from nornir.plugins.tasks.networking import netconf_get  # netconf_get_config ではなく
from xml.dom import minidom   # 表示用

nr = InitNornir(config_file="config.yaml")  # 初期化
result = nr.run(task=netconf_get, path="/interfaces-state") # ポイント

result_xml = minidom.parseString(result["ios01"].result)
print(result_xml.toprettyxml())

実行結果

$ python get_if_state.py 
<?xml version="1.0" ?>
<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:32.000772+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.000936+00:00</discontinuity-time>
                                <in-octets>24546855591</in-octets>
                                <in-unicast-pkts>39304700</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>172071875</out-octets>
                                <out-unicast-pkts>532219</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>

$ 

subtree でも指定可能

path は、上記サンプルのような xpath の他にも subtree でも指定できます。

filter_type="subtree" を指定するのがポイントです。デフォルトは xpath です。

# 抜粋
query = """
  <interfaces-state xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
  </interfaces-state>
"""
result = nr.run(task=netconf_get, path=query, filter_type="subtree")


■ サンプル3: ルーティングテーブルの情報の取得と表示

サンプル2と同じく、netconf_get を利用して、ルーティングテーブルの情報を取得します。

違いは path のみです。

コード

from nornir import InitNornir
from nornir.plugins.tasks.networking import netconf_get  # netconf_get_config ではなく
from xml.dom import minidom   # 表示用

nr = InitNornir(config_file="config.yaml")  # 初期化
result = nr.run(task=netconf_get, path="/routing-state")  # サンプル2との違い

result_xml = minidom.parseString(result["ios01"].result)
print(result_xml.toprettyxml())

実行結果

$ python get_rouging.py 
<?xml version="1.0" ?>
<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
        <routing-state xmlns="urn:ietf:params:xml:ns:yang:ietf-routing">
                <routing-instance>
                        <name>default</name>
                        <type>default-routing-instance</type>
                        <router-id>0.0.0.0</router-id>
                        <routing-protocols>
                                <routing-protocol>
                                        <type>direct</type>
                                        <name>0</name>
                                </routing-protocol>
                                <routing-protocol>
                                        <type>static</type>
                                        <name>0</name>
                                </routing-protocol>
                        </routing-protocols>
                        <ribs>
                                <rib>
                                        <name>ipv4-default</name>
                                        <address-family>ipv4</address-family>
                                        <default-rib>false</default-rib>
                                        <routes>
                                                <route>
                                                        <destination-prefix>0.0.0.0/0</destination-prefix>
                                                        <route-preference>1</route-preference>
                                                        <metric>1</metric>
                                                        <next-hop>
                                                                <outgoing-interface>GigabitEthernet1</outgoing-interface>
                                                                <next-hop-address>10.10.20.254</next-hop-address>
                                                        </next-hop>
        <!-- 略 -->
        </routing-state>
</data>

$ 

subtree 指定する場合は以下のようなかたちです。

# 抜粋
query = """
  <routing-state xmlns="urn:ietf:params:xml:ns:yang:ietf-routing">
  </routing-state>
"""
result = nr.run(task=netconf_get, path=query, filter_type="subtree")


おわりに

nornir で NETCONF 経由で情報取得するサンプルをご紹介しました。

今回はざっくりとした情報を取得しました。XML で返ってくるので、必要な情報を抽出しやすいと思います。

もちろアクセスが頻繁にならずに済むのであれば、あらかじめ path や filter で絞っても良いと思います。

参考

www.youtube.com

tekunabe.hatenablog.jp

[Ansible] 「つまずき Ansible 【Part5】Arista EOS に何かしてみる」ふりかえり

はじめに

2020/06/13 に、YouTube Live で「つまずき Ansible 【Part5】Arista EOS に何かしてみる」という配信をしました。 実際に作業しながらエラーと戦って進めるシリーズです。

tekunabe.connpass.com

今回は、Arista EOS の機器(cEOS-lab) への接続確認、show コマンド実行、スタティックルート設定する Playbook を作りました。

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

動画

www.youtube.com


■ 疎通確認

接続できないエラーが発生

以下のエラー。

(ansible) [vagrant@stumble stumble]$ ansible -i inventory.ini eos -m eos_facts
eos1 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "msg": "[Errno None] Unable to connect to port 22 on 192.168.1.133"
}

原因

コンテナで立てた cEOS-lab へは、-p 5022:22 でポートフォワーディングしているの対して、Ansible 側でポート指定していなかった。

対処

以下の変数で、ポートを指定。

ansible_port: 5022


■ スタティックルートの追加

mask オプションがない旨エラーが発生

以下のエラー。

fatal: [eos1]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "Unsupported parameters for (eos_static_route) module: mask Supported parameters include: address, admin_distance, aggregate, auth_pass, authorize, host, next_hop, password, port, provider, ssh_keyfile, state, timeout, transport, use_ssl, username, validate_certs, vrf"}

原因

ios_static_route モジュールと同じ用に prefixmask オプションで指定したが、 eos_static_route モジュールには mask オプションがない。

対処

address オプションプレフィックス表記で指定する。

    - name: set route
      eos_static_route:
        address: 0.0.0.0/0
        next_hop: 192.168.1.1


■ eAPI 経由で show コマンド実行

接続不可のエラーが発生

以下のエラー。

 Could not connect to http://192.168.1.133:80/command-api: [Errno 111] Connection refused\

原因

ansible_connection: httpapi

を指定していたため、デフォルトでは80/TCPで接続しようとするが、環境の都合上別のポート 5080 を指定する必要があった。

さらに、httpapi の場合は、ポートの指定は ansible_port 変数ではなく、ansible_httpapi_port 変数 である。

対処

以下の変数定義を追加。

ansible_httpapi_port: 5080


おまけ

閲覧したサイト

各種ファイル

インベントリ: inventory.ini

[ios]
rt01 ansible_host=192.168.1.11
rt02 ansible_host=192.168.1.12

[eos]
eos1 ansible_host=192.168.1.133

変数定義ファイル: group_vars/eos.yml

---
ansible_network_os: eos

ansible_connection: network_cli
ansible_port: 5022
# ansible_connection: httpapi    # eAPI 利用時はこちら
# ansible_httpapi_port: 5080     # eAPI 利用時はこちら

ansible_user: ansible
ansible_password: p@ssword

# 以下、一般権限を利用する場合は以下の特権設定も必要
ansible_become: true 
ansible_become_method: enable
ansible_become_password: secret 

Playbook: eos_show.yml

---
- hosts: eos
  gather_facts: false
  
  tasks:
    - name: show version
      eos_command: 
        commands:
          - show ver
      register: result
    
    - name: debug
      debug:
        msg: "{{ result }}"

Playbook: eos_set.yml

---
- hosts: eos
  gather_facts: false

  tasks:
    - name: set route
      eos_static_route:
        address: 0.0.0.0/0
        next_hop: 192.168.1.1

    - name: save
      eos_config:
        save_when: modified

全実行ログ

クリックして開く

[vagrant@stumble stumble]$ source ~/envs/ansible/bin/activate
(ansible) [vagrant@stumble stumble]$ 
(ansible) [vagrant@stumble stumble]$ 
(ansible) [vagrant@stumble stumble]$ 
(ansible) [vagrant@stumble stumble]$ ansible -i inventory.ini eos -m eos_facts
eos1 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "msg": "[Errno None] Unable to connect to port 22 on 192.168.1.133"
}
(ansible) [vagrant@stumble stumble]$ ansible -i inventory.ini eos -m eos_facts
[WARNING]: default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards
eos1 | SUCCESS => {
    "ansible_facts": {
        "ansible_net_all_ipv4_addresses": [
            "0.0.0.0"
        ],
        "ansible_net_all_ipv6_addresses": [],
        "ansible_net_api": "cliconf",
        "ansible_net_filesystems": [
            "file:",
            "flash:",
            "system:"
        ],
        "ansible_net_fqdn": "ceos1",
        "ansible_net_gather_network_resources": [],
        "ansible_net_gather_subset": [
            "hardware",
            "default",
            "interfaces"
        ],
        "ansible_net_hostname": "ceos1",
        "ansible_net_interfaces": {
            "Ethernet1": {
                "bandwidth": 0,
                "description": "",
                "duplex": "duplexFull",
                "ipv4": {},
                "lineprotocol": "up",
                "macaddress": "02:42:ac:15:00:02",
                "mtu": 9214,
                "operstatus": "connected",
                "type": "bridged"
            },
            "Ethernet2": {
                "bandwidth": 0,
                "description": "",
                "duplex": "duplexFull",
                "ipv4": {
                    "address": "0.0.0.0",
                    "masklen": 0
                },
                "lineprotocol": "up",
                "macaddress": "02:42:ac:2a:8f:7d",
                "mtu": 1500,
                "operstatus": "connected",
                "type": "routed"
            },
            "Ethernet3": {
                "bandwidth": 0,
                "description": "",
                "duplex": "duplexFull",
                "ipv4": {},
                "lineprotocol": "up",
                "macaddress": "02:42:ac:17:00:02",
                "mtu": 9214,
                "operstatus": "connected",
                "type": "bridged"
            },
            "Ethernet4": {
                "bandwidth": 0,
                "description": "",
                "duplex": "duplexFull",
                "ipv4": {},
                "lineprotocol": "up",
                "macaddress": "02:42:ac:18:00:02",
                "mtu": 9214,
                "operstatus": "connected",
                "type": "bridged"
            }
        },
        "ansible_net_memfree_mb": 1234.15625,
        "ansible_net_memtotal_mb": 1991.46875,
        "ansible_net_model": "cEOSLab",
        "ansible_net_neighbors": {},
        "ansible_net_python_version": "2.7.5",
        "ansible_net_serialnum": "",
        "ansible_net_system": "eos",
        "ansible_net_version": "4.21.10M",
        "ansible_network_resources": {},
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}
(ansible) [vagrant@stumble stumble]$ 
(ansible) [vagrant@stumble stumble]$ 
(ansible) [vagrant@stumble stumble]$ 
(ansible) [vagrant@stumble stumble]$ 
(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini eos_show.yml 

PLAY [eos] *****************************************************************************************

TASK [show version] ********************************************************************************
ok: [eos1]

TASK [debug] ***************************************************************************************
ok: [eos1] => {
    "msg": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "failed": false,
        "stdout": [
            "cEOSLab\nHardware version:    \nSerial number:       \nSystem MAC address:  0242.ac2a.8f7d\n\nSoftware image version: 4.21.10M\nArchitecture:           i386\nInternal build version: 4.21.10M-15347597.42110M\nInternal build ID:      9d960dea-a6da-424b-b373-2958c07c48c3\n\ncEOS tools version: 1.1\n\nUptime:                 0 weeks, 0 days, 3 hours and 40 minutes\nTotal memory:           2039264 kB\nFree memory:            1264168 kB"
        ],
        "stdout_lines": [
            [
                "cEOSLab",
                "Hardware version:    ",
                "Serial number:       ",
                "System MAC address:  0242.ac2a.8f7d",
                "",
                "Software image version: 4.21.10M",
                "Architecture:           i386",
                "Internal build version: 4.21.10M-15347597.42110M",
                "Internal build ID:      9d960dea-a6da-424b-b373-2958c07c48c3",
                "",
                "cEOS tools version: 1.1",
                "",
                "Uptime:                 0 weeks, 0 days, 3 hours and 40 minutes",
                "Total memory:           2039264 kB",
                "Free memory:            1264168 kB"
            ]
        ]
    }
}

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

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

PLAY [eos] ***************************************************************************************************

TASK [set route] *********************************************************************************************
fatal: [eos1]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "Unsupported parameters for (eos_static_route) module: mask Supported parameters include: address, admin_distance, aggregate, auth_pass, authorize, host, next_hop, password, port, provider, ssh_keyfile, state, timeout, transport, use_ssl, username, validate_certs, vrf"}

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

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

PLAY [eos] **********************************************************************************

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

TASK [save] *********************************************************************************
changed: [eos1]

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

(ansible) [vagrant@stumble stumble]$ ansible -i inventory.ini eos -m eos_eapi -a http=true
eos1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python",
        "eos_eapi_urls": {}
    },
    "changed": true,
    "commands": [
        "management api http-commands",
        "protocol http port 80",
        "no shutdown"
    ],
    "session_name": "ansible_1592047615"
}
(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini eos_show.yml 

PLAY [eos] **************************************************************************************************

TASK [show version] *****************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.module_utils.connection.ConnectionError: Could not connect to http://192.168.1.133:80/command-api: [Errno 111] Connection refused
fatal: [eos1]: 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-401092_8yu3k/ansible-tmp-1592047746.7101004-4016-36587511630429/AnsiballZ_eos_command.py\", line 102, in <module>\n    _ansiballz_main()\n  File \"/home/vagrant/.ansible/tmp/ansible-local-401092_8yu3k/ansible-tmp-1592047746.7101004-4016-36587511630429/AnsiballZ_eos_command.py\", line 94, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/home/vagrant/.ansible/tmp/ansible-local-401092_8yu3k/ansible-tmp-1592047746.7101004-4016-36587511630429/AnsiballZ_eos_command.py\", line 40, in invoke_module\n    runpy.run_module(mod_name='ansible.modules.network.eos.eos_command', 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  File \"/usr/lib64/python2.7/runpy.py\", line 72, in _run_code\n    exec code in run_globals\n  File \"/tmp/ansible_eos_command_payload_DavWF4/ansible_eos_command_payload.zip/ansible/modules/network/eos/eos_command.py\", line 248, in <module>\n  File \"/tmp/ansible_eos_command_payload_DavWF4/ansible_eos_command_payload.zip/ansible/modules/network/eos/eos_command.py\", line 219, in main\n  File \"/tmp/ansible_eos_command_payload_DavWF4/ansible_eos_command_payload.zip/ansible/module_utils/network/eos/eos.py\", line 637, in run_commands\n  File \"/tmp/ansible_eos_command_payload_DavWF4/ansible_eos_command_payload.zip/ansible/module_utils/network/eos/eos.py\", line 107, in get_connection\n  File \"/tmp/ansible_eos_command_payload_DavWF4/ansible_eos_command_payload.zip/ansible/module_utils/connection.py\", line 185, in __rpc__\nansible.module_utils.connection.ConnectionError: Could not connect to http://192.168.1.133:80/command-api: [Errno 111] Connection refused\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

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

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

PLAY [eos] **************************************************************************************************

TASK [show version] *****************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.module_utils.connection.ConnectionError: Could not connect to http://192.168.1.133:80/command-api: [Errno 111] Connection refused
fatal: [eos1]: 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-40497utj2p8g/ansible-tmp-1592047800.735867-4055-74241524366622/AnsiballZ_eos_command.py\", line 102, in <module>\n    _ansiballz_main()\n  File \"/home/vagrant/.ansible/tmp/ansible-local-40497utj2p8g/ansible-tmp-1592047800.735867-4055-74241524366622/AnsiballZ_eos_command.py\", line 94, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/home/vagrant/.ansible/tmp/ansible-local-40497utj2p8g/ansible-tmp-1592047800.735867-4055-74241524366622/AnsiballZ_eos_command.py\", line 40, in invoke_module\n    runpy.run_module(mod_name='ansible.modules.network.eos.eos_command', 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  File \"/usr/lib64/python2.7/runpy.py\", line 72, in _run_code\n    exec code in run_globals\n  File \"/tmp/ansible_eos_command_payload_EHXx7W/ansible_eos_command_payload.zip/ansible/modules/network/eos/eos_command.py\", line 248, in <module>\n  File \"/tmp/ansible_eos_command_payload_EHXx7W/ansible_eos_command_payload.zip/ansible/modules/network/eos/eos_command.py\", line 219, in main\n  File \"/tmp/ansible_eos_command_payload_EHXx7W/ansible_eos_command_payload.zip/ansible/module_utils/network/eos/eos.py\", line 637, in run_commands\n  File \"/tmp/ansible_eos_command_payload_EHXx7W/ansible_eos_command_payload.zip/ansible/module_utils/network/eos/eos.py\", line 107, in get_connection\n  File \"/tmp/ansible_eos_command_payload_EHXx7W/ansible_eos_command_payload.zip/ansible/module_utils/connection.py\", line 185, in __rpc__\nansible.module_utils.connection.ConnectionError: Could not connect to http://192.168.1.133:80/command-api: [Errno 111] Connection refused\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

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

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

PLAY [eos] **************************************************************************************************

TASK [show version] *****************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.module_utils.connection.ConnectionError: Could not connect to http://192.168.1.133:80/command-api: [Errno 111] Connection refused
fatal: [eos1]: 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-4088qmoqg6k4/ansible-tmp-1592047844.611491-4094-263218421081738/AnsiballZ_eos_command.py\", line 102, in <module>\n    _ansiballz_main()\n  File \"/home/vagrant/.ansible/tmp/ansible-local-4088qmoqg6k4/ansible-tmp-1592047844.611491-4094-263218421081738/AnsiballZ_eos_command.py\", line 94, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/home/vagrant/.ansible/tmp/ansible-local-4088qmoqg6k4/an
(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini eos_show.yml 

PLAY [eos] **************************************************************************************************

TASK [show version] *****************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.module_utils.connection.ConnectionError: Could not connect to http://192.168.1.133:80/command-api: [Errno 111] Connection refused
fatal: [eos1]: 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-41274_remwl3/ansible-tmp-1592047863.403169-4133-45467802027754/AnsiballZ_eos_command.py\", line 102, in <module>\n    _ansiballz_main()\n  File \"/home/vagrant/.ansible/tmp/ansible-local-41274_remwl3/ansible-tmp-1592047863.403169-4133-45467802027754/AnsiballZ_eos_command.py\", line 94, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/home/vagrant/.ansible/tmp/ansible-local-41274_remwl3/ansi
(ansible) [vagrant@stumble stumble]$ ansible-playbook -i inventory.ini eos_show.yml 

PLAY [eos] **************************************************************************************************

TASK [show version] *****************************************************************************************
ok: [eos1]

TASK [debug] ************************************************************************************************
ok: [eos1] => {
    "msg": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "failed": false,
        "stdout": [
            "cEOSLab\nHardware version:    \nSerial number:       \nSystem MAC address:  0242.ac2a.8f7d\n\nSoftware image version: 4.21.10M\nArchitecture:           i386\nInternal build version: 4.21.10M-15347597.42110M\nInternal build ID:      9d960dea-a6da-424b-b373-2958c07c48c3\n\ncEOS tools version: 1.1\n\nUptime:                 0 weeks, 0 days, 3 hours and 56 minutes\nTotal memory:           2039264 kB\nFree memory:            1254684 kB"
        ],
        "stdout_lines": [
            [
                "cEOSLab",
                "Hardware version:    ",
                "Serial number:       ",
                "System MAC address:  0242.ac2a.8f7d",
                "",
                "Software image version: 4.21.10M",
                "Architecture:           i386",
                "Internal build version: 4.21.10M-15347597.42110M",
                "Internal build ID:      9d960dea-a6da-424b-b373-2958c07c48c3",
                "",
                "cEOS tools version: 1.1",
                "",
                "Uptime:                 0 weeks, 0 days, 3 hours and 56 minutes",
                "Total memory:           2039264 kB",
                "Free memory:            1254684 kB"
            ]
        ]
    }
}

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

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

PLAY [eos] **************************************************************************************************

TASK [show version] *****************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.module_utils.connection.ConnectionError: incomplete token (at token 1: 'ver')
fatal: [eos1]: 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-4211h2y17s33/ansible-tmp-1592047950.0063741-4217-264763540732757/AnsiballZ_eos_command.py\", line 102, in <module>\n    _ansiballz_main()\n  File \"/home/vagrant/.ansible/tmp/ansible-local-4211h2y17s33/ansible-tmp-1592047950.0063741-4217-264763540732757/AnsiballZ_eos_command.py\", line 94, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/home/vagrant/.ansible/tmp/ansible-local-4211h2y17s33/ansible-tmp-1592047950.0063741-4217-264763540732757/AnsiballZ_eos_command.py\", line 40, in invoke_module\n    runpy.run_module(mod_name='ansible.modules.network.eos.eos_command', 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  File \"/usr/lib64/python2.7/runpy.py\", line 72, in _run_code\n    exec code in run_globals\n  File \"/tmp/ansible_eos_command_payload_syvs3j/ansible_eos_command_payload.zip/ansible/modules/network/eos/eos_command.py\", line 248, in <module>\n  File \"/tmp/ansible_eos_command_payload_syvs3j/ansible_eos_command_payload.zip/ansible/modules/network/eos/eos_command.py\", line 219, in main\n  File \"/tmp/ansible_eos_command_payload_syvs3j/ansible_eos_command_payload.zip/ansible/module_utils/network/eos/eos.py\", line 638, in run_commands\n  File \"/tmp/ansible_eos_command_payload_syvs3j/ansible_eos_command_payload.zip/ansible/module_utils/network/eos/eos.py\", line 483, in run_commands\n  File \"/tmp/ansible_eos_command_payload_syvs3j/ansible_eos_command_payload.zip/ansible/module_utils/network/eos/eos.py\", line 451, in run_queue\n  File \"/tmp/ansible_eos_command_payload_syvs3j/ansible_eos_command_payload.zip/ansible/module_utils/connection.py\", line 185, in __rpc__\nansible.module_utils.connection.ConnectionError: incomplete token (at token 1: 'ver')\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

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

(ansible) [vagrant@stumble stumble]$ 

Part6 にむけて

もう少しネットワーク方面をやってみたいと思います。

[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 ブランチのドキュメントで、最近 _ で始まる変数名も有効であることが明記されました。

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