예상치 못한 '변경됨' 상태 및 사실 수집 실패 해결

모듈, 핸들러, SSH 및 Python에 대한 실용적인 점검을 통해 Ansible의 잡음이 많은 변경 결과와 사실 수집 실패를 수정합니다.

예상치 못한 '변경됨' 상태 및 사실 수집 실패 해결

두 가지 Ansible 문제가 신뢰를 빠르게 손상시킵니다: 실제로 의미 있는 변경이 없었는데 changed를 보고하는 작업, 그리고 실제 작업이 시작되기도 전에 실패하는 사실 수집입니다. 첫 번째 문제는 모든 실행을 의심스럽게 만듭니다. 두 번째 문제는 운영 체제, 네트워크, 패키지 또는 하드웨어 사실에 의존하는 플레이북을 차단합니다. 실제 상태 변경과 잡음이 많은 작업을 분리하고 연결 실패와 설정 실패를 분리하면 두 문제 모두 해결할 수 있습니다.

이러한 문제의 근본 원인을 이해하는 것은 강력하고 안정적인 Ansible 자동화를 유지하는 데 중요합니다. 미묘한 파일 권한 문제, 의도치 않게 트리거된 핸들러, 또는 신뢰할 수 없는 조건문 등 예상치 못한 changed 상태 또는 실패한 사실 수집의 정확한 이유를 찾아내면 디버깅 시간을 크게 절약할 수 있습니다. 명확한 설명과 실행 가능한 예제를 통해 이러한 시나리오를 살펴보겠습니다.

Ansible의 '변경됨' 상태 이해

Ansible에서 작업이 시스템 상태를 수정하면 해당 작업은 changed로 보고됩니다. 이는 작업이 구성을 성공적으로 적용할 때 예상되는 동작입니다. 그러나 때로는 의도한 구성이 이미 적용되어 있거나 실제로 수정이 이루어지지 않았음에도 작업이 changed를 보고할 수 있습니다.

예상치 못한 '변경됨' 상태의 일반적인 원인

1. 멱등성 문제

Ansible 모듈은 멱등성을 가지도록 설계되었습니다. 즉, 여러 번 실행해도 한 번 실행한 것과 동일한 효과를 가져야 합니다. 모듈이 완벽하게 멱등성을 가지지 않거나 멱등성 검사를 우회하는 방식으로 사용되면 원하는 상태가 이미 달성되었음에도 변경을 보고할 수 있습니다. 이는 종종 모듈이 현재 상태와 원하는 상태를 확인하는 방식 때문입니다.

2. 파일 권한 및 소유권

Ansible 제어 노드 또는 관리 노드의 잘못된 파일 권한이나 소유권은 예상치 못한 변경으로 이어질 수 있습니다. 예를 들어, Ansible이 파일을 써야 하는데 필요한 쓰기 권한이 없으면 실패하고 오류를 보고할 수 있습니다. 반대로, Ansible이 파일의 존재를 확인했지만 메타데이터(수정 시간 또는 권한 등)가 템플릿과 일치하지 않으면 파일을 다시 적용하여 변경된 것으로 표시할 수 있습니다.

  • 예시: 구성 파일을 복사하는 플레이북을 생각해 보십시오. 관리 노드의 대상 파일에 대한 소유권이나 권한이 Ansible이 예상하는 것과 약간 다른 경우(예: 이전 수동 편집으로 인한 다른 타임스탬프 또는 다른 소유자), Ansible은 내용이 동일하더라도 변경을 보고할 수 있습니다.

    - name: 구성 파일이 있는지 확인
      copy:
        src: /path/to/local/config.conf
        dest: /etc/app/config.conf
        owner: appuser
        group: appgroup
        mode: '0644'
    

    /etc/app/config.conf가 올바른 내용으로 이미 존재하지만 권한이 약간 다른 경우(예: 0664), mode 매개변수가 일치하지 않기 때문에 Ansible은 changed로 보고합니다. 이를 방지하려면 mode 매개변수가 원하는 상태를 정확하게 반영하는지 확인하거나 콘텐츠를 더 잘 인식하는 모듈 사용을 고려하십시오.

3. 의도치 않게 트리거된 핸들러

