(この記事は Ansible Advent Calendar 2019 17日目として書いています)
目次
はじめに
今回はほぼ 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
と似ていますが、若干異なる点があるので紹介します。
こちらは下記のように 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 を認識して動かないようだったので、vars
に ansible_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 を使ってネットワーク自動化を便利に進められるのではないかと思いました。