SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor

Ansible 연결할 수 없는 호스트 무시하기

Ansible에서 unreachable 상태인 호스트를 처리하는 방법을 설명합니다. ignore_unreachable 설정과 함께 serial, max_fail_percentage를 사용하여 연결 실패 시에도 플레이를 안정적으로 계속 진행하는 구체적인 가이드를 확인하십시오.

연결할 수 없는 호스트는 실패한 작업이 아닙니다

Ansible에서 연결할 수 없는 호스트를 무시하려면 ignore_unreachable: true를 설정하며, 이 스위치는 정상적으로 작동합니다. 중요한 점은 이를 언제 사용해야 하는지 파악하는 것입니다. Ansible은 두 가지 서로 다른 문제를 각기 다른 방식으로 처리하기 때문입니다. 호스트에서 실행되었으나 오류를 반환한 작업은 실패(failure)로 간주합니다. 반면 Ansible이 호스트에 전혀 연결하지 못한 경우는 연결 불가(unreachable) 상태입니다. ignore_errors은 첫 번째 경우만 다룹니다. ignore_unreachable은 두 번째 경우만 다룹니다.

플레이 요약(play recap)에서의 차이점은 다음과 같습니다.

PLAY RECAP *********************************************************************
web1  : ok=7  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2  : ok=0  changed=0  unreachable=1  failed=0  skipped=0  rescued=0  ignored=0

Ansible은 web1에 연결하여 7개의 작업을 실행했습니다. web2unreachable=1failed=0를 보여주는데, 이는 해당 호스트에서 아무런 작업도 실행되지 않았음을 의미합니다. Ansible은 연결을 확보하지 못했으므로 해당 호스트를 플레이에서 제외하고 나머지를 계속 진행했습니다. 만약 해당 플레이가 보안 업데이트를 설치하는 작업이었다면, 서버 중 하나에는 업데이트가 적용되지 않은 상태가 됩니다.

호스트에 연결할 수 없는 이유

연결할 수 없다는 것은 어떤 모듈도 호스트에 도달하기 전에 연결이 실패했음을 의미합니다. 읽을 수 있는 모듈 출력은 없으며 연결 오류만 발생합니다. 이 오류는 해당 머신에 접근하는 첫 번째 작업에서 나타납니다.

fatal: [web2]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.20 port 22: Connection refused", "unreachable": true}

msg 필드에 실제 원인이 표시됩니다. 자주 접하게 되는 원인은 다음과 같습니다.

  • Connection refused: TCP 연결이 거부되었습니다. 해당 포트에서 수신 대기 중인 서비스가 없습니다. sshd가 중지되었거나, SSH 포트가 변경되었는데 인벤토리에는 여전히 22번 포트로 설정된 경우입니다.
  • Connection timed out: 아무런 응답이 없습니다. 방화벽이 패킷을 차단하고 있거나 서버가 꺼져 있는 상태입니다. 각 시도마다 기본값인 10초의 전체 연결 타임아웃 시간이 소요됩니다.
  • Host key verification failed.: ~/.ssh/known_hosts에 있는 키가 서버가 제시한 키와 일치하지 않습니다. VPS를 재설치하면 IP 주소는 유지되지만 호스트 키가 변경되므로, 재설치 후에는 예상되는 현상입니다. 그 외의 경우에는 심각한 보안 문제입니다.
  • Permission denied (publickey): SSH는 응답했으나 키를 거부했습니다. 포트는 정상이며 인증 문제입니다. 주로 잘못된 ansible_user를 사용했거나 키가 로드되지 않은 경우입니다.
  • Timeout (12s) waiting for privilege escalation prompt: 연결은 성공했으나 become가 실패했습니다. sudo가 비밀번호를 기다리고 있지만 입력이 전달되지 않는 상황입니다.

Python 인터프리터가 없는 경우를 이 목록에 포함하곤 하지만, 이는 해당하지 않습니다. SSH 연결이 성공했다면 호스트는 연결 가능한 상태입니다. 다만 모듈을 실행할 환경이 없는 것입니다.

fatal: [db1]: FAILED! => {"changed": false, "module_stdout": "/bin/sh: 1: /usr/bin/python3: not found\r\n", "msg": "The module failed to execute correctly, you probably need to set the interpreter", "rc": 127}

해당 줄은 FAILED!를 나타내며 요약 결과에서는 failed으로 집계됩니다. 따라서 ignore_unreachable은 이 작업을 수행하지 않습니다. 해당 호스트에 ansible_python_interpreter을 설정하거나 python3를 설치하십시오.

