今回は、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では、プログラム言語の様な柔軟なループ処理は難しそうです。
しかし、”属人化しないコード”を目指すという観点では、むしろループに制約があった方が良いのかもしれませんね!
何かご意見やアドバイス等ありましたら、教えていただけると幸いです。
最後までありがとうございました!