실패한 Shell 및 Command 모듈 디버깅을 위한 실용 가이드

register, stdout, stderr, rc, failed_when, changed_when 예제를 사용하여 Ansible shell 및 command 실패를 디버깅합니다.

실패한 Shell 및 Command 모듈 디버깅을 위한 실용 가이드

Ansible commandshell 모듈은 전용 모듈이 없을 때 유용하지만, 디버깅이 까다로울 수 있습니다. 실패한 태스크는 직접 명령 출력을 캡처하지 않으면 반환 코드만 표시될 수 있습니다.

이 가이드에서는 rc, stdout, stderr를 확인한 다음 failed_whenchanged_when을 사용하여 Ansible이 실제 결과를 보고하도록 하는 방법을 통해 실패한 shell 및 command 모듈을 디버깅하는 방법을 보여줍니다.


Command vs. Shell: 차이점 이해

디버깅에 들어가기 전에 두 모듈의 근본적인 차이점을 이해하는 것이 중요합니다. 실행 환경이 실패 모드에 영향을 미치기 때문입니다.

ansible.builtin.command

이 모듈은 표준 셸 환경을 거치지 않고 명령을 직접 실행합니다. 변수 보간, 글로빙, 파이프(|), 리디렉션(>)과 같은 셸 기능을 피하므로 더 안전하고 예측 가능합니다.

모범 사례: 태스크가 간단하고 셸 기능이 필요하지 않은 경우 command를 사용하십시오.

ansible.builtin.shell

이 모듈은 원격 호스트의 표준 셸(/bin/sh 또는 이와 동등한 것)을 통해 명령을 실행합니다. 이는 복잡한 작업, 환경 변수 또는 표준 셸 구문(예: cd /tmp && ls -l)을 사용해야 할 때 필요합니다.

경고: shell은 환경에 의존하기 때문에 PATH 구성, 숨겨진 환경 변수 또는 복잡한 따옴표 처리와 관련된 예측 불가능한 실패가 발생하기 쉽습니다.

Ansible 명령 실패의 구조

기본적으로 Ansible은 프로세스의 **반환 코드(RC)**를 기반으로 command 또는 shell 모듈 태스크의 성공 또는 실패를 결정합니다.

반환 코드(RC) 해석
rc = 0 성공 (태스크 계속)
rc != 0 실패 (태스크 즉시 중지, 호스트 실패로 표시)

그러나 이 간단한 검사는 실제 스크립트의 미묘한 차이를 포착하지 못하는 경우가 많습니다. 명령이 RC 0을 반환하지만 원치 않는 결과(논리적 실패)를 생성하거나 명령이 예상된 0이 아닌 RC(예: grep이 일치하는 항목을 찾지 못하면 1을 반환)를 반환할 수 있습니다.

이러한 미묘한 차이를 처리하려면 출력을 캡처하고 조건부로 실패 상태를 제어해야 합니다.

1단계: register로 명령 출력 캡처

효과적인 디버깅의 첫 번째 단계는 register 키워드를 사용하여 사용 가능한 모든 출력 스트림을 Ansible 변수에 캡처하는 것입니다. 이를 통해 반환 코드, 표준 출력 및 표준 오류를 검사할 수 있습니다.

초기 테스트 중에 0이 아닌 반환 코드가 발생해도 플레이북이 즉시 중단되지 않도록 하려면 ignore_errors: yes를 임시로 사용하는 것이 좋습니다.

- name: 잠재적으로 신뢰할 수 없는 명령을 실행하고 결과 캡처
  ansible.builtin.shell: | 
    /usr/local/bin/check_config.sh 2>&1 || exit 1
  register: cmd_output
  ignore_errors: yes  # RC != 0이 계속 진행되도록 임시 허용

등록되면 cmd_output 변수에는 여러 유용한 키가 포함되며, 가장 중요한 것은 다음과 같습니다.

  • cmd_output.rc: 정수 반환 코드.
  • cmd_output.stdout: 표준 출력 스트림.
  • cmd_output.stderr: 표준 오류 스트림.
  • cmd_output.failed: Ansible이 현재 태스크를 실패한 것으로 간주하는지 여부를 나타내는 부울 값입니다.

2단계: debug로 캡처된 데이터 검사

실패한 태스크 직후에 debug 모듈을 사용하여 등록된 변수의 내용을 검사합니다. 이는 실제 기술적 실패(예: 명령을 찾을 수 없음)와 논리적 실패(예: 스크립트가 실행되었지만 내부 오류 보고)를 구별하는 데 도움이 됩니다.

- name: 디버깅을 위해 캡처된 전체 출력 표시
  ansible.builtin.debug:
    var: cmd_output
    # 'when'을 사용하여 태스크가 실패한 경우에만 표시하여 출력 정리
  when: cmd_output.failed is defined and cmd_output.failed