핸들러는 일반적으로 변경이 발생할 때 다른 작업에 의해 알림을 받을 때만 실행되는 특수 작업입니다. changed를 잘못 보고하는 작업에 의해 핸들러가 알림을 받으면 핸들러도 실행되어 추가적인 의도하지 않은 변경이나 작업을 유발할 수 있습니다. 이는 보고된 변경의 연쇄 효과를 만들 수 있습니다.

  • 예시: 위에 표시된 copy 작업이 사소한 권한 차이로 인해 changed를 잘못 보고하고 이 작업이 서비스를 다시 시작하도록 핸들러에 알리면 구성 파일 내용이 실제로 변경되지 않았더라도 서비스가 다시 시작됩니다.

    - name: 웹 서버 다시 시작
      service:
        name: nginx
        state: restarted
      listen: "웹 서버 재시작 알림"
    

    그리고 copy 작업은 다음과 같이 알립니다:

    - name: 구성 파일이 있는지 확인
      copy:
        src: /path/to/local/config.conf
        dest: /etc/app/config.conf
      notify: "웹 서버 재시작 알림"
    

    팁: 어떤 작업이 핸들러에 알리는지 신중하게 검토하고 알림 작업이 의미 있는 구성 수정이 발생한 경우에만 changed를 보고하는지 확인하십시오. 작업이 변경을 보고해서는 안 된다는 것을 알고 있으면 changed_when: false를 현명하게 사용하거나 모듈 매개변수를 조정하여 멱등성을 개선하십시오.

4. 신뢰할 수 없는 조건부 로직

조건문(when: 절)은 강력하지만 신중하게 구성하지 않으면 예상치 못한 동작으로 이어질 수 있습니다. 조건이 잘못 평가되거나 불안정한 사실에 기반하는 경우 작업이 실행되지 않아야 할 때 실행되거나 실행되어야 할 때 실행되지 않아 changed 상태 또는 실제 구성을 위한 기회를 놓칠 수 있습니다.

  • 예시: 항상 존재하거나 일관적이지 않을 수 있는 사실에 의존하면 문제가 발생할 수 있습니다.

    - name: 기능이 활성화된 경우 애플리케이션 구성
      lineinfile:
        path: /etc/app/settings.conf
        line: "FEATURE_ENABLED=true"
      when: ansible_facts['some_custom_fact'] == "enabled"
    

    some_custom_fact가 때때로 누락되거나 값이 약간 다른 경우(예: enabled 대신 Enabled), when 조건이 예기치 않게 실패하거나 작업이 실행되지 않아야 할 때 실행될 수 있습니다. 항상 조건과 그 조건이 의존하는 사실을 검증하십시오.

    팁: debug: 작업을 사용하여 when 조건에 사용된 사실 및 변수의 값을 출력하여 플레이북 실행 중 상태를 확인하십시오.

사실 수집 실패 문제 해결

Ansible의 사실 수집은 Ansible이 관리 노드에 대한 정보(IP 주소, 운영 체제, 메모리, 디스크 공간 등)를 수집하는 프로세스입니다. 이러한 사실은 플레이북에서 사용할 수 있습니다. 사실 수집 실패는 플레이북이 올바르게 실행되거나 필수 정보를 사용하지 못하게 할 수 있습니다.

사실 수집 실패의 일반적인 원인

1. 연결 문제

기본적으로 사실은 SSH(Linux/Unix) 또는 WinRM(Windows)을 통해 수집됩니다. Ansible이 관리 노드에 대한 연결을 설정할 수 없으면 사실을 수집할 수 없습니다. 이는 종종 사실 수집 실패의 가장 직접적인 원인입니다.

  • 증상: 플레이북이 중단되거나 연결 관련 오류(예: ssh: connect to host ... port 22: Connection refused, timeout, Authentication failed)와 함께 즉시 실패합니다.
  • 해결 방법: SSH/WinRM 연결을 확인하고, 올바른 ansible_user, ansible_ssh_private_key_file 및 기타 연결 매개변수가 인벤토리 또는 ansible.cfg에 올바르게 설정되었는지 확인하십시오. 방화벽 규칙을 확인하십시오.

2. 관리 노드의 권한 부족

Ansible이 사실을 수집하려면 Ansible이 연결하는 사용자에게 관리 노드에 대한 적절한 권한이 있어야 합니다. 이는 일반적으로 특정 명령을 실행하고 특정 디렉터리에 액세스할 수 있어야 함을 의미합니다.

  • 증상: 사실 수집이 부분적으로 완료되거나 uname, df, lsblk와 같은 명령을 실행하거나 /proc 파일 시스템 항목에 액세스하려고 할 때 권한 거부 오류와 함께 실패할 수 있습니다.

  • 해결 방법: 연결 사용자에게 (특정 명령에 필요한 경우) 암호 없이 sudo 권한이 있거나 사용자가 필요한 시스템 정보에 직접 읽기 액세스 권한이 있는지 확인하십시오.

    # 사실 수집을 위해 sudo를 사용할 수 있도록 하는 방법의 예
    - name: 사실 수집
      setup:
      # 특정 명령에 sudo가 필요한 경우 사용자에게 암호 없는 sudo가 설정되어 있는지 확인하십시오.
    

    팁: 사실 수집 중 권한 상승을 위해 Ansible은 종종 become 지시문에 의존합니다. 연결 사용자가 사실 수집을 위한 명령을 실행하기 위해 상승된 권한이 필요한 경우 플레이북 또는 인벤토리에서 become: yesbecome_method: sudo(또는 이에 상응하는 것)를 구성하십시오. become_user(종종 root)에게 필요한 권한이 있는지 확인하십시오.