플레이에서 연결할 수 없는 호스트를 무시하는 방법

태스크 수준에서는 모듈 옆에 키워드를 배치합니다:

- name: Read the package list, and do not stop if the host is down
  ansible.builtin.command: dpkg -l
  register: packages
  changed_when: false
  ignore_unreachable: true

플레이 수준에서는 플레이 내 모든 태스크의 기본값이 설정되며, 개별 태스크에서 이를 다시 설정할 수 있습니다:

- name: Opportunistic fleet maintenance
  hosts: all
  ignore_unreachable: true
  tasks:
    - name: This runs, cannot connect, and the play carries on
      ansible.builtin.ping:

    - name: This one still ends the play for a host that is down
      ansible.builtin.ping:
      ignore_unreachable: false

내부적으로 어떤 변화가 일어나는지 아는 것이 중요합니다. ignore_unreachable가 설정되면 호스트가 플레이에서 제거되지 않으므로, 이후의 모든 태스크가 다시 연결을 시도하고 동일한 방식으로 실패합니다. 각 시도는 연결 타임아웃 시간만큼 대기하는데, ansible.cfg에서 timeout을 변경하지 않았다면 기본값은 10초입니다. 죽은 서버 하나를 대상으로 20개의 태스크를 실행하면 전체 실행 시간에 약 200초가 추가되고 로그에는 20개의 빨간 줄이 남습니다.

따라서 한 번 확인한 뒤 해당 호스트를 깔끔하게 중단하십시오:

- name: Opportunistic fleet maintenance
  hosts: all
  gather_facts: false
  tasks:
    - name: Check that the host answers before doing any work
      ansible.builtin.ping:
      register: reachable
      ignore_unreachable: true

    - name: End the play for this host if it never answered
      ansible.builtin.meta: end_host
      when: reachable.unreachable | default(false)

    - name: Gather facts now that the connection is known good
      ansible.builtin.setup:

    - name: Refresh the package index
      ansible.builtin.apt:
        update_cache: true
      become: true

이렇게 하면 태스크당 한 번이 아니라 죽은 호스트당 한 번만 연결을 시도합니다. Ansible 2.8에 추가된 end_host는 현재 호스트에 대한 플레이를 실패로 표시하지 않고 종료합니다. unreachable 키는 연결이 실패했을 때만 등록된 결과에 존재하므로, default(false)를 사용하면 응답한 모든 호스트에서 조건이 유효하게 유지됩니다. 플레이 수준에서 팩트 수집(fact gathering)을 끄는 이유는, 그렇지 않으면 암시적인 Gathering Facts 태스크가 끊어진 연결을 만나는 첫 번째 태스크가 되기 때문이며, 사용자는 직접 작성한 ping 태스크가 그 역할을 하길 원하기 때문입니다.

ignore_unreachable은 플레이 키워드이자 태스크 키워드입니다. 이 키워드는 실행 시 어떤 호스트를 건너뛰어도 되는지 결정하므로, 역할(role) 내부보다는 읽는 사람이 확인할 수 있도록 플레이북에 유지하십시오. 플레이북과 역할의 구분에서 이러한 설정을 어느 계층에서 관리해야 하는지 다룹니다.

ignore_errors가 이 상황에서 부적절한 도구인 이유

Ansible 문서는 이 기능의 한계를 명확히 밝히고 있습니다. ignore_errors "이 기능은 작업이 실행되어 'failed' 값을 반환할 때만 작동합니다. 정의되지 않은 변수 오류, 연결 실패, 실행 문제(예: 패키지 누락) 또는 구문 오류는 무시하지 않습니다."

연결 실패는 failed: true을 사용해도 작업 결과로 처리되지 않습니다. 이는 별도의 플래그로 전달되며, Ansible은 이 플래그를 우선적으로 처리합니다. 즉, 해당 호스트는 unreachable 목록으로 이동하여 플레이에서 제외됩니다. 플레이의 12개 작업 전체에 ignore_errors: true를 설정하더라도, SSH 포트가 닫힌 호스트는 첫 번째 작업에서 즉시 중단됩니다. 이는 이 분야에서 가장 흔히 발생하는 혼동이며, 특히 VPS를 대상으로 첫 플레이북을 작성하며 배울 때 작성했던 구형 플레이북에서 grep을 통해 점검해 볼 가치가 있습니다.

