루프

루프 (Loops)

Ansible은 작업을 여러 번 실행하기 위해 loop, with_<lookup>, until 키워드를 제공해요. 흔히 쓰이는 루프 예시로는 file 모듈로 여러 파일/디렉터리의 소유권을 바꾸거나, user 모듈로 여러 사용자를 만들거나, 특정 결과에 도달할 때까지 폴링 단계를 반복하는 경우가 있어요.

출처: 문서

본문

Ansible은 작업을 여러 번 실행하기 위해 loop, with_<lookup>, until 키워드를 제공해요. 흔히 쓰이는 루프 예시로는 file 모듈로 여러 파일/디렉터리의 소유권을 바꾸기, user 모듈로 여러 사용자 만들기, 특정 결과에 도달할 때까지 폴링 단계를 반복하는 것이 있어요.

참고

  • Ansible 2.5에서 루프를 더 단순히 하는 방법으로 loop를 추가했어요. 대부분의 사용 사례에 이것을 권장해요.
  • with_<lookup> 사용은 폐기하지 않았어요. 그 문법은 가까운 미래에도 여전히 유효할 거예요.
  • loopwith_<lookup>은 상호 배타적이에요. until 아래에 중첩하는 것은 가능하지만, 각 루프 반복에 영향을 줘요.

루프 비교하기

  • until의 일반적인 사용 사례는 실패할 가능성이 있는 작업과 관련이 있고, loopwith_<lookup>은 약간의 변형이 있는 반복 작업을 위한 거예요.
  • loopwith_<lookup>은 입력으로 사용된 목록의 항목마다 작업을 한 번씩 실행하는 반면, until은 조건이 충족될 때까지 작업을 다시 실행해요. 프로그래머 관점에서 전자는 "for 루프"이고 후자는 "while/until 루프"예요.
  • with_<lookup> 키워드는 Lookup 플러그인에 의존해요. items조차도 lookup이에요.
  • loop 키워드는 with_list와 동일하며, 단순한 루프에 가장 좋은 선택이에요.
  • loop 키워드는 문자열을 입력으로 받지 않아요. 'loop에 리스트 입력 보장하기: lookup 대신 query 사용하기'를 참고하세요.
  • until 키워드는 "암묵적으로 템플릿 처리되는"( {{ }} 불필요) '끝 조건(end conditional)'(참/거짓을 반환하는 표현식)을 받아요. 보통 작업에 대해 register한 변수를 기반으로 해요.
  • loop_controlloopwith_<lookup> 모두에 영향을 주지만 until에는 영향을 주지 않아요. untilretriesdelay라는 자체 동반 키워드가 있어요.
  • 일반적으로 'with_X에서 loop로 마이그레이션하기'에서 다루는 with_*의 모든 사용은 loop를 쓰도록 업데이트할 수 있어요.
  • with_itemsloop로 바꿀 때는 주의해야 해요. with_items는 암시적인 단일 레벨 평탄화(single-level flattening)를 수행하기 때문이에요. 정확히 같은 결과를 얻으려면 loop와 함께 | flatten(1)을 사용해야 할 수 있어요. 예를 들어 다음과 같은 출력을 얻으려면:
    with_items:
      - 1
      - [2,3]
      - 4
    
    다음과 같이 해야 해요:
    loop: "{{ [1, [2, 3], 4] | flatten(1) }}"
    
  • 루프 안에서 lookup을 사용해야 하는 with_* 문은 loop 키워드를 쓰도록 변환하면 안 돼요. 예를 들어 다음처럼 하기보다는:
    loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"
    
    다음을 유지하는 게 더 깔끔해요:
    with_fileglob: '*.txt'
    

루프 사용하기

단순 리스트 반복하기

반복 작업은 단순한 문자열 리스트에 대한 표준 루프로 작성할 수 있어요. 리스트를 작업에서 직접 정의할 수 있어요.

- name: Add several users
  ansible.builtin.user:
    name: "{{ item }}"
    state: present
    groups: "wheel"
  loop:
     - testuser1
     - testuser2

리스트를 변수 파일이나 플레이의 'vars' 섹션에 정의한 다음, 작업에서 리스트 이름을 참조할 수 있어요.

loop: "{{ somelist }}"

