てくなべ (tekunabe)

ansible / network / automation

[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 のちょっとした改善や、別の設定変更をやってみたいと思います。

[Ansible] ios_config モジュールで save_when: modified 指定時に常に changed になる原因

はじめに

Cisco IOS 機器に設定コンフィグを流し込む、ios_config モジュールには、コンフィグの保存(copy running-config startup-config)する条件を指定する、save_whenをいうオプションがあります。

ここで、modified を指定すると「running-config と startup-config に差分があるときだけ copy する」という動作になります。

ところが先日、差分がないであろうタイミングでも毎回 changed になってしまう現象に遭遇しました。always ではないのに。

その原因を調べました。結果としては、よく見たら差分がありました。

  • 動作環境
  • Ansible 2.9.9
  • Cisco IOS XE Software, Version 17.01.01

現象

たとえば、以下のようなタスクを実行したときです。

    - name: save_when test
      ios_config:
        save_when: modified

1回目の実行で、コンフィグ差分があれば changed (保存した)になるのは納得できるのですが、続いて実行したときも毎回 changed (保存した)になりました。

TASK [save_when test] ********************************************************************
changed: [rt01]

※この例で、linessrc オプションの指定がないことからも分かるに、コンフィグを投入しなくても、コンフィグ保存の動作が起こるとすると changed になります。そのため「このモジュールに冪等性がなく、毎回コンフィグを投入してるのではないか」という印象になるかもしれませんがそうではありません。linessrc オプションが指定され場合は、指定されたコンフィグがすでにあるかないかを事前に調べてから投入の有無を決定します。このあたりは注意点があります。

原因

よく見たら、コンフィグ保存直後にも関わらず、running-config と startup-config に差分がありました。

意味合い的には変わらないでしょうが、証明書情報をコンフィグ内に埋め込んでいるか、NVRAM 内のファイルを参照するかの違いでした。

  • running-config
...(略)...
crypto pki certificate chain TP-self-signed-1813660109
 certificate self-signed 01
  30820330 30820218 A0030201 02020101 300D0609 2A864886 F70D0101 05050030 
...(略)...
  • startup-config
...(略)...
crypto pki certificate chain TP-self-signed-1813660109
 certificate self-signed 01 nvram:IOS-Self-Sig#1.cer
...(略)...

コンフィグによっては、他の箇所が原因になることも有り得そうです。

補足

なお、running-configと startup-config は他にも出力が異なる箇所があります。

たとえば、冒頭部分です。

  • running-config
rt01#sh running-config 
Building configuration...

Current configuration : 4356 bytes
!
! Last configuration change at 14:19:27 UTC Thu May 28 2020 by ec2-user
!
version 16.12
...(略)...
  • startup-config
rt01#sh startup-config 
Using 2439 out of 33554432 bytes
!
! Last configuration change at 14:05:56 UTC Thu May 28 2020
!
version 16.12
...(略)...

このあたりは、比較対象外にする文字列として、Ansible 內部で定義されているようです。

github.com

他にも、 ! で始まる行も比較前に除外されます。

たとえば、

Building configuration...

Current configuration : 4344 bytes
!
! Last configuration change at 14:05:56 UTC Thu May 28 2020
!
version 16.12
service timestamps debug datetime msec
...(略)...

version 16.12
service timestamps debug datetime msec
...(略)...

に、予め整形されます。

おわりに

最初は linessrc オプションと併用していたときに毎回 chagned になってしまったため、コンフィグの書き方などに気を取られていました。

今回のようなケースで困る場合は、modified ではなく changed を検討すると良さそうです。

[Ansible/AWX] ジョブテンプレートやワークフロージョブテンプレートを一括削除するワンライナー

はじめに

awx コマンドを利用すると、API を通じて Ansible Tower / AWX 上の 様々な操作ができます。

少し組み合わせて、ジョブテンプレートやワークフロージョブテンプレートを一括削除するワンライナーをご紹介します。

awx コマンドのインストールや接続情報の設定については、以下の記事を参照してください。

tekunabe.hatenablog.jp

また、json をフィルターする関係で jq をインストールしておく必要があります。

  • 動作確認環境
    • AWX 11.0.0
    • awx コマンド 11.2.0

ジョブテンプレートの一括削除

awx job_template listname 一覧を取得して、各 nameawx job_template delete します。

for jt in $(awx job_template list -f jq --filter ".results[] | .name"); do awx job_template delete ${jt}; done

ワークフロージョブテンプレートの一括削除

awx workflow_job_templates listname 一覧を取得して、各 nameawx workflow_job_templates delete します。

for wf in $(awx workflow_job_templates list -f jq --filter ".results[] | .name"); do awx workflow_job_templates delete ${wf}; done 

補足: 一覧を確認したい場合

削除する前に、一覧を確認したい場合は、以下のように実行します。

  • 例1
$ awx job_template list -f jq --filter ".results[] | .name"
jt_01_show
jt_02_debug
jt_03_debug
  • 例2
$ awx job_template list -f human
id name        
== =========== 
28 jt_01_show  
26 jt_02_debug 
27 jt_03_debug 

ワークフロージョブテンプレートの場合は、job_templateworkflow_job_templates に読み替えてください。

おわりに

両方とも、特に確認やログ出力などなく削除するため、十分にご注意ください。

jq のフィルターを工夫すると、特定の条件にマッチしたものだけ削除といった応用もできると思います。