억제하기 전에 디버깅하기

영구적인 억제는 서버 관리의 부실로 이어집니다. 접근할 수 없는 호스트는 패치도 이루어지지 않기 때문입니다. 먼저 다음 순서대로 문제를 해결하십시오. 아래의 모든 명령어는 읽기 전용입니다.

  1. ansible web2 -i inventory.ini -m ansible.builtin.ping -o은 하나의 호스트에 대해 하나의 모듈을 실행하고 한 줄의 결과를 출력합니다.
  2. 동일한 명령어에 -vvvv을 추가하십시오. Ansible은 대상 사용자, 포트, 개인 키, 전달하는 옵션을 포함하여 구성된 전체 ssh 명령어를 출력합니다.
  3. -v를 사용하여 해당 ssh 명령어를 직접 실행해 보십시오. 일반 ssh로 접속할 수 없다면 문제는 Ansible 하위 계층에 있는 것이며, 어떤 플레이북 키워드로도 해결할 수 없습니다.
  4. msg 문자열을 읽고 위의 목록과 대조하십시오. Connection refusedConnection timed out는 각각 SSH 서비스와 네트워크 경로라는 서로 다른 두 지점을 가리킵니다.
  5. Host key verification failed.의 경우, ssh-keygen -F web2.example.com에 저장된 내용을 확인하십시오. 서버가 재구축되었다면 ssh-keygen -R web2.example.com로 기존 항목을 삭제하고, 제공자 콘솔에서 확인한 후 새로운 키를 수락하십시오. ansible.cfg에서 host_key_checking = False를 설정하면 오류는 사라지지만, 다른 장비가 해당 주소로 응답하고 있다는 사실을 알려주는 검사 기능도 함께 제거됩니다.
  6. Permission denied (publickey)의 경우, Ansible이 어떤 설정을 사용하려는지 확인하십시오. ansible-inventory -i inventory.ini --host web2ansible_useransible_port를 포함하여 현재 적용 중인 변수를 출력합니다.
  7. SSH는 작동하지만 모듈이 실행되지 않는다면 ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'로 인터프리터를 확인하십시오. raw 모듈은 셸을 통해 명령어를 실행하므로 대상 서버에 python이 필요하지 않습니다.

이 과정을 거친 후에야 호스트를 무시하는 것이 습관이 아닌 의도적인 결정이 됩니다.

요약은 연결할 수 없는 호스트를 별도로 집계하며, CI는 보통 이를 놓칩니다

ansible-playbook은 성공 시 0을 반환하고, 최소 한 대의 호스트가 실패하면 2를, 최소 한 대의 호스트에 연결할 수 없으면 4를 반환합니다. 이 두 값은 소스 코드에서 비트 플래그로 처리되므로, 실패한 호스트와 연결할 수 없는 호스트가 동시에 발생하면 6을 반환합니다. ansible 명령도 동일한 코드를 반환합니다. 이 내용은 2026년 8월 기준 ansible-core 소스 코드를 바탕으로 확인되었습니다.

이제 ignore_unreachable: true를 설정하고 동일한 7개 작업으로 구성된 플레이를 동일한 죽은 호스트에 대해 실행합니다.

PLAY RECAP *********************************************************************
web1  : ok=7  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2  : ok=7  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=7

web2unreachable=0 및 7개의 작업이 ok되었다고 보고하며, 실행은 0을 반환합니다. 해당 키워드가 설정되면 Ansible은 dark라고 부르는 카운터 대신 해당 호스트에 대한 okignored 카운터를 증가시킵니다. darkunreachable 열을 채우는 카운터입니다. 빨간색 UNREACHABLE! 줄은 여전히 출력되므로 로그는 정직하지만, 요약과 종료 코드는 그렇지 않습니다.

플레이북을 실행하고 $?만 확인하는 CI 작업은 해당 실행을 성공으로 간주하며, 요약 어디에도 기기에 전혀 접근하지 못했다는 사실이 기록되지 않습니다. 연결성 확인을 플레이 실행 전 별도의 단계로 분리하십시오.

ansible all -i inventory.ini -m ansible.builtin.ping -o

이 명령은 호스트당 한 줄씩 출력하며, 연결할 수 없는 호스트가 있으면 4를 반환합니다. 이를 통해 파이프라인에서 실패를 감지할 수 있고 로그에 호스트 이름을 남길 수 있습니다. ping는 대상 시스템에서 작동하는 Python 인터프리터가 필요하므로, 단순 연결 확인보다 더 많은 정보를 제공하며 이는 보통 의도한 바와 일치합니다. 그 후 ignore_unreachable 옵션을 사용하여 플레이북을 실행하면, 정상적인 호스트들은 여전히 변경 사항을 적용받을 수 있습니다.