3. 호환되지 않는 Python 인터프리터

사실 수집에 사용되는 setup 모듈을 포함한 Ansible 모듈은 종종 관리 노드의 Python 인터프리터에 의존합니다. 기본 Python 인터프리터가 호환되지 않거나(예: Ansible이 Python 2를 예상하는데 Python 3이거나 그 반대인 경우, Ansible 버전 및 모듈 요구 사항에 따라 다름) 누락된 경우 사실 수집이 실패할 수 있습니다.

  • 증상: Python 실행 관련 오류, ImportError 또는 사실 수집 중 모듈 실패.

  • 해결 방법: 인벤토리 또는 ansible.cfg에서 ansible_python_interpreter를 사용하여 올바른 Python 인터프리터를 지정하십시오. 관리 노드에 호환되는 Python 버전이 설치되어 있는지 확인하십시오.

    # 인벤토리 파일 예시
    [my_servers]
    server1.example.com ansible_python_interpreter=/usr/bin/python3
    server2.example.com ansible_python_interpreter=/usr/bin/python2.7
    

4. 손상되었거나 누락된 /etc/ansible/facts.d 디렉터리

Ansible은 관리 노드의 /etc/ansible/facts.d 디렉터리에 있는 파일에서 사용자 정의 사실을 수집할 수도 있습니다. 이 디렉터리나 그 내용이 손상되었거나 액세스할 수 없으면 사실 수집 프로세스를 방해할 수 있지만, 표준 사실 수집의 경우 일반적이지 않습니다.

  • 증상: /etc/ansible/facts.d 관련 문제를 구체적으로 언급하는 오류.
  • 해결 방법: 관리 노드에서 /etc/ansible/facts.d의 권한과 내용을 확인하십시오. 디렉터리인지 Ansible이 읽기 권한을 가지고 있는지 확인하십시오.

5. gather_facts: no 또는 gather_subset 제한

일부 플레이북에서는 실행 속도를 높이기 위해 gather_factsno로 설정되거나 수집된 사실을 제한하기 위해 gather_subset이 사용될 수 있습니다. 그런 다음 수집되지 않은 사실을 사용하려고 하면 실패로 나타납니다.

  • 증상: 사실에 액세스할 때 정의되지 않은 변수 또는 AttributeError: 'dict' object has no attribute '...'와 같은 오류.

  • 해결 방법: 플레이에 대해 gather_facts: yes(또는 기본 동작)가 활성화되어 있는지 확인하거나 사용하려는 사실의 하위 집합을 명시적으로 활성화하십시오. gather_facts: no가 의도적이라면 사실을 사용하지 않거나 수동으로 정의해야 합니다.

    - name: 내 플레이
      hosts: all
      gather_facts: yes # 또는 이 줄을 생략하여 기본값(yes)을 사용합니다.
      tasks:
        - name: OS 제품군 표시
          debug:
            msg: "{{ ansible_os_family }}에서 실행 중"
    

    사실의 하위 집합만 필요한 경우 작업에서 setup 모듈을 사용하여 최적화할 수 있습니다:

    - name: 사실에 최적화된 내 플레이
      hosts: all
      gather_facts: false
      tasks:
        - name: 네트워크 사실만 수집
          ansible.builtin.setup:
            gather_subset:
              - '!all'
              - network
    
        - name: 네트워크 인터페이스 표시
          debug:
            msg: "인터페이스: {{ ansible_interfaces }}"
    

실용적인 분류 경로

플레이북에 잡음이 많을 때는 하나의 호스트와 하나의 의심스러운 작업부터 시작하십시오. 전체 인벤토리에서 전체 플레이를 실행하면 출력을 읽기가 더 어려워지고 테스트하려는 의도가 없었던 핸들러가 트리거될 수 있습니다.

ansible-playbook -i inventory.ini site.yml --limit app01.example.com --check --diff

--diff는 파일 작업에 특히 유용합니다. 템플릿 또는 복사 작업이 changed를 보고하면 diff는 종종 내용이 변경되었는지, 모드가 변경되었는지, 또는 생성된 타임스탬프만 변경되었는지 알려줍니다. 생성된 타임스탬프는 잘못된 변경의 전형적인 원인입니다:

# {{ ansible_date_time.iso8601 }}에 생성됨

