てくなべ (tekunabe)

ansible / network / automation

[Ansible] 「つまずき Ansible 【Part17】ansible 2.10 まわり」ふりかえり

はじめに

2020/09/26 に、YouTube Live で「つまずき Ansible 【Part17】Ansible 2.10 まわり」という配信をしました。

実際に作業しながらエラーと戦って進めるシリーズです。

tekunabe.connpass.com

今回は、2020/09/22 にリリースされたの Asnible 2.10.0(not ansible-base)をとりまく仕組み(collectionなど)について復習しました。

やったことや、わかったことをふりかえります。

  • 環境
    • ansible-base 2.10.1
    • ansible 2.10.0

動画

youtu.be


■ やったこと

インストール

比較のため、ansible-base 2.10.1 (標準モジュールのみ)と、ansible 2.10.0 を別 vevn にそれぞれインストール。

ansible-base インストール

# pip install ansible-base
...(略)...
# ansible --version
ansible 2.10.1
...(略)...
# pip freeze
ansible-base==2.10.1    # ansible-base
cffi==1.14.3
cryptography==3.1.1
Jinja2==2.11.2
MarkupSafe==1.1.1
packaging==20.4
pycparser==2.20
pyparsing==2.4.7
PyYAML==5.3.1
six==1.15.0

ansible インストール

$ pip install ansible-base
...(略)...
# ansible --version
ansible 2.10.1
...(略)...
# pip freeze
ansible==2.10.0         # ansible-base とは別パッケージ扱い
ansible-base==2.10.1    # ansible-base
bcrypt==3.2.0
cffi==1.14.3
cryptography==3.1.1
Jinja2==2.11.2
MarkupSafe==1.1.1
packaging==20.4
paramiko==2.7.2
pycparser==2.20
PyNaCl==1.4.0
pyparsing==2.4.7
PyYAML==5.3.1
six==1.15.0

アップデート

ansible 2.9 から 2.10 へのアップーデートは直接できない。

# pip install ansible -U
Collecting ansible
  Using cached https://files.pythonhosted.org/packages/4a/0b/44b586965bd51135d3915a02d1327fb392843630435cd41d6c89898c5f24/ansible-2.10.0.tar.gz
    Complete output from command python setup.py egg_info:
    
    
                ### ERROR ###
    
                Upgrading directly from ansible-2.9 or less to ansible-2.10 or greater with pip is
                known to cause problems.  Please uninstall the old version found at:
    
                /root/envs/a29/lib64/python3.6/site-packages/ansible/__init__.py
    
                and install the new version:
    
                    pip uninstall ansible
                    pip install ansible
    
                If you have a broken installation, perhaps because ansible-base was installed before
                ansible was upgraded, try this to resolve it:
    
                    pip install --force-reinstall ansible ansible-base
    
                If ansible is installed in a different location than you will be installing it now
                (for example, if the old version is installed by a system package manager to
                /usr/lib/python3.8/site-packages/ansible but you are installing the new version into
                ~/.local/lib/python3.8/site-packages/ansible with `pip install --user ansible`)
                or you want to install anyways and cleanup any breakage afterwards, then you may set
                the ANSIBLE_SKIP_CONFLICT_CHECK environment variable to ignore this check:
    
                    ANSIBLE_SKIP_CONFLICT_CHECK=1 pip install --user ansible
    
                ### END ERROR ###
    
    
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-p3lwq3xc/ansible/

一度アンストールしてから、再インストールする。

# pip uninstall ansible
# pip install ansible

参考: ログイン - Google アカウント

collection の操作

インストール

# ansible-galaxy collection install cisco.ios

確認

# ansible-galaxy collection list
(ログとり忘れ・・)

デフォルトでは上記のログにもあるように、~/.ansible/collections/ansible_collections/ 配下にインストールされる。(-p オプションや、COLLECTIONS_PATHS で変更可能)