위 두 예시 중 어느 쪽이든 다음 예시와 동일해요:

- name: Add user testuser1
  ansible.builtin.user:
    name: "testuser1"
    state: present
    groups: "wheel"

- name: Add user testuser2
  ansible.builtin.user:
    name: "testuser2"
    state: present
    groups: "wheel"

일부 플러그인은 리스트를 파라미터에 직접 전달할 수 있어요. yumapt 같은 대부분의 패키징 모듈이 이 기능을 가져요. 가능하다면 작업을 루프로 돌리는 것보다 리스트를 파라미터로 전달하는 것이 더 좋아요. 예:

- name: Optimal yum
  ansible.builtin.yum:
    name: "{{ list_of_packages }}"
    state: present

- name: Non-optimal yum, slower and may cause issues with interdependencies
  ansible.builtin.yum:
    name: "{{ item }}"
    state: present
  loop: "{{ list_of_packages }}"

특정 모듈의 파라미터에 리스트를 전달할 수 있는지 모듈 문서를 확인하세요.

해시 리스트 반복하기

해시 리스트가 있다면 루프에서 하위 키를 참조할 수 있어요. 예:

- name: Add several users
  ansible.builtin.user:
    name: "{{ item.name }}"
    state: present
    groups: "{{ item.groups }}"
  loop:
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }

조건문을 루프와 결합하면 when: 문이 각 항목마다 별도로 처리돼요. 예시는 when과 함께하는 기본 조건문을 참고하세요.

딕셔너리 반복하기

dict를 루프로 돌리려면 dict2items를 사용하세요:

- name: Using dict2items
  ansible.builtin.debug:
    msg: "{{ item.key }}: {{ item.value.ip_address }} {{ item.value.role }}"
  loop: "{{ server_configs | dict2items }}"
  vars:
    server_configs:
      web_01:
        ip_address: "10.1.1.50"
        role: "frontend"
      db_01:
        ip_address: "10.1.1.100"
        role: "backend_db"

이 예시는 server_configs를 반복하면서 키와 선택된 중첩 필드를 출력해요.

딕셔너리의 값이 다시 딕셔너리일 때(예: 각 그룹이 gid를 담은 dict로 매핑될 때), dict2items를 적용한 후 각 루프 항목에는 item.keyitem.value 두 속성이 있다는 것을 기억하세요. 중첩 필드에는 item.value.<field>로 접근해요.

루프로 변수 등록하기

루프의 출력을 변수로 등록할 수 있어요. 예:

- name: Register loop output as a variable
  ansible.builtin.shell: "echo {{ item }}"
  loop:
    - "one"
    - "two"
  register: echo

루프와 함께 register를 사용하면 변수에 담긴 데이터 구조는 모듈의 모든 응답 목록인 results 속성을 포함해요. 이것은 루프 없이 register를 사용할 때 반환되는 데이터 구조와는 달라요. results 옆에 있는 changed/failed/skipped 속성은 전체 상태를 나타내요. changed/failed는 반복 중 하나라도 변경/실패를 트리거하면 true가 되고, skipped는 모든 반복이 건너뛰어졌을 때만 true가 돼요.

{
    "changed": true,
    "msg": "All items completed",
    "results": [
        {
            "changed": true,
            "cmd": "echo \"one\" ",
            "delta": "0:00:00.003110",
            "end": "2013-12-19 12:00:05.187153",
            "invocation": {
                "module_args": "echo \"one\"",
                "module_name": "shell"
            },
            "item": "one",
            "rc": 0,
            "start": "2013-12-19 12:00:05.184043",
            "stderr": "",
            "stdout": "one"
        },
        {
            "changed": true,
            "cmd": "echo \"two\" ",
            "delta": "0:00:00.002920",
            "end": "2013-12-19 12:00:05.245502",
            "invocation": {
                "module_args": "echo \"two\"",
                "module_name": "shell"
            },
            "item": "two",
            "rc": 0,
            "start": "2013-12-19 12:00:05.242582",
            "stderr": "",
            "stdout": "two"
        }
    ]
}

등록된 변수를 반복해서 결과를 검사하는 이후의 루프는 이렇게 생겼을 수 있어요:

