플레이북 오류 처리
플레이북 오류 처리 (Error handling in playbooks)
기본적으로 Ansible은 명령이 0이 아닌 반환 코드를 주거나 모듈이 실패하면 그 호스트에서 실행을 멈추고 다른 호스트에서는 계속해요. 그런데 상황에 따라 다른 동작이 필요할 때가 있죠. 때로는 0이 아닌 반환 코드가 성공을 뜻하기도 하고, 한 호스트의 실패가 모든 호스트의 실행을 중단해야 하는 경우도 있습니다. 이 페이지에서는 Ansible이 제공하는 오류 처리 도구와 설정을 다룹니다.
출처: 문서
본문
Ansible이 명령에서 0이 아닌 반환 코드를 받거나 모듈에서 실패를 받으면, 기본적으로 그 호스트에서 실행을 멈추고 다른 호스트에서는 계속해요. 하지만 어떤 상황에서는 다른 동작을 원할 수 있습니다. 때로는 0이 아닌 반환 코드가 성공을 의미하기도 하고, 한 호스트의 실패가 모든 호스트의 실행을 중단하기를 원하기도 하죠. Ansible은 이런 상황을 처리하고 원하는 동작, 출력, 보고를 얻는 데 도움이 되는 도구와 설정을 제공합니다.
실패한 명령 무시하기 (Ignoring failed commands)
기본적으로 Ansible은 호스트에서 작업이 실패하면 그 호스트에서 작업 실행을 멈춰요. ignore_errors를 사용하면 실패에도 불구하고 계속할 수 있습니다.
- name: Do not count this as a failure
ansible.builtin.command: /bin/false
ignore_errors: true
ignore_errors 지시문은 작업이 실행될 수 있고 'failed' 값을 반환할 때만 동작해요. 정의되지 않은 변수 오류, 연결 실패, 실행 문제(예: 패키지 누락), 문법 오류를 무시하게 만들지는 않습니다.
도달 불가능한 호스트 오류 무시하기 (Ignoring unreachable host errors)
버전 2.7에서 추가되었어요.
ignore_unreachable 키워드로 호스트 인스턴스가 'UNREACHABLE'이어서 발생한 작업 실패를 무시할 수 있어요. Ansible은 작업 오류를 무시하지만 도달할 수 없는 호스트에 대해 이후 작업을 계속 실행합니다. 예를 들어 작업 레벨에서:
- name: This executes, fails, and the failure is ignored
ansible.builtin.command: /bin/true
ignore_unreachable: true
- name: This executes, fails, and ends the play for this host
ansible.builtin.command: /bin/true
그리고 플레이북 레벨에서:
- hosts: all
ignore_unreachable: true
tasks:
- name: This executes, fails, and the failure is ignored
ansible.builtin.command: /bin/true
- name: This executes, fails, and ends the play for this host
ansible.builtin.command: /bin/true
ignore_unreachable: false
도달 불가능한 호스트 재설정하기 (Resetting unreachable hosts)
Ansible이 호스트에 연결할 수 없으면 그 호스트를 'UNREACHABLE'로 표시하고 해당 실행의 활성 호스트 목록에서 제거해요. meta: clear_host_errors를 사용해 모든 호스트를 다시 활성화하면 이후 작업이 다시 연결을 시도할 수 있습니다.
핸들러와 실패 (Handlers and failure)
Ansible은 각 플레이의 끝에서 핸들러를 실행해요. 한 작업이 핸들러를 통지(notify)했는데 플레이의 나중에 다른 작업이 실패하면, 기본적으로 그 호스트에서는 핸들러가 실행되지 않아 호스트가 예상치 못한 상태로 남을 수 있습니다. 예를 들어 어떤 작업이 구성 파일을 업데이트하고 핸들러에게 서비스 재시작을 통지했다고 해봅시다. 같은 플레이의 나중에 작업이 실패하면 구성 파일은 바뀌지만 서비스는 재시작되지 않을 수 있어요.
--force-handlers 명령줄 옵션, 플레이에 force_handlers: True를 포함시키거나 ansible.cfg에 force_handlers = True를 추가해 이 동작을 바꿀 수 있어요. 핸들러가 강제되면 Ansible은 모든 호스트에서 통지된 모든 핸들러를 실행합니다. 작업이 실패한 호스트에서도요. (호스트가 unreachable이 되는 것 같은 특정 오류는 여전히 핸들러 실행을 막을 수 있음을 유의하세요.)
실패 정의하기 (Defining failure)
Ansible은 각 작업에서 failed_when 조건부로 "실패"가 무엇을 의미하는지 정의하게 해줘요. Ansible의 모든 조건부처럼 여러 failed_when 조건 목록은 암시적 and로 결합됩니다. 즉 작업은 모든 조건이 충족될 때만 실패합니다. 조건 중 하나라도 충족되면 실패를 트리거하려면, 조건을 명시적 or 연산자가 있는 단일 문자열로 정의해야 합니다.
예를 들어 두 조건 중 하나라도 참일 때 실패시키려면:
- name: Fail task when either condition is met
ansible.builtin.command: /usr/bin/example-command
register: command_result
failed_when: command_result.rc != 0 or 'ERROR' in command_result.stdout
명령 출력에서 단어나 구를 검색해 실패를 확인할 수도 있어요.
- name: Fail task when the command error output prints FAILED
ansible.builtin.command: /usr/bin/example-command -x -y -z
register: command_result
failed_when: "'FAILED' in command_result.stderr"
또는 반환 코드를 기준으로:
- name: Fail task when both files are identical
ansible.builtin.raw: diff foo/file1 bar/file2
register: diff_cmd
failed_when: diff_cmd.rc == 0 or diff_cmd.rc >= 2
여러 실패 조건을 결합할 수도 있어요. 두 조건이 모두 참이면 실패하는 작업입니다:
- name: Check if a file exists in temp and fail task if it does
ansible.builtin.command: ls /tmp/this_should_not_be_here
register: result
failed_when:
- result.rc == 0
- '"No such" not in result.stderr'
작업이 조건 하나만 충족돼도 실패하길 원한다면 failed_when 정의를 이렇게 바꾸세요:
failed_when: result.rc == 0 or "No such" not in result.stderr
한 줄에 담기에 조건이 너무 많으면 >로 여러 줄 YAML 값으로 나눌 수 있어요.
- name: example of many failed_when conditions with OR
ansible.builtin.shell: "./myBinary"
register: ret
failed_when: >
("No such file or directory" in ret.stdout) or
(ret.stderr != '') or
(ret.rc == 10)
변수를 등록하지 않고 암시적 변수 _task의 result 속성으로 작업 결과에 접근할 수도 있어요.
- name: Fail task when either condition is met
ansible.builtin.command: /usr/bin/example-command
failed_when: _task.result.rc != 0 or 'ERROR' in _task.result.stdout
참고 (Note)
_task암시적 변수는when,until,failed_when,changed_when,break_when을 포함한 모든 조건부 키워드에서 사용할 수 있어요.
"changed" 정의하기 (Defining "changed")
Ansible은 changed_when 조건부로 특정 작업이 원격 노드를 "변경"했는지 정의하게 해줘요. 이를 통해 반환 코드나 출력을 기반으로 변경이 Ansible 통계에 보고되어야 하는지, 그리고 핸들러가 트리거되어야 하는지 결정할 수 있습니다. Ansible의 모든 조건부처럼 여러 changed_when 조건 목록은 암시적 and로 결합됩니다. 즉 작업은 모든 조건이 충족될 때만 변경을 보고합니다. 조건 중 하나라도 충족되면 변경을 보고하려면 명시적 or 연산자가 있는 문자열로 정의해야 합니다. 예를 들면:
tasks:
- name: Report 'changed' when the return code is not equal to 2
ansible.builtin.shell: /usr/bin/billybass --mode="take me to the river"
register: bass_result
changed_when: "bass_result.rc != 2"
- name: This will never report 'changed' status
ansible.builtin.shell: wall 'beep'
changed_when: False
- name: This task will always report 'changed' status
ansible.builtin.command: /path/to/command
changed_when: True
여러 조건을 결합해 'changed' 결과를 덮어쓸 수도 있어요.
- name: Combine multiple conditions to override 'changed' result
ansible.builtin.command: /bin/fake_command
register: result
ignore_errors: True
changed_when:
- '"ERROR" in result.stderr'
- result.rc == 2
failed_when과 마찬가지로 암시적 변수 _task를 사용해 변수 등록을 피할 수 있어요:
- name: Combine multiple conditions to override 'changed' result
ansible.builtin.command: /bin/fake_command
changed_when:
- some_msg in _task.result.stdout
- some_warning not in _task.result.stderr
조건부에서 단순 변수를 참조해 특정 용어를 반복하지 않을 수도 있어요. 다음 예시처럼요:
- name: Example playbook
hosts: myHosts
vars:
log_path: /home/ansible/logfolder/
log_file: log.log
tasks:
- name: Create empty log file
ansible.builtin.shell: mkdir {{ log_path }} || touch {{ log_path }}{{ log_file }}
register: tmp
changed_when:
- tmp.rc == 0
- 'tmp.stderr != "mkdir: cannot create directory ‘" ~ log_path ~ "’: File exists"'
참고 (Note)
changed_when문에서log_path변수 주위에 이중 중괄호{{ }}가 없다는 점을 주목하세요.when처럼 이 두 조건부는 원시(raw) Jinja2 표현식이기 때문에 템플릿팅 구분자({{ }})가 필요하지 않아요. 여전히 사용하면 Ansible은 조건부 문에 jinja2 템플릿팅 구분자가 포함되어서는 안 된다고 경고합니다. 더 많은 조건부 문법 예시는 "실패 정의하기(Defining failure)"를 참고하세요.
command와 shell의 성공 보장하기 (Ensuring success for command and shell)
command와 shell 모듈은 반환 코드를 신경 씁니다. 그래서 성공 종료 코드가 0이 아닌 명령이 있다면 이렇게 할 수 있어요:
tasks:
- name: Run this command and ignore the result
ansible.builtin.shell: /usr/bin/somecommand || /bin/true
모든 호스트에서 플레이 중단하기 (Aborting a play on all hosts)
때로는 단일 호스트의 실패나 특정 비율의 호스트 실패가 모든 호스트의 전체 플레이를 중단하길 원할 수 있어요. any_errors_fatal로 첫 실패 이후 플레이 실행을 멈출 수 있습니다. 더 세밀하게 제어하려면 max_fail_percentage를 사용해 특정 비율의 호스트가 실패한 뒤 실행을 중단할 수 있어요.
첫 오류에서 중단하기: any_errors_fatal (Aborting on the first error)
any_errors_fatal을 설정하고 작업이 오류를 반환하면, Ansible은 현재 배치(batch)의 모든 호스트에서 해당 치명적인 작업을 마친 뒤 모든 호스트에서 플레이 실행을 중지해요. 이후 작업과 플레이는 실행되지 않습니다. 블록에 rescue 섹션을 추가해 치명적인 오류에서 복구할 수 있습니다. any_errors_fatal은 play 또는 block 레벨에서 설정할 수 있어요.
- hosts: somehosts
any_errors_fatal: true
roles:
- myrole
- hosts: somehosts
tasks:
- block:
- include_tasks: mytasks.yml
any_errors_fatal: true
모든 작업이 100% 성공해야 플레이북 실행을 계속할 수 있을 때 이 기능을 사용할 수 있어요. 예를 들어 로드 밸런서로 사용자 트래픽을 서비스로 전달하는 여러 데이터 센터의 머신에서 서비스를 운영한다면, 유지보수를 위해 서비스를 중지하기 전에 모든 로드 밸런서를 비활성화하길 원할 거예요. 로드 밸런서를 비활성화하는 작업에서 어떤 실패든 다른 모든 작업을 멈추도록 하려면:
---
- hosts: load_balancers_dc_a
any_errors_fatal: true
tasks:
- name: Shut down datacenter 'A'
ansible.builtin.command: /usr/bin/disable-dc
- hosts: frontends_dc_a
tasks:
- name: Stop service
ansible.builtin.command: /usr/bin/stop-software
- name: Update software
ansible.builtin.command: /usr/bin/upgrade-software
- hosts: load_balancers_dc_a
tasks:
- name: Start datacenter 'A'
ansible.builtin.command: /usr/bin/enable-dc
이 예시에서 Ansible은 모든 로드 밸런서가 성공적으로 비활성화된 경우에만 프론트 엔드에서 소프트웨어 업그레이드를 시작합니다.
최대 실패 비율 설정하기 (Setting a maximum failure percentage)
기본적으로 Ansible은 아직 실패하지 않은 호스트가 있는 한 작업을 계속 실행해요. 롤링 업데이트를 실행할 때 같은 상황에서는 특정 실패 임계값에 도달하면 플레이를 중단하고 싶을 수 있습니다. 이를 위해 플레이에 최대 실패 비율을 설정할 수 있어요:
---
- hosts: webservers
max_fail_percentage: 30
serial: 10
max_fail_percentage 설정은 serial과 함께 사용하면 각 배치에 적용돼요. 위 예시에서 첫 번째(또는 어떤) 서버 배치의 10대 중 3대 이상이 실패하면 나머지 플레이는 중단됩니다.
참고 (Note)
설정된 비율은 초과되어야 하지 같아서는 안 돼요. 예를 들어 serial이 4이고 시스템 2대가 실패하면 플레이를 중단시키려면 max_fail_percentage를 50이 아니라 49로 설정하세요.
블록에서 오류 제어하기 (Controlling errors in blocks)
블록을 사용해 작업 오류에 대한 응답을 정의할 수도 있어요. 이 방식은 많은 프로그래밍 언어의 예외 처리와 유사합니다. 자세한 내용과 예시는 "블록으로 오류 처리하기(Handling errors with blocks)"를 참고하세요.
더 보기 (See also)
- Ansible 플레이북 — 플레이북 소개.
- 일반 팁 — 플레이북을 위한 팁과 트릭.
- 조건문 (Conditionals) — 플레이북의 조건문.
- 변수 사용 (Using variables) — 변수에 대한 모든 것.
- 커뮤니케이션 — 질문이나 도움이 필요하거나 아이디어를 나누고 싶다면 Ansible 커뮤니케이션 안내서를 참고하세요.
더 알아보기 (Learn more)
failed_when/changed_when과 같은 조건부 작성법은 "조건문(playbooks_conditionals)" 페이지에서, 오류 응답 블록은 "블록(playbooks_blocks)" 페이지에서 이어서 배울 수 있어요.