pip install ansible でインストールした場合は lib/python3.6/site-packages/ansible_collections/ のように、別の Python パッケージような扱いで配置される。(COLLECTIONS_SCAN_SYS_PATHTrue ならここも探す)

collection 関連のデフォルト設定

# ansible-config dump | grep -i collection
COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH(default) = warning
COLLECTIONS_PATHS(default) = ['/root/.ansible/collections', '/usr/share/ansible/collections']
COLLECTIONS_SCAN_SYS_PATH(default) = True

先に、COLLECTIONS_PATHS を探して、なければ COLLECTIONS_SCAN_SYS_PATH に従う模様。

2.9 で書いた Playbook が動くか?

以下の Playbook をおためし。

---
- hosts: ios
  gather_facts: false
  
  tasks:
    - name: show ip route
      cisco.ios.ios_command:      # ayashii
        commands:
          - show ip route
      register: resgister_show_ip_route

    - name: debug route
      debug:            # daijoubu
        msg: "{{ resgister_show_ip_route.stdout_lines }}"

ansible-base (collectionなし)

個別に collection をインストールしていない状態で実行。collection に移行した、ios_command がないというエラーに。

# ansible-playbook -i inventory.ini ios_show.yml 
ERROR! couldn't resolve module/action 'cisco.ios.ios_command'. This often indicates a misspelling, missing collection, or incorrect module path.

The error appears to be in '/root/general/vagrant/nwlab/stumble/ios_show.yml': line 6, column 7, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

  tasks:
    - name: show ip route
      ^ here

ansible (collection)

pip install ansible して、自動でざまざまなな collection (cisco.ios含む)をインストールした環境でおためし。正常完了。

# ansible-playbook -i inventory.ini ios_show.yml 

PLAY [ios] ********************************************************************************

TASK [show ip route] **********************************************************************
ok: [rt01]
ok: [rt02]

TASK [debug route] ************************************************************************
ok: [rt01] => 
  msg:
  - - 'Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP'
    - '       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area '
    - '       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2'
    - '       E1 - OSPF external type 1, E2 - OSPF external type 2'
    - '       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2'
    - '       ia - IS-IS inter area, * - candidate default, U - per-user static route'
    - '       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP'
    - '       a - application route'
    - '       + - replicated route, % - next hop override, p - overrides from PfR'
    - ''
    - Gateway of last resort is not set
...(略)...

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

なぜ動いたか?

前述のように、 pip install ansible でインストールすると、様々な collection (cisco.ios含む)がインストールされるため。

かつ、ios_command のような、装飾なしのモジュールの指定でも FQCN へ紐付けるリダイレクトデータがあるため。

ただ、これまでのバージョンアップ同様、ぎりぎり使えたオプションが使えなくなったり挙動が変わったり警告が表示されるようになったりする可能性はある。


ドキュメントはどうなった?

Module Index が Collection Index に

Collection Index — Ansible Documentation

標準のモジュール類は ansible.builtin を参照

カテゴリごとではなく、collection ごとに。標準のモジュール類は ansible.builtin

Plugin Index — Ansible Documentation

モジュールの説明ページの旧リンクはリダイレクトされる

たとえば https://docs.ansible.com/ansible/latest/modules/ios_command_module.htmlhttps://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_command_module.html へリダイレクトされる。

URL に collection 名が入るのが特徴。

事前にいたご質問

質問1

質問2

何が同梱されて、何が同梱されないのかについて (ざっくり) 知りたいです! (debugはさすがに同梱されると思いますが、ipaddrフィルターとかどうなんだろう)

debugモジュール は標準同梱、ipaddr フィルターは ansible.netcommon collection 配下です。

すでに、ipaddr フィルターの説明ページでも、ansible.netcommon.ipaddr といった表記になっています。

pip install ansible でインストールした環境の場合、ansible-doc -l で表示したときに、装飾なしのモジュール名で表示されるようです。

