Ansible の pyATS 関連 role (ansible-pyats/parse_genie) を試す / ネットワーク自動化

(この記事は Ansible Advent Calendar 2019 17日目として書いています)

Ansible に関することでしたら何でも23

はじめに

今回はほぼ Ansible 初挑戦という状況で、Ansible Advent Calendar に参加します。普段はネットワーク自動化に pyATS/Genie を使っているのですが、Ansible で pyATS/Genie の role( role で呼び方合ってるのかな?) が ansible-galaxy に公開されていたりするので、使ってみたかったのが理由です。

Ansible はあまり詳しくないので、何かもっと良い方法があるよーとかれば教えてください。

なぜ Ansible で pyATS/Genie が必要なのか?

何故 pyATS/Genie が必要なの?という疑問があるかもしれませんが、Genie Parser が最大の理由かと思います。最近の Netconf や REST API では XML/JSON で構造化データが手に入りますが、CLI な機器を自動化したい場合には CLI 出力のパース(解析)が必要となり、Genie Parser はネットワーク機器に対応した豊富な数のパーサーを持っている点と、詳細な構造化データを取得できる為です。

用意されている Genie Parser 一覧はこちらで確認できます。1500 以上のパーサーが用意されています。
https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/parsers

今回使うネットワーク構成

今回は別の記事(pyATS/Genie: プログラミング不要な Genie CLI でログチェックの効率化/高速化/自動化)でも使ったこちらの構成を使います。
IOSXE(R1_xe)、IOS-XR(R2_xr)、NX-OS(R3_nx)を VIRL 上で起動して使います。

全ルータで OSPF が Area0 で設定されており、接続インタフェースとループバック(10.1.1.1/32, 10.2.2.2/32, 10.3.3.3/32)が OSPF 経路として見える状況です。

各機器は VIRL 上の Flat 接続で VIRL ホストと通信でき、VIRL ホストに python 環境を作って Ansible と pyATS/Genie をインストールすることにします。

今回はメインで R1_xe を使います。

Ansible と pyATS/Genie のインストール

それでは早速 Ansible と pyATS/Genie をインストールします。Ansible でネットワーク機器へ接続するのに paramiko が必要になるので、paramiko もインストールしておきます。

$ pyenv virtualenv 3.6.5 ansible_pyats
$ pyenv activate ansible_pyats
(ansible_pyats)$ pip install ansible paramiko pyats[full]

pyats[full] の [] がうまく動かない場合は下記のように ” で囲んで試してみてください。

(ansible_pyats)$ pip install ansible paramiko 'pyats[full]'

Ansible の inventory ファイル作成

まずは Ansible の inventory ファイルを作成する。

[all:vars]
ansible_connection=network_cli

[iosxe]
R1_xe ansible_host=172.16.1.228
R2_xr ansible_host=172.16.1.229
R3_nx ansible_host=172.16.1.230

[iosxe:vars]
ansible_become=yes
ansible_become_method=enable
ansible_network_os=ios
ansible_user=admin
ansible_ssh_pass=Cisc0123
ansible_become_pass=Cisc0123

pyATS/Genie 関連の2つの Role を発見

今回は現時点で確認できる下記2つを試してみます。

clay584/parse_genie

clay584/parse_genie はたぶん初めて Genie Parser を Ansible で使う為に作られたものだと思います。その為、参考例もインターネットで見つけやすい。

ansible-galaxy コマンドでインストールします。

(ansible_pyats)$ ansible-galaxy install clay584.parse_genie

GitHub の README を参考にして、show ip ospf neighbor を R1_xe から取得する playbook を作ってみました。

- hosts: R1_xe
  gather_facts: False
  tasks:
  - name: Read in parse_genie role
    include_role:
      name: clay584.parse_genie

  - name: Get Data From Device
    ios_command:
      commands: show ip ospf neighbor
    register: ospf_output

  - name: Print Structured Data
    debug:
      msg: "{{ ospf_output['stdout'][0] | parse_genie(command='show ip ospf neighbor', os='iosxe') }}"

実行するとこんな感じ。

(ansible_pyats)$ ansible-playbook -i inventory playbook.yml

PLAY [R1_xe] *************************************************************************************************************************************************************************************************

TASK [Read in parse_genie role] ******************************************************************************************************************************************************************************

TASK [Get Data From Device] **********************************************************************************************************************************************************************************
ok: [R1_xe]