- name: Fail if return code is not 0
  ansible.builtin.fail:
    msg: "The command ({{ item.cmd }}) did not have a 0 return code"
  when: item.rc != 0
  loop: "{{ echo.results }}"

반복 중에는 현재 항목의 결과가 변수에 담겨요.

- name: Place the result of the current item in the variable
  ansible.builtin.shell: echo "{{ item }}"
  loop:
    - one
    - two
  register: echo
  changed_when: echo.stdout != "one"

(버전 2.21에 추가됨)

변수 등록 없이 작업 결과에 접근하거나 여러 변수를 한 번에 등록하려면 register projection을 사용할 수도 있어요. 자세한 내용은 변수 등록하기 (Registering variables)를 참고하세요.

반복 중 현재 항목의 결과는 암시적 변수 _taskresult 속성에서도 접근할 수 있어요. 이를 통해 변수 등록 없이 루프 항목 결과에 접근할 수 있어요.

- name: Place the result of the current item in the variable
  ansible.builtin.shell: echo "{{ item }}"
  loop:
    - one
    - two
  changed_when: _task.result.stdout != "one"

register projection과 _task 변수에 대한 자세한 내용은 변수 등록하기 (Registering variables)를 참고하세요.

반복 중에 누적된 전체 루프 결과 목록에 접근하려면 _task 암시적 변수의 loop_result 속성을 사용할 수 있어요.

- name: Run a loop and access individual item output
  ansible.builtin.shell: "{{ item }}"
  register:
    foo_output: _task.loop_result.results[0].stdout
    bar_output: _task.loop_result.results[1].stdout
  loop:
    - /usr/bin/foo
    - /usr/bin/bar

참고: Register projection 표현식은 작업이 완료된 후 평가되므로, 등록된 변수를 작업 후에만 사용한다면 _task.loop_result에 접근할 때 default가 필요 없어요. 하지만 등록된 projection 변수를 반복 중 조건부에서 사용하려면, 그 변수는 첫 번째 반복에 존재하지 않으므로 register 표현식 자체에서 default를 사용해야 해요.

- name: Using registered projection in conditional during iteration
  ansible.builtin.shell: "{{ item }}"
  register:
    first_output: _task.loop_result.results[0].stdout | default('')  # default needed to use first_output during iteration
  loop: [1, 2, 3]
  when: first_output != 'skip'

조건부에서 _task를 직접 사용하는 예시는 조건문 (Conditionals)을 참고하세요.

여러 변수를 등록할 때 이름만 있는 변수 등록과 같은 기능에 접근하려면 _task 암시적 변수의 polymorphic_result 속성을 사용할 수 있어요. 반복 중에는 가장 최근 루프 반복의 결과를 담고, 이후에는 전체 작업 결과를 담아요. 이렇게 하면 다음 두 작업이 동일해져요:

- name: Run a loop and register a single variable
  ansible.builtin.shell: "{{ item }}"
  register: foo_bar_result
  loop:
    - /usr/bin/foo
    - /usr/bin/bar

- name: Register the same variable in the multi-variable register format
  ansible.builtin.shell: "{{ item }}"
  register:
    foo_bar_result: _task.polymorphic_result
  loop:
    - /usr/bin/foo
    - /usr/bin/bar

조건이 충족될 때까지 작업 재시도하기

(버전 1.4에 추가됨)

until 키워드를 사용하면 특정 조건이 충족될 때까지 작업을 재시도할 수 있어요. 예시:

- name: Retry a task until a certain condition is met
  ansible.builtin.shell: /usr/bin/foo
  register: result
  until: result.stdout.find("all systems go") != -1
  retries: 5
  delay: 10

이 작업은 각 시도 사이에 10초의 지연을 두고 최대 5번 실행돼요. 어떤 시도의 결과 stdout에 "all systems go"가 있으면 작업은 성공해요. "retries"의 기본값은 3이고 "delay"는 5예요.

개별 재시도의 결과를 보려면 -vv로 플레이를 실행하세요.

until로 작업을 실행하고 결과를 변수로 등록하면, 등록된 변수에는 그 작업의 재시도 횟수를 기록하는 "attempts"라는 키가 포함돼요.

until을 지정하지 않으면 작업은 성공할 때까지 재시도하지만 최대 retries 횟수까지예요 (버전 2.16에 추가됨).