# ansible-doc -l 
...(略)...
copy                                                                           Copy files to remote locations               
cron                                                                           Manage cron.d and crontab entries            
cyberark.pas.cyberark_account                                                  Module for CyberArk Account object creation, ...
cyberark.pas.cyberark_authentication                                           CyberArk Authentication using PAS Web Service...
cyberark.pas.cyberark_credential                                               Credential retrieval using AAM Central Creden...
cyberark.pas.cyberark_user                                                     CyberArk User Management using PAS Web Servic...
debconf                                                                        Configure a .deb package                     
debug                                                                          Print statements during execution            
dellemc.os10.base_xml_to_dict                                                  Operations for show command output ...(略)...

この方法いいですね!


Part17 にむけて

以下のネタを検討中です。気が向いたものをやります。

  • Ansible 2.10 関連ほかにも
  • connection: local ななにか
  • Ansible Toewr / AWX をコマンドがら操作する
  • ansible.cfg
  • Jinja2、フィルター
  • Windows
  • ESXi で VM作成
  • parsee_cli モジュール(Part15 の続き)
  • when や assert

[Ansible] 内包表記のようにリストの各要素に処理して別の要素を生成する

はじめに

Ansible で、リストの中の各要素に対して一律でなにかの処理をして、別の要素を生成したいときがあります、

Python の内包表記でリストを生成するイメージです。

全く同じというわけではないのですが、map フィルターを利用すると近いことが少しできることを知りました。

少し説明しにくいので、 Python で書く場合と比較していくつかサンプルをご紹介します。

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


各要素に文字列を結合する

Python

Python だとこのイメージ。

>>> ['Hello ' + i for i in ['funa', 'kingyo', 'same']]
['Hello funa', 'Hello kingyo', 'Hello same']

Ansible

Ansible だとこのような感じです。

もっとシンプルにする方法があると良いのですが、正規表現を使う方法しか思いつきませんでした。

    - name: naihou
      debug: 
        msg: "{{ ['funa', 'kingyo', 'same'] | map('regex_replace', '^(.+)$', 'Hello \\1') | list }}"
  • 結果
ok: [localhost] => {
    "msg": [
        "Hello funa",
        "Hello kingyo",
        "Hello same"
    ]
}

なお、map フィルターに regex_replace を渡せることは以下の記事で知りました。ありがとうございます。 Jinja2のmapフィルタの中身に引数が必要なフィルタを指定する - Qiita


各要素を大文字にする

Python

Python だとこのイメージ。

>>> [i.upper() for i in ['funa', 'kingyo', 'same']]
['FUNA', 'KINGYO', 'SAME']

Ansible

Ansible だとこのような感じです。upper フィルターを併用します。

    - name: naihou
      debug: 
        msg: "{{ ['funa', 'kingyo', 'same'] | map('upper') | list }}"
  • 結果
ok: [localhost] => {
    "msg": [
        "FUNA",
        "KINGYO",
        "SAME"
    ]
}


各要素を int にキャストする

Python

Python だとこのイメージ。

>>> [int(i) for i in ['111', '222', '333']]
[111, 222, 333]

Ansible

Ansible だとこのような感じです。

    - name: naihou
      debug: 
        msg: "{{ ['111', '222', '333'] | map('int') | list }}"
  • 結果
ok: [localhost] => {
    "msg": [
        111,
        222,
        333
    ]
}


おわりに

リスト loop で回しながら、処理して set_fact する方法でもできますが(こちらのほうが柔軟)、一つのタスクするまでもないときには、このように内包表記チックにやるのもいいと思います。

なお、Jinja2 として、内包表記そのものはサポートしていないとドキュメントに明記されています。

[Ansible] 複数の assert を一通り実施したあとで全結果を再 assert する

はじめに

Ansible には、値が期待したした条件を満たすかどうかをチェック assert モジュールがあります(標準モジュール)。

基本的には、assert 結果が fail だとその時点で Playbook の処理が中止されます。

一方で、1つ fail しただけで止めるのではなく、いくつかの assert を一通り実行しそれらの結果がすべて success なら success としたい場合もあるのではないでしょうか。