TASK [Print Structured Data] *********************************************************************************************************************************************************************************
ok: [R1_xe -> localhost] => {
    "msg": {
        "interfaces": {
            "GigabitEthernet2": {
                "neighbors": {
                    "172.16.12.2": {
                        "address": "172.16.12.2",
                        "dead_time": "00:00:33",
                        "priority": 1,
                        "state": "FULL/DR"
                    }
                }
            }
        }
    }
}

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

ただ、これだとGenie Parser からの出力を表示しているだけで確認などはしていません。なので、簡単なチェックを追加した例が下記です。

- hosts: R1_xe
  gather_facts: False
  tasks:
  - name: Read in parse_genie role
    include_role:
      name: clay584.parse_genie

  - name: Get Data From Device
    ios_command:
      commands: show ip ospf neighbor
    register: ospf_output

  - name: Get Structured Data
    set_fact:
      output: "{{ ospf_output['stdout'][0] | parse_genie(command='show ip ospf neighbor', os='iosxe') }}"

  - name: Print Structured Data
    debug:
      msg: "{{ output }}"

  - name: OSPF Neighbor Check
    assert:
      that:
        - '"172.16.12.2" in output.interfaces.GigabitEthernet2.neighbors'
      success_msg:
        - 'Found OSPF neighbor 172.16.12.2'

構造化データを確認するだけでなく、構造化データに特定の文字列(今回の場合は “172.16.12.2”) が含まれるかどうかをチェックしています。

上記チェック部分では下記記事を参考にさせて頂きました。
AnsibleでNW機器の情報取得してみた(IOSXR)

せっかく Genie Parser が構造化データを返すので、構造化データの階層構想からループをして Neighbor を動的に探したりしたいのですが。。。どうやってやるのか Ansible 勉強中です。

CiscoDevNet/ansible-pyats

次は別の role ansible-pyats を紹介します。先ほどの parse_genie と似ていますが、若干異なる点があるので紹介します。

Contribute to CiscoDevNet/ansible-pyats development by creating an account on GitHub.

こちらは下記のように git clone で取得しました。git clone <url> の後に clone したファイルを保存するパスを記載しています。

(ansible_pyats)$ git clone https://github.com/CiscoDevNet/ansible-pyats "$VIRTUAL_ENV/roles/ansible-pyats"
Cloning into '/home/virl/.pyenv/versions/3.6.5/envs/ansible_pyats/roles/ansible-pyats'...
remote: Enumerating objects: 87, done.
remote: Counting objects: 100% (87/87), done.
remote: Compressing objects: 100% (58/58), done.
remote: Total 87 (delta 30), reused 60 (delta 14), pack-reused 0
Unpacking objects: 100% (87/87), done.
Checking connectivity... done.

先ほどの parse_genie で使った playbook.yml を少し変更しました。

  • ansible_python_interpreter の追加
  • name: Get Structured Data タスクの削除
- hosts: R1_xe
  gather_facts: False
  roles:
    - ansible-pyats
  vars:
    ansible_python_interpreter: /home/virl/.pyenv/shims/python
  tasks:
  - name: Get Data From Device
    pyats_parse_command:
      command: show ip ospf neighbor
    register: output

  - name: Print Structured Data
    debug:
      msg: "{{ output.structured }}"

  - name: OSPF Neighbor Check
    assert:
      that:
        - '"172.16.12.2" in output.structured.interfaces.GigabitEthernet2.neighbors'
      success_msg:
        - 'Found OSPF neighbor 172.16.12.2'

ansible-pyats では python virtualenv を認識して動かないようだったので、varsansible_python_interpreter を追加しました。

また、ansible-pyats では構造化データを直接取得するため、一度 show コマンドの出力を受けて、構造化データに変換していた部分が省略できます。

そのため、同じ事をやる場合には ansible-pyats を使うと少しシンプルになる事が分かります。
(pyats_parserというのもあり、parse_genie と同じように使うこともできます)

上記 playbook2.yml を実行するとこんな感じです。

(ansible_pyats)$ ansible-playbook -i inventory playbook2.yml

PLAY [R1_xe] ***************************************************************************************************************************************************************************************************************************************************************************************************************************

TASK [Get Data From Device] ************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [R1_xe]

TASK [Print Structured Data] ***********************************************************************************************************************************************************************************************************************************************************************************************************
ok: [R1_xe] => {
    "msg": {
        "interfaces": {
            "GigabitEthernet2": {
                "neighbors": {
                    "172.16.12.2": {
                        "address": "172.16.12.2",
                        "dead_time": "00:00:33",
                        "priority": 1,
                        "state": "FULL/DR"
                    }
                }
            }
        }
    }
}

TASK [OSPF Neighbor Check] *************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [R1_xe] => {
    "changed": false,
    "msg": [
        "Found OSPF neighbor 172.16.12.2"
    ]
}

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