until 키워드를 loopwith_<lookup>과 결합할 수 있어요. 루프의 각 요소에 대한 작업 결과가 변수에 등록되고 until 조건에서 사용될 수 있어요. 예시:

- name: Retry combined with a loop
  uri:
    url: "https://{{ item }}.ansible.com"
    method: GET
  register: uri_output
  with_items:
  - "galaxy"
  - "docs"
  - "forum"
  - "www"
  retries: 2
  delay: 1
  until: "uri_output.status == 200"

참고: 루프에서 timeout 키워드를 사용하면 작업 액션의 각 시도에 적용돼요. 자세한 내용은 TASK_TIMEOUT을 참고하세요.

암시적 _task 변수를 사용하면 변수를 등록하지 않고 until에서 현재 결과에 접근할 수 있어요:

- name: Wait for each service to be ready
  ansible.builtin.command: systemctl is-active {{ item }}
  loop:
    - nginx
    - postgresql
    - redis
  retries: 5
  delay: 2
  until: _task.result.rc == 0

참고: _task 암시적 변수와 register projection에 대한 자세한 내용은 '변수 등록하기'를 참고하세요.

인벤토리 반복하기

보통 플레이 자체가 인벤토리에 대한 루프예요. 하지만 때로는 작업이 다른 호스트 집합에 대해 같은 일을 해야 할 수도 있어요. 인벤토리 또는 그 일부를 반복하려면 ansible_play_batchgroups 변수와 함께 일반 loop를 사용할 수 있어요.

- name: Show all the hosts in the inventory
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ groups['all'] }}"

- name: Show all the hosts in the current play
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ ansible_play_batch }}"

inventory_hostnames라는 특정 lookup 플러그인도 있는데, 이렇게 사용할 수 있어요:

- name: Show all the hosts in the inventory
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ query('inventory_hostnames', 'all') }}"

- name: Show all the hosts matching the pattern, ie all but the group www
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ query('inventory_hostnames', 'all:!www') }}"

패턴에 대한 자세한 내용은 패턴: 호스트와 그룹 타겟팅에서 찾을 수 있어요.

loop에 리스트 입력 보장하기: lookup 대신 query 사용하기

loop 키워드는 입력으로 리스트를 요구하지만, lookup 키워드는 기본적으로 쉼표로 구분된 값의 문자열을 반환해요. Ansible 2.5는 항상 리스트를 반환하는 query라는 새 Jinja2 함수를 도입했어요. 이 함수는 loop 키워드를 쓸 때 lookup 플러그인에서 더 단순한 인터페이스와 더 예측 가능한 출력을 제공해요.

wantlist=True를 사용해서 lookuploop에 리스트를 반환하도록 강제하거나, 대신 query를 사용할 수 있어요.

다음 두 예시는 같은 일을 해요.

loop: "{{ query('inventory_hostnames', 'all') }}"

loop: "{{ lookup('inventory_hostnames', 'all', wantlist=True) }}"

루프에 제어 추가하기

(버전 2.1에 추가됨)

loop_control 키워드를 사용하면 루프를 유용한 방식으로 관리할 수 있어요.

label로 루프 출력 제한하기

(버전 2.2에 추가됨)

복잡한 데이터 구조를 루프로 돌리면 작업의 콘솔 출력이 엄청나게 커질 수 있어요. 표시되는 출력을 제한하려면 loop_control과 함께 label 지시문을 사용하세요.

- name: Create servers
  digital_ocean:
    name: "{{ item.name }}"
    state: present
  loop:
    - name: server1
      disks: 3gb
      ram: 15Gb
      network:
        nic01: 100Gb
        nic02: 10Gb
        # ...
  loop_control:
    label: "{{ item.name }}"

이 작업의 출력은 각 itemname 필드만 표시하고, 여러 줄짜리 {{ item }} 변수의 전체 내용은 표시하지 않아요.

참고: 이것은 콘솔 출력을 더 읽기 쉽게 만들기 위한 것이지, 민감한 데이터를 보호하기 위한 것이 아니에요. loop에 민감한 데이터가 있다면 작업에 no_log: true를 설정해서 노출을 방지하세요.

루프 안에서 일시 중지하기

(버전 2.2에 추가됨)