- name: stderr 내용 강조 표시
  ansible.builtin.debug:
    msg: "캡처된 STDERR: {{ cmd_output.stderr }}"
  when: cmd_output.stderr | length > 0

전체 출력을 검사하면 실제 실패를 나타내는 특정 오류 메시지나 패턴을 정확히 찾아낼 수 있습니다.

3단계: failed_when으로 기본 실패 동작 재정의

failed_when 조건문은 복잡한 셸 모듈 결과를 디버깅하고 관리하기 위한 가장 강력한 도구입니다. 기본 반환 코드에 관계없이 Jinja2 표현식을 사용하여 태스크를 실패로 표시해야 하는지 여부를 결정하는 사용자 정의 논리를 정의할 수 있습니다.

시나리오 A: 예상된 0이 아닌 반환 코드 처리

일부 유틸리티는 예상된 결과에 대해 0이 아닌 코드를 반환합니다. 예를 들어 grep은 일치하는 항목이 없으면 1을 반환하고 실제 오류에 대해서는 1보다 큰 값을 반환합니다.

- name: 설정이 있는지 확인하지만 없을 때 실패하지 않음
  ansible.builtin.command: grep -q '^feature_enabled=true' /etc/myapp.conf
  register: grep_result
  failed_when: grep_result.rc > 1
  changed_when: false

시나리오 B: 논리적 오류 시 실패(RC=0이지만 잘못된 출력)

스크립트가 내부 오류가 발생해도 항상 RC=0을 반환하지만 stdout 또는 stderr에 특정 오류 문자열을 출력하는 경우 failed_when을 사용하여 해당 문자열을 포착합니다.

- name: 데이터베이스 연결 스크립트 검증
  ansible.builtin.shell: /opt/scripts/db_connect_test.sh
  register: db_result
  # 일반적인 오류 문구에 대해 stdout과 stderr를 모두 확인
  failed_when: >
    ('Connection refused' in db_result.stderr) or
    ('Authentication failure' in db_result.stdout)

시나리오 C: RC 및 출력 검사 결합

강력한 검사를 위해 논리 연산자(and, or, 괄호)를 사용하여 반환 코드와 내용 검사를 결합합니다.

- name: 배포 로그 확인
  ansible.builtin.shell: tail -n 50 /var/log/deployment.log
  register: log_check
  # RC가 0이 아니거나 성공 출력에 'FATAL'이라는 단어가 포함된 경우 실패
  failed_when: log_check.rc != 0 or 'FATAL' in log_check.stdout

팁: failed_when을 사용할 때는 일반적으로 ignore_errors: yes를 제거해야 합니다. 실패를 기록하면서 플레이를 계속 진행하려는 경우가 아니라면 말입니다.

안정적인 명령 실행을 위한 모범 사례

복잡한 디버깅의 필요성을 최소화하려면 command 또는 shell을 사용하는 태스크를 작성할 때 다음 표준을 따르십시오.

1. 항상 절대 경로 사용

원격 사용자의 $PATH에 의존하지 마십시오. 항상 실행 파일의 전체 경로를 지정하십시오(예: python이 아닌 /usr/bin/python). 이는 일관되지 않은 환경이나 실행 경로의 미묘한 차이로 인한 실패를 방지합니다.

2. 셸 로직보다 조건문 활용

shell 모듈 내에서 || 또는 &&와 같은 복잡한 셸 로직을 사용하는 대신 Ansible의 기본 조건문(when:, failed_when:, changed_when:)과 register 키워드를 활용하십시오. 이렇게 하면 플레이북 로직이 투명해지고 디버깅이 더 쉬워집니다.

3. 변경 감지 명시적 제어(changed_when)

기본적으로 commandshell은 반환 코드가 0이면 태스크를 changed로 표시합니다. 스크립트가 실행되지만 시스템에 변경 사항이 없는 경우(예: 간단한 상태 확인) changed_when을 사용하여 태스크가 변경을 초래하는 시기를 수동으로 정의해야 합니다.

- name: 디스크 공간 확인('changed'가 발생하지 않아야 함)
  ansible.builtin.command: df -h /data
  changed_when: false

4. 가능하면 상태 모듈 사용

파일 존재 확인, 서비스 시작/중지 또는 패키지 설치를 위해 shell을 사용하고 있다면 중단하고 전용 Ansible 모듈(예: ansible.builtin.stat, ansible.builtin.service, ansible.builtin.package)을 찾으십시오. 전용 모듈은 내부적으로 멱등성과 오류 검사를 처리하므로 디버깅 노력이 크게 줄어듭니다.

최종 요점

셸 또는 명령 태스크가 실패하면 먼저 결과를 캡처하고 rc, stdout, stderr를 검사한 다음 failed_when에 실제 성공 조건을 인코딩하십시오. 태스크가 안정화되면 changed_when을 추가하여 상태 검사가 모든 플레이북 실행에서 잘못된 변경 사항을 표시하지 않도록 하십시오.