ansible-pyats には Genie Parser が返した構造化データを使って差分をチェックする機能(pyats_diff)があります。インタフェースを shutdown して OSPFネイバーをダウンさせて、差分をチェックする playbook3.yml を作ってみました。

- hosts: R1_xe
  gather_facts: False
  roles:
    - ansible-pyats
  vars:
    ansible_python_interpreter: /home/virl/.pyenv/shims/python
  tasks:
  - name: Get Data From Device
    pyats_parse_command:
      command: show ip ospf neighbor
    register: output_before

  - name: Print Structured Data
    debug:
      msg: "{{ output_before.structured }}"

  - name: shutdown interface
    ios_config:
      lines:
        - shutdown
      parents: interface GigabitEthernet2
      save_when: modified

  - name: Get Data From Device After
    pyats_parse_command:
      command: show ip ospf neighbor
    register: output_after

  - name: Print Structured Data After
    debug:
      msg: "{{ output_after.structured }}"

  - name: Diff before and after snapshots
    set_fact:
      diff_output: "{{ output_before.structured | pyats_diff(output_after.structured) }}"

  - name: Show Diff
    debug:
      msg: "{{ diff_output}}"

設定変更には ios_config を使いました。pyats_diff へ事前事後のアウトプット(output_before.structured/output_after.structured)を渡して diff を行い、次のタスクで内容を表示しています。

実行した際の結果が下記になります。

(ansible_pyats)$ ansible-playbook -i inventory playbook3.yml

PLAY [R1_xe] ******************************************************************************************************************************************************************************

TASK [Get Data From Device] ***************************************************************************************************************************************************************
ok: [R1_xe]

TASK [Print Structured Data] **************************************************************************************************************************************************************
ok: [R1_xe] => {
    "msg": {
        "interfaces": {
            "GigabitEthernet2": {
                "neighbors": {
                    "172.16.12.2": {
                        "address": "172.16.12.2",
                        "dead_time": "00:00:38",
                        "priority": 1,
                        "state": "FULL/DR"
                    }
                }
            }
        }
    }
}

TASK [shutdown interface] *****************************************************************************************************************************************************************
changed: [R1_xe]

TASK [Get Data From Device After] *********************************************************************************************************************************************************
ok: [R1_xe]

TASK [Print Structured Data After] ********************************************************************************************************************************************************
ok: [R1_xe] => {
    "msg": {}
}

TASK [Diff before and after snapshots] ****************************************************************************************************************************************************
ok: [R1_xe]

TASK [Show Diff] **************************************************************************************************************************************************************************
ok: [R1_xe] => {
    "msg": "-interfaces: \n- GigabitEthernet2: \n-  neighbors: \n-   172.16.12.2: \n-    address: 172.16.12.2\n-    dead_time: 00:00:38\n-    priority: 1\n-    state: FULL/DR"
}

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

ちゃんと差分として見えていた構造化データが -(マイナス) 表記で消えたことを表しているのが分かります。

ただ、改行コードで改行してくれないので見辛いですね。何かいい方法がないか調べたのですが、うまく見つけれませんでした。。

前述の parse_genie では構造化データを取得するだけだったので、自分で差分をチェックする必要がありますが、ansible-pyats の diff 機能を使うことで差分を簡単に確認できる事が分かりました。

また、今回は紹介しませんでしたが、コンフィグの差分ができる genie_config_diff というのもあるので、興味のある人は試してみてください。

まとめ

  • Ansible でネットワーク自動化を行う場合には pyATS/Genie の便利な role が存在する (parse_genie 及び ansible-pyats)
  • Ansible から pyATS/Genie の持つ豊富なパーサーを使う事ができ、show コマンドの構造化データを取得できる
  • Genie Parser の構造化データを使う事でデバイス状態のチェックが簡単になる
  • ansible-pyats の pyats_diff を使う事でデバイス状態の差分を簡単に取る事が可能

Ansible 初心者の私でも pyATS/Genie の role を使う事で簡単にネットワーク機器の状態を構造化データで取得でき、解析できる事が分かりました。

私のような Ansible初心者なネットワークエンジニアには、vars/register/save_when など、ちょっと playbook で必要となるキーワードを理解するには多少時間が必要かなというのと、構造化データをどのようにチェックするのかという情報がまだ少ないような気がしました。

しかしながら、Ansible + pyATS/Genie はネットワーク自動化に非常に有効な組み合わせだと思います。Ansible を知っている人であれば、pyATS/Genie の Role を使ってネットワーク自動化を便利に進められるのではないかと思いました。

スポンサーリンク