해당 줄은 렌더링된 파일이 실행할 때마다 다르다는 것을 보장합니다. 애플리케이션에 타임스탬프가 필요하지 않으면 제거하십시오. 사람이 파일이 관리되고 있음을 알아야 하는 경우 안정적인 주석을 사용하십시오:

# Ansible에 의해 관리됨. 로컬 편집은 덮어쓸 수 있습니다.

명령 및 셸 작업의 경우 반대가 증명될 때까지 멱등성이 없다고 가정하십시오. 다음과 같은 작업은 일반적으로 매번 변경되었다고 보고합니다:

- name: 애플리케이션 캐시 재구축
  ansible.builtin.command: /opt/app/bin/rebuild-cache

명령이 검사일 뿐인 경우 정직하게 표시하십시오:

- name: 애플리케이션 캐시 상태 확인
  ansible.builtin.command: /opt/app/bin/cache-status
  register: cache_status
  changed_when: false

명령이 파일이 없을 때만 실행되어야 하는 경우 creates를 사용하십시오:

- name: 애플리케이션 데이터베이스 초기화
  ansible.builtin.command:
    cmd: /opt/app/bin/init-db
    creates: /var/lib/app/.db_initialized

파일이 있을 때만 실행되어야 하는 경우 removes를 사용하십시오. 이러한 가드는 changed_when: false보다 불필요한 실행도 방지하기 때문에 더 좋습니다.

핸들러도 동일한 규율이 필요합니다. 재시작 핸들러는 서비스의 유효 구성을 변경하는 작업에 의해 알림을 받아야 하며, 디렉터리를 건드리는 관련 없는 작업에 의해 알림을 받아서는 안 됩니다. 역할이 매 실행마다 Nginx를 다시 시작하는 경우 --diff로 각 알림 작업을 검사하십시오. 잡음이 많은 작업은 종종 불안정한 공백, 파일 모드 불일치 또는 항상 변경되었다고 보고하는 명령 작업이 있는 템플릿입니다.

사실 수집 실패는 연결 테스트와 사실 테스트를 분리하면 더 쉽습니다:

ansible app01.example.com -i inventory.ini -m ping
ansible app01.example.com -i inventory.ini -m setup -a "filter=ansible_distribution*"

ping이 실패하면 연결, 인증, 권한 또는 Python 부트스트랩 문제가 있는 것입니다. ping은 작동하지만 setup이 실패하면 문제는 사실 수집에 있을 가능성이 더 높습니다: 명령 누락, 제한된 권한, 손상된 Python 인터프리터 또는 문제가 있는 사용자 정의 사실.

최소 Linux 이미지에서 Python이 없거나 Ansible이 자동으로 감지하지 못하는 위치에 설치될 수 있습니다. ansible_python_interpreter를 명시적으로 설정하십시오:

[app]
app01.example.com ansible_python_interpreter=/usr/bin/python3

진정으로 Python 2.7이 필요한 오래된 시스템을 관리하지 않는 한 /usr/bin/python2.7을 하드 코딩하지 마십시오. 대부분의 최신 Linux 배포판은 Ansible 모듈 실행에 Python 3을 사용합니다.

사용자 정의 사실은 설정 중에 실행되므로 예기치 않은 방식으로 실패할 수 있습니다. 관리 호스트에서 직접 확인하십시오:

sudo find /etc/ansible/facts.d -maxdepth 1 -type f -ls
sudo /etc/ansible/facts.d/example.fact

실행 가능한 .fact 파일은 유효한 JSON 또는 INI 스타일 데이터를 반환해야 합니다. JSON 앞에 경고를 출력하는 스크립트는 구문 분석을 깨뜨릴 수 있습니다. 내부 서비스를 호출하는 동안 중단되는 스크립트는 사실 수집을 SSH 시간 초과처럼 보이게 만들 수 있습니다.

사실 수집이 느리지만 중단되지는 않는 경우 모든 곳에서 사실을 비활성화하는 대신 범위를 줄이십시오. 플레이 수준에서 자동 수집을 비활성화하고 필요한 곳에서만 하위 집합 또는 필터와 함께 setup을 호출하십시오. 이렇게 하면 이후 작업이 정직해집니다: 플레이가 수집하지 않은 사실에 실수로 의존할 수 없습니다.

목표는 모든 실행이 changed=0을 표시하도록 강제하는 것이 아닙니다. 일부 변경은 실제입니다. 목표는 신뢰입니다. Ansible이 변경되었다고 말할 때 변경된 파일, 서비스, 패키지 또는 명령 결과를 가리킬 수 있어야 합니다. 사실 수집이 실패하면 Ansible이 연결할 수 없었는지, Python을 실행할 수 없었는지, 시스템 데이터를 읽을 수 없었는지, 사용자 정의 사실을 구문 분석할 수 없었는지 알아야 합니다.