any_errors_fatal 및 max_fail_percentage의 배치 적용

이 두 키워드는 배치의 일부에서 문제가 발생했을 때 어떤 동작을 수행할지 결정하며, 연결할 수 없는 호스트를 서로 다르게 처리합니다.

any_errors_fatal: true은 연결할 수 없는 호스트에 반응합니다. Ansible은 현재 배치의 나머지 호스트에 대해 작업을 완료한 뒤, 해당 배치의 모든 호스트에 대해 플레이를 중단합니다. 조정된 스키마 변경과 같이 전체가 성공하거나 아예 실행되지 않아야 하는 경우에 사용하십시오.

max_fail_percentage: 30는 연결할 수 없는 호스트에 반응하지 않습니다. 이 검사는 실패한 호스트 수를 배치 크기로 나누며, 연결할 수 없는 호스트는 별도의 목록으로 관리되므로 해당 수치에 영향을 주지 않습니다. 10개의 호스트 중 4개가 연결 불가능한 상태라도 max_fail_percentage: 10 하에서는 계속 진행되지만, 2개의 호스트에서 작업이 실패하면 플레이가 중단됩니다. 문서에서는 한 가지 주의사항을 더 언급합니다: "설정된 백분율을 초과해야 하며, 같아서는 안 됩니다." serial: 4를 사용할 때 4개 중 2개의 실패로 중단하려면 50이 아닌 49를 입력해야 합니다.

연결할 수 없는 호스트가 단독으로 실행을 중단시키는 경우가 하나 있습니다. 배치 내의 모든 호스트가 실패했거나 연결할 수 없는 상태라면, Ansible은 더 이상 작업할 대상이 없으므로 NO MORE HOSTS LEFT와 함께 플레이를 종료합니다.

serial: 전체 서버군에 변경 사항 배포하기

- name: Rolling nginx config update
  hosts: webservers
  serial: 2
  max_fail_percentage: 25
  tasks:
    - name: Deploy the site config
      ansible.builtin.template:
        src: site.conf.j2
        dest: /etc/nginx/conf.d/site.conf
        owner: root
        mode: "0644"
      become: true
      notify: Reload nginx
  handlers:
    - name: Reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded
      become: true

serial: 2은 전체 플레이를 두 대의 호스트에 대해 실행하고 완료한 뒤, 다음 두 대를 시작합니다. serial: "25%"은 그룹 크기에 따라 확장됩니다. serial: [1, 5, 10]과 같은 리스트는 카나리(canary) 배포 형태를 띱니다. 첫 번째 호스트를 먼저 실행하고, 그다음 5대, 그다음 10대를 실행하며, 남은 호스트는 마지막 배치 크기만큼씩 실행합니다. max_fail_percentage는 배치 단위로 측정되므로 두 설정은 함께 작동합니다. 첫 번째 장비에서 문제가 발생하면 40대의 장비로 확산되기 전에 실행이 중단됩니다. 이것이 바로 단일 제어 장비에서 여러 Linux 서버 관리하기를 하나의 명령어로 안전하게 수행할 수 있게 하는 핵심입니다.

연결할 수 없는 호스트를 무시해야 할 때와 그렇지 않을 때

기회주의적 작업에서는 호스트를 무시해도 됩니다. 팩트 수집이나 매시간 수행하는 드리프트 점검은 호스트가 다운되어 건너뛰더라도 다음 실행 시점에 다시 확인하므로 손실이 없습니다. 이 경우 ignore_unreachable: true 플레이 수준 설정이 적절하며, ping 단계를 함께 사용하여 건너뛴 호스트 목록을 사람이 확인할 수 있는 곳에 남겨야 합니다.

보안 패치 작업 시에는 절대 무시해서는 안 됩니다. 패치 작업의 가치는 모든 호스트에 수정 사항이 적용되었음을 보장하는 데 있습니다. 연결할 수 없는 상태를 억제하면 "서버 한 대가 여전히 취약함"이라는 경고가 "성공"이라는 녹색 요약으로 둔갑하게 됩니다. 2주 동안 연결할 수 없었던 호스트야말로 가장 뒤처져 있을 가능성이 큽니다. 해당 작업이 4번 코드로 종료되게 두고 사람이 직접 확인하게 하십시오.