ignore_errors を併用すると実現できます。 この記事では簡単なサンプルでご紹介します。

(もっといい方法があるかも知れません)

  • 動作確認環境
    • ansible-base 2.10.1

サンプル Playbook

2つの 個別の assert があります。fail しても中止しないように、ignore_errors: true を指定します。(タスクごとに指定するのが面倒の場合は block で囲って指定) また、あとで、assert 結果を参照できるように、個別の assert には register で結果を受け取るようにします。

ここでは 1つめの assert が fail するようにしています。

---
- hosts: all
  gather_facts: false

  tasks:
    - name: assert 1   # 個別 assert
      assert:
        that: 1 == 0     # fail する
      ignore_errors: true
      register: res_assert1

    - name: assert 2  # 個別 assert
      assert:
        that: 2 == 2
      ignore_errors: true
      register: res_assert2

    - name: all assert
      assert:
        that:   # 複数指定で and 条件
          - not res_assert1.failed  # fail
          - not res_assert2.failed  # success

    - name: Finished
      debug:
        msg: Finished!


実行1(failパターン)

まずは、1つめの assert が fail するパターンです。

実行

$ ansible-playbook -i localhost, assert.yml

PLAY [all] **********************************************************************************************************

TASK [assert 1] *****************************************************************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "1 == 0",
    "changed": false,
    "evaluated_to": false,
    "msg": "Assertion failed"
}
...ignoring

TASK [assert 2] *****************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [all assert] ***************************************************************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "not res_assert1.failed",
    "changed": false,
    "evaluated_to": false,
    "msg": "Assertion failed"
}

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

中断せずに 2つめに assert しているのが分かります。また、2つの結果をまとめて assert するタスク「all assert」では fail していることが分かります。


実行2 (successパターン)

1つめの assert の条件を以下のようにして、success になるようにして再実行します。

   - name: assert 1   # 個別 assert
      assert:
        that: 1 == 1     # ★修正
      ignore_errors: true
      register: res_assert1

実行

$ ansible-playbook -i localhost, assert.yml

PLAY [all] **********************************************************************************************************

TASK [assert 1] *****************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [assert 2] *****************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [all assert] ***************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Finished] *****************************************************************************************************
ok: [localhost] => {
    "msg": "Finished!"
}

PLAY RECAP **********************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

個別の assert もまとめの assert も success となり、最後の debug タスクも実行されました。


応用

再 asser ではなく、結果を一覧で 表示する場合は、タスク all assert の代わりに以下のようにします。

    - name: assert results
      debug:
        msg:
          - "{{ res_assert1 }}"
          - "{{ res_assert2 }}"

実行結果(抜粋)

TASK [assert results] ***************************************************************************************************
ok: [localhost] => {
    "msg": [
        {
            "assertion": "1 == 0",
            "changed": false,
            "evaluated_to": false,
            "failed": true,
            "msg": "Assertion failed"
        },
        {
            "changed": false,
            "failed": false,
            "msg": "All assertions passed"
        }
    ]
}


別解(loop の活用)

今回のように、比較する値が予めすべて取り揃っている状態から assert する場合は、loop で回すという方法もあります。 loop で assert すると、一通り assert しつつも、1つでも fail になるとそのタスクが fail となります。

---
- hosts: all
  gather_facts: false

  tasks:
    - name: assert
      assert:
        that: "{{ item }}" 
      loop:
        - 1 == 0    # fail
        - 2 == 2    # success

    - name: Finished
      debug:
        msg: Finished!

実行

$ ansible-playbook -i localhost, assert_loop.yml

PLAY [all] **********************************************************************************************************

TASK [assert] *******************************************************************************************************
failed: [localhost] (item=1 == 0) => {
    "ansible_loop_var": "item",
    "assertion": "1 == 0",
    "changed": false,
    "evaluated_to": false,
    "item": "1 == 0",
    "msg": "Assertion failed"
}
ok: [localhost] => (item=2 == 2) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": "2 == 2",
    "msg": "All assertions passed"
}

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

