플레이북 실행 제어하기: 전략과 그 외

플레이북 실행 제어하기: 전략과 그 외 (Controlling playbook execution: strategies and more)

기본적으로 Ansible은 5개의 fork를 사용해서, 어떤 호스트에서든 다음 작업을 시작하기 전에 플레이의 영향을 받는 모든 호스트에서 각 작업을 실행해요. 이 기본 동작을 바꾸고 싶다면 다른 전략 플러그인을 사용하거나, fork 개수를 바꾸거나, serial 같은 여러 키워드 중 하나를 적용하면 돼요.

출처: 문서

본문

기본적으로 Ansible은 5개의 fork를 사용해서, 어떤 호스트에서든 다음 작업을 시작하기 전에 플레이의 영향을 받는 모든 호스트에서 각 작업을 실행해요. 이 기본 동작을 바꾸고 싶다면 다른 전략 플러그인을 사용하거나 fork 개수를 바꾸거나, serial 같은 여러 키워드 중 하나를 적용하면 돼요.

전략 선택하기

위에서 설명한 기본 동작은 linear 전략이에요. Ansible은 다른 전략도 제공해요. debug 전략(작업 디버깅하기 참고)과 free 전략이 있어요. free 전략은 각 호스트가 가능한 한 빨리 플레이가 끝날 때까지 스스로 실행하게 해 줘요:

- hosts: all
  strategy: free
  tasks:
  # ...

위와 같이 각 플레이마다 다른 전략을 선택할 수도 있고, ansible.cfgdefaults 스탠자 아래에서 선호하는 전략을 전역으로 설정할 수도 있어요:

[defaults]
strategy = free

모든 전략은 전략 플러그인으로 구현돼요. 각 전략 플러그인이 어떻게 동작하는지 자세한 내용은 해당 플러그인 문서를 확인하세요.

fork 개수 설정하기

처리 능력이 충분하고 더 많은 fork를 사용하고 싶다면 ansible.cfg에서 그 숫자를 설정할 수 있어요:

[defaults]
forks = 30

또는 명령줄로 전달할 수도 있어요: ansible-playbook -f 30 my_playbook.yml.

실행 제어를 위한 키워드 사용하기

전략 외에도 여러 키워드가 플레이 실행에 영향을 줘요. serial로 한 번에 관리하고 싶은 호스트의 숫자, 백분율, 또는 숫자 목록을 설정할 수 있어요. Ansible은 지정된 숫자나 백분율의 호스트에서 플레이를 완료한 뒤 다음 호스트 배치를 시작해요. throttle로 블록이나 작업에 할당되는 워커 수를 제한할 수 있어요. order로 그룹에서 다음으로 실행할 호스트를 Ansible이 어떻게 선택할지 제어할 수 있어요. run_once로 단일 호스트에서 작업을 실행할 수 있어요. 이 키워드들은 전략이 아니라, 플레이·블록·작업에 적용되는 지시문 또는 옵션이에요.

플레이 실행에 영향을 주는 다른 키워드로는 ignore_errors, ignore_unreachable, any_errors_fatal이 있어요. 이 옵션들은 플레이북에서의 오류 처리 (Error handling in playbooks)에 문서화되어 있어요.

serial로 배치 크기 설정하기

기본적으로 Ansible은 각 플레이의 hosts: 필드에 설정한 패턴의 모든 호스트에 대해 병렬로 실행돼요. 롤링 업데이트처럼 한 번에 몇 대의 머신만 관리하고 싶다면, serial 키워드로 Ansible이 한 번에 관리할 호스트 수를 정의할 수 있어요:

---
- name: test play
  hosts: webservers
  serial: 3
  gather_facts: False

  tasks:
    - name: first task
      command: hostname
    - name: second task
      command: hostname

위 예시에서 'webservers' 그룹에 6개의 호스트가 있다면, Ansible은 3개 호스트에서 플레이를 완전히(두 작업 모두) 실행한 뒤 다음 3개 호스트로 넘어가요:

PLAY [webservers] ***********************************************************************

TASK [first task] ***********************************************************************
changed: [web1]
changed: [web3]
changed: [web2]

TASK [second task] **********************************************************************
changed: [web1]
changed: [web2]
changed: [web3]

PLAY [webservers] ***********************************************************************

TASK [first task] ***********************************************************************
changed: [web4]
changed: [web5]
changed: [web6]

TASK [second task] **********************************************************************
changed: [web4]
changed: [web5]
changed: [web6]

PLAY RECAP ******************************************************************************
web1                       : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2                       : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web3                       : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web4                       : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web5                       : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web6                       : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

참고: serial로 배치 크기를 설정하면 Ansible 실패의 범위가 전체 호스트 목록이 아닌 배치 크기로 바뀌어요. 이 동작을 수정하려면 ignore_unreachable이나 max_fail_percentage를 사용할 수 있어요.

serial 키워드로 백분율을 지정할 수도 있어요. Ansible은 그 백분율을 플레이의 전체 호스트 수에 적용해서 한 번(pass)당 호스트 수를 정해요:

---
- name: test play
  hosts: webservers
  serial: "30%"

호스트 수가 패스 수로 균등하게 나누어 떨어지지 않으면, 마지막 패스가 나머지를 포함해요. 이 예시에서 webservers 그룹에 20개의 호스트가 있다면, 첫 배치에는 6개, 두 번째 배치에는 6개, 세 번째 배치에는 6개, 마지막 배치에는 2개의 호스트가 들어가요.

배치 크기를 목록으로도 지정할 수 있어요. 예:

---
- name: test play
  hosts: webservers
  serial:
    - 1
    - 5
    - 10

위 예시에서 첫 배치는 단일 호스트, 다음 배치는 5개 호스트를 담고, (남은 호스트가 있다면) 그 이후의 모든 배치는 10개 호스트 또는 남은 호스트가 10개 미만이라면 나머지 전부를 담아요.

여러 배치 크기를 백분율로 나열할 수도 있어요:

---
- name: test play
  hosts: webservers
  serial:
    - "10%"
    - "20%"
    - "100%"

값을 섞어 쓸 수도 있어요:

---
- name: test play
  hosts: webservers
  serial:
    - 1
    - 5
    - "20%"

참고: 백분율이 아무리 작아도 패스당 호스트 수는 항상 1 이상이에요.

throttle로 실행 제한하기

throttle 키워드는 특정 작업의 워커 수를 제한해요. 블록과 작업 레벨에서 설정할 수 있어요. CPU 집약적이거나 속도 제한이 있는 API와 상호작용하는 작업을 제한하려면 throttle을 사용하세요:

tasks:
- command: /path/to/cpu_intensive_command
  throttle: 1

이미 병렬로 실행할 fork 수나 머신 수를 제한했다면, throttle로 워커 수를 줄일 수는 있지만 늘릴 수는 없어요. 다시 말해 효과를 보려면, throttleforksserial과 함께 사용할 때 throttle 설정이 그보다 낮아야 해요.

인벤토리 기반 실행 순서 정하기

order 키워드는 호스트가 실행되는 순서를 제어해요. order의 가능한 값은 다음과 같아요:

  • inventory (기본값): 요청한 선택에 대해 인벤토리가 제공하는 순서 (아래 참고 사항 참조)
  • reverse_inventory: 위와 같지만 반환된 목록을 뒤집은 순서
  • sorted: 이름 기준 알파벳순 정렬
  • reverse_sorted: 이름 기준 역알파벳순 정렬
  • shuffle: 실행마다 무작위 순서

참고: 'inventory' 순서는 인벤토리 소스 파일에서 호스트/그룹이 정의된 순서와 같지 않아요. '컴파일된 인벤토리에서 선택이 반환되는 순서'를 뜻해요. 이는 하위 호환 옵션이며 재현 가능하지만 보통 예측 가능하진 않아요. 인벤토리, 호스트 패턴, limit, 인벤토리 플러그인, 다중 소스 허용의 특성상 그런 순서를 반환하는 것은 거의 불가능해요. 단순한 경우라면 파일 정의 순서와 우연히 일치할 수도 있지만 보장되지는 않아요.

run_once로 단일 머신에서 실행하기

작업을 호스트 배치의 첫 번째 호스트에서만 실행하고 싶다면, 그 작업에 run_once를 true로 설정하세요:

---
# ...

  tasks:

    # ...

    - command: /opt/application/upgrade_db.py
      run_once: true

    # ...

Ansible은 현재 배치의 첫 번째 호스트에서 이 작업을 실행하고, 모든 결과와 팩트를 같은 배치의 모든 호스트에 적용해요. 이 접근법은 작업에 다음과 같은 조건을 적용하는 것과 비슷해요:

- command: /opt/application/upgrade_db.py
  when: inventory_hostname == webservers[0]

하지만 run_once를 쓰면 결과가 모든 호스트에 적용돼요. 배치의 첫 번째 호스트 대신 특정 호스트에서 작업을 실행하려면 작업을 위임(delegate)하세요:

- command: /opt/application/upgrade_db.py
  run_once: true
  delegate_to: web01.example.org

위임 (delegation)에서 늘 그렇듯, 액션은 위임된 호스트에서 실행되지만 정보는 여전히 작업의 원래 호스트 것이에요.

참고: serial과 함께 사용하면 run_once로 표시된 작업은 serial 배치의 한 호스트에서 실행돼요. serial 모드와 무관하게 작업이 정확히 한 번만 실행되어야 한다면 when: inventory_hostname == ansible_play_hosts_all[0] 구조를 사용하세요.

참고: 어떤 조건(when:)이든 '첫 번째 호스트'의 변수를 사용해서 작업 실행 여부를 결정해요. 다른 호스트는 테스트되지 않아요.

참고: 모든 호스트에 팩트를 설정하는 기본 동작을 피하고 싶다면, 해당 작업이나 블록에 delegate_facts: True를 설정하세요.

더 알아보기 (Learn more)