今回は、Ansibleで多重ループ処理を実現する3つの方法についてまとめました。

複数のリストを同時に処理したい場合や、ネストの深い変数を処理したい場合に重宝すると思います。

【目次】

スポンサーリンク




多重ループの方法3選

ACI x Ansibleの検証をする機会があり、その際に3つ程使えそうな多重ループ処理の方法を見つけました。
まずは、3つの方法を簡単に紹介します。

product filter

subelements filter

include_tasks + 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では、プログラム言語の様な柔軟なループ処理は難しそうです。
しかし、”属人化しないコード”を目指すという観点では、むしろループに制約があった方が良いのかもしれませんね!

何かご意見やアドバイス等ありましたら、教えていただけると幸いです。
最後までありがとうございました!