[Ansible] ansible 2.10.0 で ini のインベントリファイルのansible_becomeのbool値解釈が修正された

はじめに

ini ファイル形式のインベントリファイルで指定された ansible_become 変数の値の解釈が ansible 2.10.0 で修正されました。

2.9 系との動作を比較して検証します。

前提となる ini 形式のインベントリファイル

[awx]
awx1

[awx:vars]
ansible_host=10.0.0.145
ansible_user=admin
ansible_become=hogehoge

become が true 扱いなら、リモートでも実行ユーザーが、root ユーザー、 false なら admin ユーザーとなります。

Ansible 2.9 系

whoami を実行する簡単なアドホックコマンドを実行します。

$ ansible --version
ansible 2.9.9
...(略)...
$ ansible -i inventory.ini awx1 -m command -a whoami
awx1 | CHANGED | rc=0 >>
admin

正常に完了し、admin と表示されました。つまり ansible_become=hogehogehogehogefalse 扱いです。

Ansible 2.10.1

先程同じアドホックコマンドを実行します。

$ ansible --version
ansible 2.10.1
...(略)...
$ ansible -i inventory.ini awx1 -m command -a whoami
awx1 | FAILED | rc=-1 >>
the field 'become' has an invalid value (hogehoge), and could not be converted to an bool.The error was: The value 'hogehoge' is not a valid boolean.  Valid booleans include: 0, 1, 'off', 'n', '1', 'y', 'true', '0', 'yes', 'no', 'f', 'on', 't', 'false'

hogehoge という値は正しい boolean 値でないというエラーになりました。有効な値は以下の通りとメッセージがあります。

0, 1, 'off', 'n', '1', 'y', 'true', '0', 'yes', 'no', 'f', 'on', 't', 'false'

試してみると、引き続き TrueFalse も(大文字はじまり)正常な boolean として扱われました。

[Ansible] ansible 2.10.0 が ansible-base 2.10.1 ベースである経緯を探る

はじめに

まもなく ansible 2.10.0 がリリースされる予定です。

2.10 から collection 前提の運用が始まり、ansible を取り巻くパッケージは 2つになりました。

  1. 標準モジュールのみのパッケージを ansible-base
  2. ansible-base に加えて各種コレクションをセットでインストールする ansible(以前は ACD(Ansible Community Distribution)とよばれていたように思いました)

ansible は どのバージョンの ansible-baseベースするかが定義がされています。

例えば ansible 2.10.0 の場合は、ansible-base 2.10.1 です。直感に反して末尾のバージョンが異なります。こうなった経緯を自分なりに調べてみましたのでまとめます。(お手数ですが誤りがありましたら @akira6592までご連絡ください)

ひとことで言いますと、ansible-base 2.10.0 のリリース後に、gluster_* モジュールが所属するコレクションが変更され、リダイレクト先の定義も変更されたからです。

経緯

参考

経緯を追うために確認したページです。

Frequently Asked Questions — Ansible Documentation

[Ansible] 「つまずき Ansible 【Part16】モジュールの指定をFQCN表記に移行する」ふりかえり

はじめに

2020/09/20 に、YouTube Live で「つまずき Ansible 【Part16】モジュールの指定をFQCN表記に移行する」という配信をしました。

実際に作業しながらエラーと戦って進めるシリーズです。

tekunabe.connpass.com

今回は、Asnible 2.10 と Collection な Playbook を FQCN 表記に移行する作業をしました。

やったことや、わかったことをふりかえります。

  • 環境
    • ansible-base 2.10.1

動画

youtu.be


■ やったこと

基本的には、下記のブログの流れに沿いました。