작업 루프의 각 항목 실행 사이의 시간(초)을 제어하려면 loop_control과 함께 pause 지시문을 사용하세요.

# main.yml
- name: Create servers, pause 3s before creating next
  community.digitalocean.digital_ocean:
    name: "{{ item }}"
    state: present
  loop:
    - server1
    - server2
  loop_control:
    pause: 3

루프에서 빠져나오기

(버전 2.18에 추가됨)

Jinja2 표현식에 기반해서 어떤 항목 후에 루프를 종료하려면 loop_control과 함께 break_when 지시문을 사용하세요.

# main.yml
- name: Use set_fact in a loop until a condition is met
  vars:
    special_characters: "!@#$%^&*(),.?:{}|<>"
    character_set: "digits,ascii_letters,{{ special_characters }}"
    password_policy: '^(?=.*\d)(?=.*[A-Z])(?=.*[{{ special_characters | regex_escape }}]).{12,}$'
  block:
    - name: Generate a password until it contains a digit, uppercase letter, and special character (10 attempts)
      set_fact:
        password: "{{ lookup('password', '/dev/null', chars=character_set, length=12) }}"
      loop: "{{ range(0, 10) }}"
      loop_control:
        break_when:
          - password is match(password_policy)

    - fail:
        msg: "Maximum attempts to generate a valid password exceeded"
      when: password is not match(password_policy)

register projection을 사용할 때 break_when에서 등록된 변수를 참조할 수 있어요:

- name: Break after processing two items
  ansible.builtin.debug:
    msg: "Processing {{ item }}"
  loop: [1, 2, 3, 4, 5]
  loop_control:
    break_when: items_processed == 2
  register:
    items_processed: _task.loop_result.results | length

index_var로 루프 진행 상황 추적하기

(버전 2.5에 추가됨)

루프에서 현재 위치를 추적하려면 loop_control과 함께 index_var 지시문을 사용하세요. 이 지시문은 현재 루프 인덱스를 담을 변수 이름을 지정해요.

- name: Count our fruit
  ansible.builtin.debug:
    msg: "{{ item }} with index {{ my_idx }}"
  loop:
    - apple
    - banana
    - pear
  loop_control:
    index_var: my_idx

참고: index_var는 0부터 시작해요.

확장 루프 변수

(버전 2.8에 추가됨)

Ansible 2.8부터 loop control에 extended 옵션을 사용해서 확장 루프 정보를 얻을 수 있어요. 이 옵션은 다음 정보를 노출해요.

변수 설명
ansible_loop.allitems 루프의 모든 항목 목록
ansible_loop.index 루프의 현재 반복 (1부터 시작)
ansible_loop.index0 루프의 현재 반복 (0부터 시작)
ansible_loop.revindex 루프 끝에서부터의 반복 횟수 (1부터 시작)
ansible_loop.revindex0 루프 끝에서부터의 반복 횟수 (0부터 시작)
ansible_loop.first 첫 번째 반복이면 True
ansible_loop.last 마지막 반복이면 True
ansible_loop.length 루프의 항목 수
ansible_loop.previtem 루프의 이전 반복 항목. 첫 번째 반복 동안에는 정의되지 않음
ansible_loop.nextitem 루프의 다음 반복 항목. 마지막 반복 동안에는 정의되지 않음
loop_control:
  extended: true

참고: loop_control.extended를 사용하면 제어 노드에서 더 많은 메모리를 사용하게 돼요. ansible_loop.allitems가 매 루프마다 전체 루프 데이터에 대한 참조를 담고 있기 때문이에요. 메인 ansible 프로세스 안의 callback 플러그인에서 표시를 위해 결과를 직렬화할 때, 이 참조들이 역참조되어 메모리 사용량이 증가할 수 있어요.

(버전 2.14에 추가됨)

메모리 소비를 줄이기 위해 ansible_loop.allitems 항목을 비활성화하려면 loop_control.extended_allitems: false를 설정하세요.

loop_control:
  extended: true
  extended_allitems: false

loop_var의 이름 접근하기

(버전 2.8에 추가됨)

Ansible 2.8부터 loop_control.loop_var에 제공된 값의 이름을 ansible_loop_var 변수로 얻을 수 있어요.