두 경우 모두 한 가지 규칙이 적용됩니다. 중단은 억제하되 기록은 절대 억제하지 마십시오. 호스트를 건너뛰었다면 요약, CI 로그, 또는 모니터링 알림 중 어디에든 그 사실이 남아야 합니다. Ansible은 플레이가 실행되는 몇 초 동안만 호스트의 존재를 인식하므로, 화요일부터 서버가 다운되어 있었다는 사실을 파악하기에는 적절한 도구가 아닙니다. 그 역할은 모니터링의 몫이며, Zabbix를 설치하는 Ansible 플레이북을 사용하면 오후 시간 내에 전체 인프라에 대한 가시성을 확보할 수 있습니다.

FAQ

Ansible에서 ignore_errors와 ignore_unreachable의 차이점은 무엇입니까?

ignore_errors: true은 호스트에서 작업이 실행되었으나 명령어가 0이 아닌 종료 코드를 반환하는 등 실패했을 때 적용됩니다. ignore_unreachable: true는 Ansible이 호스트에 연결할 수 없어 모듈 자체가 실행되지 않았을 때 적용됩니다. 두 설정은 작업 결과의 서로 다른 필드를 참조하므로, 하나가 다른 경우를 대신할 수 없습니다. Ansible 문서에 따르면 ignore_errors은 "정의되지 않은 변수 오류, 연결 실패, 실행 문제(예: 패키지 누락) 또는 구문 오류를 무시하지 않으며", SSH 포트가 닫힌 경우도 연결 실패에 해당합니다.

ignore_unreachable을 사용하면 플레이 요약(play recap)에서 호스트가 숨겨집니까?

사실상 그렇습니다. 해당 키워드를 설정하면 Ansible은 해당 호스트를 unreachable로 집계하지 않고, 작업당 한 번씩 okignored으로 집계하며, 실행 결과는 0을 반환합니다. fatal: [host]: UNREACHABLE! 줄은 여전히 출력되므로 요약과 종료 코드는 정확하지 않더라도 로그는 정확하게 남습니다. ignored 열을 확인하거나, 연결할 수 없는 호스트가 발생했을 때 0이 아닌 종료 코드를 반환하도록 ansible all -m ansible.builtin.ping -o를 별도의 단계로 실행하십시오.

호스트에 연결할 수 없을 때 ansible-playbook은 어떤 종료 코드를 반환합니까?

4를 반환합니다. 실패한 호스트가 하나라도 있으면 2를 반환하며, 이 두 값은 비트 플래그이므로 실패와 연결 불가 호스트가 모두 발생하면 6을 반환합니다. 성공적인 실행은 0을 반환합니다. 이 코드들은 2026년 8월 기준으로 ansible-core 소스 코드를 통해 확인되었습니다. ignore_unreachable: true을 설정하면 4가 반환되지 않으므로, 종료 코드만 확인하는 파이프라인에서는 건너뛴 장비를 식별할 수 없습니다.

응답이 없는 호스트에 대해 플레이의 나머지 부분을 건너뛰려면 어떻게 해야 합니까?

첫 번째 작업을 ansible.builtin.ping로 설정하고 ignore_unreachable: trueregister: reachable을 사용한 뒤, when: reachable.unreachable | default(false) 조건 하에 ansible.builtin.meta: end_host를 배치하십시오. end_host은 해당 호스트를 실패로 표시하지 않고 플레이를 종료합니다. 플레이에 gather_facts: false을 설정하여 ping 작업에서 연결 끊김을 감지하도록 하십시오. 이 패턴을 사용하지 않으면 응답 없는 호스트가 플레이에 남아, 이후의 모든 작업에서 연결 시간 초과를 다시 기다리게 됩니다.

보안 패치 실행 중에 연결할 수 없는 호스트를 무시해야 합니까?

아니요. 패치 실행은 모든 호스트가 업데이트를 완료했음을 보장하기 위해 수행하는 작업입니다. 연결할 수 없는 호스트를 무시하면 그 보장이 사라지고 요약 결과만 녹색으로 표시될 뿐입니다. 실행 결과가 4를 반환하도록 두고, 응답하지 않은 호스트의 이름을 확인하여 조치하십시오. 오류 무시는 다음 실행에서 누락된 부분을 처리할 수 있는 반복적인 기회주의적 실행에만 적합합니다.

#ansible#playbooks#error-handling#inventory#automation