tekunabe.hatenablog.jp

  • ポイント
    • Ansible 2.10 では多くのモジュールが collection に移行して、移行したモジュール名などは基本的に FQCN で指定する
    • Ansible 2.9 まで標準で含まれていたモジュールについては、装飾なしの名前指定でも FQCN へのリダイレクトの仕組みがある
      • なので、標準だったモジュールについては FQCN 指定は必須ではない(全部試したわけではないですが)
    • ansible-playbook コマンドに -vvv をつけるとリダイレクト先の FQCN が確認できるので、FQCN 表記にするならそれを参考にする
      • チェックモード(-C--check)でも確認できる
      • --syntax-check では不可
    • モジュールだけでなく、フィルターやプラグインも collection へ移行されている

再掲にはなりますが、最終的には各ファイルは以下のようになりました。

変数定義ファイル

---
ansible_network_os: cisco.ios.ios
ansible_connection: ansible.netcommon.network_cli
ansible_user: ansible
ansible_password: p@ssword
ansible_become: true
ansible_become_method: ansible.netcommon.enable 
ansible_become_password: secret

ansible.cfg

[defaults]
host_key_checking = false
stdout_callback=community.general.yaml

Playbook

---
- hosts: rt01
  gather_facts: false

  vars:
    ntp_server: 10.0.0.123

  tasks:
    - name: assert ntp server
      assert:
        that: ntp_server | ansible.netcommon.ipv4

    - name: ios test
      cisco.ios.ios_config:
        lines:
          - "ntp server {{ ntp_server }}"

いただいた質問

インターネットへの接続性は不要です。FQCN への対応表を静的なファイルで自分の中でももっていてそれを利用します。

私もやってみましたが、やはりファイルが見つからないというエラーでした。

(ab210) [root@2a5505f15a98 stumble]# ansible-playbook -i inventory.ini tofqcn.yml -vvv --checkERROR! Unexpected Exception, this is probably a bug: [Errno 2] No such file or directory: b'/root/envs/ab210/lib64/python3.6/site-packages/ansible/config/ansible_builtin_runtime.yml'
the full traceback was:

Traceback (most recent call last):
  File "<frozen importlib._bootstrap>", line 888, in _find_spec
AttributeError: '_AnsibleCollectionFinder' object has no attribute 'find_spec'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/root/envs/ab210/bin/ansible-playbook", line 92, in <module>
    mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass)
  File "/root/envs/ab210/lib64/python3.6/site-packages/ansible/cli/__init__.py", line 21, in <module>
    from ansible.inventory.manager import InventoryManager
  File "/root/envs/ab210/lib64/python3.6/site-packages/ansible/inventory/manager.py", line 39, in <module>
    from ansible.utils.helpers import deduplicate_list
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 951, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 890, in _find_spec
  File "<frozen importlib._bootstrap>", line 864, in _find_spec_legacy
  File "/root/envs/ab210/lib64/python3.6/site-packages/ansible/utils/collection_loader/_collection_finder.py", line 172, in find_module
    return _AnsibleInternalRedirectLoader(fullname=fullname, path_list=path)
  File "/root/envs/ab210/lib64/python3.6/site-packages/ansible/utils/collection_loader/_collection_finder.py", line 632, in __init__
    builtin_meta = _get_collection_metadata('ansible.builtin')
  File "/root/envs/ab210/lib64/python3.6/site-packages/ansible/utils/collection_loader/_collection_finder.py", line 965, in _get_collection_metadata
    collection_pkg = import_module('ansible_collections.' + collection_name)
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
  File "/root/envs/ab210/lib64/python3.6/site-packages/ansible/utils/collection_loader/_collection_finder.py", line 502, in load_module
    with open(to_bytes(metadata_path), 'rb') as fd:
FileNotFoundError: [Errno 2] No such file or directory: b'/root/envs/ab210/lib64/python3.6/site-packages/ansible/config/ansible_builtin_runtime.yml'
(ab210) [root@2a5505f15a98 stumble]# 


Part17 にむけて

以下のネタを検討中です。気が向いたものをやります。

  • Ansible 2.10 関連
  • connection: local ななにか
  • Ansible Toewr / AWX をコマンドがら操作する
  • ansible.cfg
  • Jinja2、フィルター
  • Windows
  • ESXi で VM作成
  • parsee_cli モジュール(Part15 の続き)
  • when や assert