롤 작성자의 경우, 필요한 loop_var 값을 정해두는 대신 루프를 허용하는 롤을 작성하면서 다음을 통해 값을 얻을 수 있어요:

"{{ lookup('vars', ansible_loop_var) }}"

중첩 루프 (Nested Loops)

이 예시들에서는 loop를 사용하고 있지만, with_<lookup>에도 같은 내용이 적용돼요.

중첩 리스트 반복하기

'중첩' 루프를 만드는 가장 간단한 방법은 루프를 중첩하지 않고, 같은 결과를 얻도록 데이터를 포맷하는 것이에요. Jinja2 표현식을 사용해서 복잡한 리스트를 반복할 수 있어요. 예를 들어 루프가 중첩 리스트를 결합하여 중첩 루프를 시뮬레이션할 수 있어요.

- name: Give users access to multiple databases
  community.mysql.mysql_user:
    name: "{{ item[0] }}"
    priv: "{{ item[1] }}.*:ALL"
    append_privs: true
    password: "foo"
  loop: "{{ ['alice', 'bob'] | product(['clientdb', 'employeedb', 'providerdb']) | list }}"

include_tasks로 루프 쌓기 (Stacking loops)

(버전 2.1에 추가됨)

include_tasks를 사용해서 두 개의 루프 작업을 중첩할 수 있어요. 하지만 기본적으로 Ansible은 각 루프에 대해 루프 변수 item을 설정해요. 즉 내부 중첩 루프가 외부 루프의 item 값을 덮어써요. 이를 피하려면 loop_control과 함께 loop_var로 각 루프의 변수 이름을 지정할 수 있어요.

# main.yml
- include_tasks: inner.yml
  loop:
    - 1
    - 2
    - 3
  loop_control:
    loop_var: outer_item

# inner.yml
- name: Print outer and inner items
  ansible.builtin.debug:
    msg: "outer item={{ outer_item }} inner item={{ item }}"
  loop:
    - a
    - b
    - c

참고: Ansible이 현재 루프가 이미 정의된 변수를 사용하고 있음을 감지하면 작업을 실패시키는 오류를 발생시켜요.

Until과 loop

until 조건은 loopitem마다 적용돼요:

- debug: msg={{item}}
  loop:
    - 1
    - 2
    - 3
  retries: 2
  until: item > 2

이렇게 하면 Ansible이 처음 2개 항목을 2번 재시도하고, 3번째 시도에서 그 항목을 실패시키며, 3번째 항목은 첫 시도에서 성공하고, 결국 전체 작업은 실패해요.

[started TASK: debug on localhost]
FAILED - RETRYING: [localhost]: debug (2 retries left).Result was: {
    "attempts": 1,
    "changed": false,
    "msg": 1,
    "retries": 3
}
FAILED - RETRYING: [localhost]: debug (1 retries left).Result was: {
    "attempts": 2,
    "changed": false,
    "msg": 1,
    "retries": 3
}
failed: [localhost] (item=1) => {
    "msg": 1
}
FAILED - RETRYING: [localhost]: debug (2 retries left).Result was: {
    "attempts": 1,
    "changed": false,
    "msg": 2,
    "retries": 3
}
FAILED - RETRYING: [localhost]: debug (1 retries left).Result was: {
    "attempts": 2,
    "changed": false,
    "msg": 2,
    "retries": 3
}
failed: [localhost] (item=2) => {
    "msg": 2
}
ok: [localhost] => (item=3) => {
    "msg": 3
}
fatal: [localhost]: FAILED! => {"msg": "One or more items failed"}

with_X에서 loop로 마이그레이션하기

대부분의 경우 루프는 with_X 스타일 루프보다 loop 키워드와 함께 사용할 때 가장 잘 동작해요. loop 문법은 querylookup을 복잡하게 사용하는 것보다 필터로 표현하는 것이 대개 가장 좋아요.

이 예시들은 많은 일반적인 with_ 스타일 루프를 loop와 필터로 변환하는 방법을 보여줘요.

with_list

with_listloop로 직접 대체돼요.

- name: with_list
  ansible.builtin.debug:
    msg: "{{ item }}"
  with_list:
    - one
    - two

- name: with_list -> loop
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop:
    - one
    - two

with_items

with_itemsloopflatten 필터로 대체돼요.

