今回は、Ansibleで多重ループ処理を実現する3つの方法についてまとめました。
複数のリストを同時に処理したい場合や、ネストの深い変数を処理したい場合に重宝すると思います。
【目次】
多重ループの方法3選
ACI x Ansibleの検証をする機会があり、その際に3つ程使えそうな多重ループ処理の方法を見つけました。
まずは、3つの方法を簡単に紹介します。
product filter
- https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#with-nested-with-cartesian
- 2つの別々のリストを同時にループさせるイメージ
subelements filter
- https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#with-subelements
- 既に入れ子になっているリストのサブ要素をループさせるイメージ
include_tasks + loop_var
- https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#defining-inner-and-outer-variable-names-with-loop-var
- 外側のタスクからループでアイテムを引き渡し、内側のタスクで更にループさせるイメージ
これ以降のセクションでは、ユースケースや例となるPlaybookを交えて、各方法の動作を追っていきます。
product filter
product filterを利用すると、2つの別々のリストを同時にループさせることが出来ます。
最初に指定したリストを基準に、2番目のリストを何回もループさせるイメージです。
ユースケースとして、次のような例が考えられます。
- 全てのTenant配下に同じVRFを作成したい (ACIの例)
- 全てのDB配下に、同じユーザを作成したい 等
変数構造
--- tenants: - 1111-Tenant - 2222-Tenant vrfs: - Develop_VRF - Test_VRF - Production_VRF
Playbook
- debug: msg: - "{{ item.0 }}" # 1つ目のリストのアイテム - "{{ item.1 }}" # 2つ目のリストのアイテム loop: "{{ tenants | product(vrfs) }}"
実行結果
TASK [debug] ******************************************************************************************************************** ok: [localhost] => (item=['1111-Tenant', 'Develop_VRF']) => { "msg": [ "1111-Tenant", "Develop_VRF" ] } ok: [localhost] => (item=['1111-Tenant', 'Test_VRF']) => { "msg": [ "1111-Tenant", "Test_VRF" ] } ok: [localhost] => (item=['1111-Tenant', 'Production_VRF']) => { "msg": [ "1111-Tenant", "Production_VRF" ] } ok: [localhost] => (item=['2222-Tenant', 'Develop_VRF']) => { "msg": [ "2222-Tenant", "Develop_VRF" ] } ok: [localhost] => (item=['2222-Tenant', 'Test_VRF']) => { "msg": [ "2222-Tenant", "Test_VRF" ] } ok: [localhost] => (item=['2222-Tenant', 'Production_VRF']) => { "msg": [ "2222-Tenant", "Production_VRF" ] } PLAY RECAP ********************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
全てのTenantと引き渡したVRFが紐づけられていることを確認できました。
subelements filter
subelements filterを利用すると、リストのサブ要素をさらに取り出してループさせることが出来ます。
最初に指定したリストからサブ要素を指定し、親要素と同時にループさせるイメージです。
階層構造で設定を定義したい場合などで重宝すると思います。
ユースケースとして、次のような例が考えられます。
- Application Profileの配下に複数のEPGを定義したい (ACIの例)
変数構造
--- aps: - ap_name: Web_App epgs: - epg_name: Web - epg_name: DB
Playbook
- debug: msg: - "{{ item.0.ap_name }}" # リストの一番上の階層のアイテム - "{{ item.1 }}" # subelementsで指定した要素配下のアイテム loop: "{{ aps | subelements('epgs') }}"
実行結果
TASK [debug] ******************************************************************************************************************** ok: [localhost] => (item=[{'ap_name': 'Web_App', 'epgs': ['Web', 'DB']}, 'Web']) => { "msg": [ "Web_App", "Web" ] } ok: [localhost] => (item=[{'ap_name': 'Web_App', 'epgs': ['Web', 'DB']}, 'DB']) => { "msg": [ "Web_App", "DB" ] } PLAY RECAP ********************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Application Profileの配下に複数のEPGが紐づけられていることを確認できました。
include_tasks + loop_var
include_tasks + loop_varを利用すると、外側のタスクでループさせた変数を、内側のタスクでさらにループさせることが出来ます。
loop_varはループで取り出したアイテムに名前を付与するためのオプションです。
デフォルトは”item”という名前ですが、この名前を変更することによって、外側と内側のタスクでアイテムを区別できるようになります。
ユースケースとして、次のような例が考えられます。
- Application Profile配下のEPGにContractを紐づけたい (ACIの例)
変数構造
--- - aps: - ap_name: Test_Client_Web_AP epgs: - epg_name: Web contracts: - contract_name: Web_to_DB contract_type: consumer - epg_name: DB contracts: - contract_name: Web_to_DB contract_type: provider
Playbook (外側)
- include_tasks: inner.yml loop: "{{ aps | subelements('epgs') }}" loop_control: loop_var: outer_item # 外側のアイテムに名前を付与
Playbook (inner.yml)
- debug: msg: - "{{ outer_item.0.ap_name }}" - "{{ outer_item.1.epg_name }}" - "{{ inner_item.0.contract_name }}" - "{{ inner_item.contract_type }}" loop: "{{ outer_item.contracts }}" loop_control: loop_var: inner_item # 内側のアイテムに名前を付与
実行結果
TASK [include_tasks] ************************************************************************************************************ included: /root/test/inner.yml for localhost => (item=[{'ap_name': 'Web_App', 'epgs': [{'epg_name': 'Web', 'contracts': [{'contract_name': 'Web_to_DB', 'contract_type': 'consumer'}]}, {'epg_name': 'DB', 'contracts': [{'contract_name': 'Web_to_DB', 'contract_type': 'provider'}]}]}, {'epg_name': 'Web', 'contracts': [{'contract_name': 'Web_to_DB', 'contract_type': 'consumer'}]}]) included: /root/test/inner.yml for localhost => (item=[{'ap_name': 'Web_App', 'epgs': [{'epg_name': 'Web', 'contracts': [{'contract_name': 'Web_to_DB', 'contract_type': 'consumer'}]}, {'epg_name': 'DB', 'contracts': [{'contract_name': 'Web_to_DB', 'contract_type': 'provider'}]}]}, {'epg_name': 'DB', 'contracts': [{'contract_name': 'Web_to_DB', 'contract_type': 'provider'}]}]) TASK [debug] ******************************************************************************************************************** ok: [localhost] => (item={'contract_name': 'Web_to_DB', 'contract_type': 'consumer'}) => { "msg": [ "Web_App", "Web", "Web_to_DB", "consumer" ] } TASK [debug] ******************************************************************************************************************** ok: [localhost] => (item={'contract_name': 'Web_to_DB', 'contract_type': 'provider'}) => { "msg": [ "Web_App", "DB", "Web_to_DB", "provider" ] } PLAY RECAP ********************************************************************************************************************** localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
各EPGにContractが紐づいていることを確認できました。
おわりに
今回は、Ansibleで使える3つの多重ループの方法についてまとめてみました。
Ansibleではループに色々と制約があり、やりたいことを実現するまでに結構苦労しました。。。
Ansibleでは、プログラム言語の様な柔軟なループ処理は難しそうです。
しかし、”属人化しないコード”を目指すという観点では、むしろループに制約があった方が良いのかもしれませんね!
何かご意見やアドバイス等ありましたら、教えていただけると幸いです。
最後までありがとうございました!