[Ansible] role 単位で collections を指定する

はじめに

Playbook 内で利用する collection のモジュールを利用する際、モジュール名を FQCN(例:cisco.ios.ios_config)で指定するほか、collections ディレクティブでまとめて指定することもできます。

現時点(2020/09/20)の最新版のドキュメントには、以下のように Playbook の Play 単位で collections を指定する例が紹介されています。

- hosts: all
  collections:    # Play 単位の場合
   - my_namespace.my_collection
  tasks:
    - import_role:
        name: role1

一方、開発版のドキュメントでは、role 単位で collections を指定する例が紹介されています。role の meta/main.yml に指定します。

# myrole/meta/main.yml
collections:
  - my_namespace.first_collection
  - my_namespace.second_collection
  - other_namespace.other_collection

role としては、利用するコレクションを Playbook 側で指定されるより、role 自身で指定できる方が嬉しいのではないでしょうか。

この記事では、自分のサンプルでrole 単位での collections の指定を検証します。

  • 動作確認環境
    • ansible-base 2.10.1


■ 各ファイルの内容

ディレクトリ構成

site.yml から、testrole を呼び出します。

.
├── roles
│   └── testrole
│       ├── meta
│       │   └── main.yml  (c)
│       └── tasks
│           └── main.yml  (b)
└── site.yml   (a)

f:id:akira6592:20200920140114p:plain
全体

(a) Playbook (site.yml)

testrole role を呼び出すだけの Playbook です。 ここには collections ディレクティブを指定しません。

---
- hosts: rt01
  gather_facts: false
  
  tasks:
    - name: import testrole
      import_role:
        name: testrole

(b) role のタスク (testrole/tasks/main.yml

処理本体です。

cisco.ios collection 内の ios_config モジュールを利用する意図です。

---
- name: ios test
  ios_config:
    lines:
      - ntp server 10.0.0.123

cisco.ios.ios_config のように FQCN 指定でもよいのですが、この記事の趣旨上、装飾なしの名前にしています。

代わりに、後述の testrole/meta/main.ymlcollections をしています。

(c) role のメタ情報 (testrole/meta/main.yml

ここで collections を指定します。cisco.ios collection のみを指定しています。複数指定もできます。

---
collections:
  - cisco.ios

この指定により、装飾なしのモジュール名の探索に cisco.ios collections が加わります。 (自動で collection がインストールされるわけではありません)


■ Playbook 実行

検証用の準備(リダイレクトの無効化)

少しややこしいですが、検証のためにちょっとした準備をします。

iso_config モジュールは Ansible 2.9 以前は標準で含まれていたため、Ansible 2.10 では 移行容易性のため(おそらく)リダイレクトの仕組みがあります。つまり ios_config と指定すると、cisco_ios.ios_config にリダイレクトします。この機能に頼ると、今回の collections の指定が効いたかどうか区別できません。

そのため、この検証では該当リダイレクトを無効にします。具体的には [python環境]/lib/python3.X/site-packages/ansible/config/ansible_builtin_runtime.ymlcisco.ios.ios_config へのリダイレクト定義を、以下のようにコメントアウトします。

    # ios_config:
    #   redirect: cisco.ios.ios_config

実行

Playbook を実行します。

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

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

TASK [testrole : ios test] *******************************************************************************************************
ok: [rt01]

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

無事に cisco.ios.ios_config モジュールを実行できました。

もしろん、リダイレクトを無効にしたたまま、 collections の指定をなくすと、ERROR! couldn't resolve module/action 'ios_config'. のようにエラーになります。


おわりに

はじめにも書きましたが、role としては、利用するコレクションを Playbook 側で指定されるより、role 自身で指定できる方が制御が効いてよいでしょう。FQCN 指定やリダイレクトの仕組みに頼らない事業がある場合は、便利な方法だと思います。