- name: with_items
  ansible.builtin.debug:
    msg: "{{ item }}"
  with_items: "{{ items }}"

- name: with_items -> loop
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ items|flatten(levels=1) }}"

with_indexed_items

with_indexed_itemsloop, flatten 필터, loop_control.index_var로 대체돼요.

- name: with_indexed_items
  ansible.builtin.debug:
    msg: "{{ item.0 }} - {{ item.1 }}"
  with_indexed_items: "{{ items }}"

- name: with_indexed_items -> loop
  ansible.builtin.debug:
    msg: "{{ index }} - {{ item }}"
  loop: "{{ items|flatten(levels=1) }}"
  loop_control:
    index_var: index

with_flattened

with_flattenedloopflatten 필터로 대체돼요.

- name: with_flattened
  ansible.builtin.debug:
    msg: "{{ item }}"
  with_flattened: "{{ items }}"

- name: with_flattened -> loop
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ items|flatten }}"

with_together

with_togetherloopzip 필터로 대체돼요.

- name: with_together
  ansible.builtin.debug:
    msg: "{{ item.0 }} - {{ item.1 }}"
  with_together:
    - "{{ list_one }}"
    - "{{ list_two }}"

- name: with_together -> loop
  ansible.builtin.debug:
    msg: "{{ item.0 }} - {{ item.1 }}"
  loop: "{{ list_one|zip(list_two)|list }}"

복잡한 데이터를 사용하는 또 다른 예시:

- name: with_together -> loop
  ansible.builtin.debug:
    msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}"
  loop: "{{ data[0]|zip(*data[1:])|list }}"
  vars:
    data:
      - ['a', 'b', 'c']
      - ['d', 'e', 'f']
      - ['g', 'h', 'i']

with_dict

with_dictloopdictsort 또는 dict2items 필터로 대체할 수 있어요.

- name: with_dict
  ansible.builtin.debug:
    msg: "{{ item.key }} - {{ item.value }}"
  with_dict: "{{ dictionary }}"

- name: with_dict -> loop (option 1)
  ansible.builtin.debug:
    msg: "{{ item.key }} - {{ item.value }}"
  loop: "{{ dictionary|dict2items }}"

- name: with_dict -> loop (option 2)
  ansible.builtin.debug:
    msg: "{{ item.0 }} - {{ item.1 }}"
  loop: "{{ dictionary|dictsort }}"

with_sequence

with_sequencelooprange 함수, 그리고 잠재적으로 format 필터로 대체돼요.

- name: with_sequence
  ansible.builtin.debug:
    msg: "{{ item }}"
  with_sequence: start=0 end=4 stride=2 format=testuser%02x

- name: with_sequence -> loop
  ansible.builtin.debug:
    msg: "{{ 'testuser%02x' | format(item) }}"
  loop: "{{ range(0, 4 + 1, 2)|list }}"

루프의 범위는 끝점을 제외해요.

with_subelements

with_subelementsloopsubelements 필터로 대체돼요.

- name: with_subelements
  ansible.builtin.debug:
    msg: "{{ item.0.name }} - {{ item.1 }}"
  with_subelements:
    - "{{ users }}"
    - mysql.hosts

- name: with_subelements -> loop
  ansible.builtin.debug:
    msg: "{{ item.0.name }} - {{ item.1 }}"
  loop: "{{ users|subelements('mysql.hosts') }}"

with_nested/with_cartesian

with_nestedwith_cartesian은 loop와 product 필터로 대체돼요.

- name: with_nested
  ansible.builtin.debug:
    msg: "{{ item.0 }} - {{ item.1 }}"
  with_nested:
    - "{{ list_one }}"
    - "{{ list_two }}"

- name: with_nested -> loop
  ansible.builtin.debug:
    msg: "{{ item.0 }} - {{ item.1 }}"
  loop: "{{ list_one|product(list_two)|list }}"

with_random_choice

with_random_choiceloop 없이 random 필터만 사용하는 것으로 대체돼요.

- name: with_random_choice
  ansible.builtin.debug:
    msg: "{{ item }}"
  with_random_choice: "{{ my_list }}"

- name: with_random_choice -> loop (No loop is needed here)
  ansible.builtin.debug:
    msg: "{{ my_list|random }}"
  tags: random

더 알아보기 (Learn more)