Ansible 'UNREACHABLE!' 오류 해결 방법

AnsibleBeginner
지금 연습하기

소개

Ansible 은 복잡한 IT 환경 관리를 단순화하는 강력한 인프라 자동화 도구입니다. 하지만 사용자는 종종 자동화 워크플로우를 방해할 수 있는 'UNREACHABLE!' 오류를 접하게 됩니다. 이 오류는 일반적으로 Ansible 이 대상 호스트와의 연결을 설정할 수 없을 때 발생합니다. 이 랩에서는 Ansible 배포에서 'UNREACHABLE!' 오류를 식별, 문제 해결 및 예방하는 방법을 배우게 됩니다.

이 랩을 마치면 Ansible 에서 연결 문제의 일반적인 원인을 이해하고 자동화가 원활하게 실행되도록 효과적인 솔루션을 구현할 수 있게 됩니다.

Ansible 환경 설정

이 단계에서는 사용할 기본적인 Ansible 환경을 설정합니다. Ansible 을 설치하고, 필수 파일을 구성하며, 모든 것이 실험을 위해 준비되었는지 확인합니다.

Ansible 설치

먼저, 다음 명령을 사용하여 LabEx VM 에 Ansible 을 설치해 보겠습니다.

sudo apt update
sudo apt install -y ansible

이렇게 하면 Ubuntu 저장소에서 사용할 수 있는 최신 버전의 Ansible 이 설치됩니다. 설치가 완료되면 Ansible 버전을 확인하여 설치를 확인합니다.

ansible --version

다음과 유사한 출력이 표시되어 Ansible 버전 및 구성 세부 정보를 보여줍니다.

ansible [core 2.12.x]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/labex/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /home/labex/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.x (default, Apr 8 2022, 09:04:19) [GCC 11.2.0]
  jinja version = 3.0.3
  libyaml = True

작업 디렉토리 생성

Ansible 작업을 위한 전용 디렉토리를 만들어 보겠습니다.

mkdir -p ~/project/ansible-lab
cd ~/project/ansible-lab

Ansible 구성 파일 생성

이제 프로젝트 디렉토리에 기본 Ansible 구성 파일을 만들어 보겠습니다.

cat > ansible.cfg << 'EOF'
[defaults]
inventory = ./inventory
host_key_checking = False
remote_user = labex
EOF

이 구성 파일은 다음을 수행합니다.

  • 인벤토리 파일의 위치를 지정합니다.
  • SSH 호스트 키 확인을 비활성화합니다 (랩 환경에 유용함).
  • 기본 원격 사용자를 'labex'로 설정합니다.

인벤토리 파일 생성

인벤토리 파일은 Ansible 이 관리할 호스트를 정의합니다. 간단한 인벤토리 파일을 만들어 보겠습니다.

cat > inventory << 'EOF'
[local]
localhost ansible_connection=local

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

이 인벤토리에는 두 개의 그룹이 포함되어 있습니다.

  • local: 로컬 연결을 사용하는 localhost 만 포함합니다.
  • virtual: 'UNREACHABLE!' 오류를 시연하는 데 사용할 가상 호스트를 포함합니다.

virtual-host는 환경에 존재하지 않는 IP 주소 (10.10.10.10) 로 구성되어 'UNREACHABLE!' 오류를 생성하는 데 도움이 됩니다.

Ansible 테스트

로컬 호스트에 대한 간단한 ping 명령을 실행하여 Ansible 설정을 테스트해 보겠습니다.

ansible local -m ping

다음과 같은 성공적인 응답이 표시되어야 합니다.

localhost | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

이는 Ansible 이 로컬 연결에 대해 올바르게 작동하고 있음을 확인합니다. 이제 가상 호스트를 ping 해 보겠습니다. 실패해야 합니다.

ansible virtual -m ping

호스트가 존재하지 않으므로 'UNREACHABLE!' 오류가 발생합니다.

virtual-host | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.10.10.10 port 22: Connection timed out",
    "unreachable": true
}

이제 Ansible 을 성공적으로 설정하고 다음 단계에서 조사할 'UNREACHABLE!' 오류가 발생하는 시나리오를 만들었습니다.

'UNREACHABLE!' 오류 이해

이전 단계에서 존재하지 않는 호스트에 연결하려고 할 때 'UNREACHABLE!' 오류가 발생했습니다. 이제 오류를 자세히 이해하고 일반적인 원인을 살펴보겠습니다.

오류 메시지 분석

받은 오류 메시지를 살펴보겠습니다.

virtual-host | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.10.10.10 port 22: Connection timed out",
    "unreachable": true
}

오류 메시지는 귀중한 정보를 제공합니다.

  • UNREACHABLE!은 Ansible 이 호스트에 연결을 설정할 수 없음을 나타냅니다.
  • msg 필드는 그 이유를 알려줍니다: "Failed to connect to the host via ssh" (SSH 를 통해 호스트에 연결하지 못했습니다)
  • 구체적인 오류는 "Connection timed out" (연결 시간 초과) 으로, Ansible 이 연결을 시도했지만 응답을 받지 못했음을 의미합니다.

'UNREACHABLE!' 오류의 일반적인 원인

'UNREACHABLE!' 오류는 여러 가지 이유로 발생할 수 있습니다.

  1. 네트워크 문제: 호스트가 방화벽 뒤에 있거나 네트워크 연결 문제가 있을 수 있습니다.
  2. 잘못된 호스트 정보: 인벤토리의 호스트 이름 또는 IP 주소가 잘못되었을 수 있습니다.
  3. SSH 구성: 대상 호스트에서 SSH 가 올바르게 구성되지 않았을 수 있습니다.
  4. 인증 문제: SSH 키 또는 비밀번호가 잘못되었을 수 있습니다.
  5. 호스트 사용 불가: 호스트가 다운되었거나 연결할 수 없는 상태일 수 있습니다.

테스트 플레이북 생성

오류를 더 자세히 시연하기 위해 간단한 플레이북을 만들어 보겠습니다.

cat > test_playbook.yml << 'EOF'
---
- name: Test Connectivity
  hosts: all
  gather_facts: no
  tasks:
    - name: Ping the hosts
      ping:
EOF

이 플레이북은 인벤토리에 정의된 모든 호스트를 ping 하려고 시도합니다. 실행해 보겠습니다.

ansible-playbook test_playbook.yml

다음과 유사한 출력이 표시되어야 합니다.

PLAY [Test Connectivity] ************************************************

TASK [Ping the hosts] ***************************************************
ok: [localhost]
fatal: [virtual-host]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.10.10.10 port 22: Connection timed out", "unreachable": true}

PLAY RECAP *************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
virtual-host               : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0

플레이북은 localhost에 대해 성공했지만 virtual-host에 대해 'UNREACHABLE!' 오류와 함께 실패했습니다.

Ansible 상세 수준 검사

Ansible 은 문제를 진단하는 데 도움이 되는 다양한 상세 수준을 제공합니다. 플레이북을 상세 수준을 높여 실행해 보겠습니다.

ansible-playbook test_playbook.yml -v

더 자세한 출력을 보려면 -vv 또는 -vvv를 사용하십시오.

ansible-playbook test_playbook.yml -vvv

-vvv 옵션은 Ansible 이 사용하려는 정확한 SSH 명령을 표시하여 가장 자세한 출력을 제공합니다.

<virtual-host> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o User=labex -o ConnectTimeout=10 -o ControlPath=/home/labex/.ansible/cp/ansible-ssh-%h-%p-%r 10.10.10.10 '/bin/sh -c '"'"'echo ~labex && sleep 0'"'"''

이러한 세부 정보 수준은 SSH 연결 문제를 해결하는 데 매우 유용할 수 있습니다.

--limit 옵션 사용

대규모 인벤토리를 사용할 때 --limit 옵션을 사용하여 특정 호스트 또는 그룹에 대해 Ansible 이 명령을 실행하도록 제한할 수 있습니다.

ansible-playbook test_playbook.yml --limit localhost

이 명령은 localhost에 대해서만 플레이북을 실행하여 virtual-host의 'UNREACHABLE!' 오류를 방지합니다.

이제 'UNREACHABLE!' 오류를 더 잘 이해했으므로 다음 단계에서 이러한 문제를 해결하고 수정하는 방법을 알아보겠습니다.

'UNREACHABLE!' 오류 문제 해결 및 수정

이제 'UNREACHABLE!' 오류의 원인을 이해했으므로, 이를 해결하고 수정하는 방법을 배우겠습니다. 다양한 접근 방식을 사용하여 연결 문제를 진단하고 해결할 것입니다.

인벤토리 문제 수정

'UNREACHABLE!' 오류의 가장 일반적인 원인 중 하나는 잘못된 인벤토리 정보입니다. 인벤토리 파일을 수정해 보겠습니다.

cd ~/project/ansible-lab

먼저, 유효한 호스트를 포함하도록 인벤토리 파일을 업데이트하겠습니다. 이 랩 환경에서는 문제 해결 기술을 시연하기 위해 다양한 연결 방법으로 localhost를 사용하는 데 중점을 둡니다.

cat > inventory << 'EOF'
[local]
localhost ansible_connection=local

[ssh_local]
local-ssh ansible_host=127.0.0.1 ansible_connection=ssh

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

로컬 연결 방법 대신 SSH 를 통해 localhost 에 연결을 시도하는 호스트가 있는 새 그룹 ssh_local을 추가했습니다.

SSH 연결 직접 테스트

Ansible 을 사용하기 전에 항상 SSH 연결을 직접 테스트하는 것이 좋습니다.

ssh 127.0.0.1

비밀번호를 묻거나 호스트 키에 대한 메시지가 표시될 수 있습니다. 이는 SSH 연결이 작동하고 있음을 의미하는 좋은 신호이지만, Ansible 에 맞게 SSH 를 제대로 구성해야 할 수 있습니다.

비밀번호 프롬프트에서 멈춘 경우 Ctrl+C 를 눌러 종료하십시오.

암호 없는 인증을 위한 SSH 키 설정

Ansible 은 일반적으로 인증에 SSH 키를 사용합니다. localhost 에 대한 암호 없는 SSH 액세스를 설정해 보겠습니다.

## Generate an SSH key if you don't have one
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa

## Add the key to authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

## Set proper permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

이제 SSH 를 통해 localhost 에 연결해 보십시오.

ssh 127.0.0.1

비밀번호를 묻지 않고 연결할 수 있어야 합니다. exit를 입력하여 원래 세션으로 돌아갑니다.

SSH 연결로 Ansible 테스트

이제 SSH 연결을 사용하여 Ansible 을 테스트해 보겠습니다.

ansible ssh_local -m ping

SSH 설정이 올바르면 성공적인 응답이 표시되어야 합니다.

local-ssh | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

여전히 'UNREACHABLE!' 오류가 표시되면 인벤토리 파일에 더 많은 연결 매개변수를 추가해 보겠습니다.

cat > inventory << 'EOF'
[local]
localhost ansible_connection=local

[ssh_local]
local-ssh ansible_host=127.0.0.1 ansible_connection=ssh ansible_user=labex ansible_ssh_private_key_file=~/.ssh/id_rsa

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

ping 명령을 다시 시도하십시오.

ansible ssh_local -m ping

사용자 지정 SSH 구성으로 Ansible 사용

경우에 따라 더 복잡한 SSH 구성이 필요합니다. 사용자 지정 SSH 구성 파일을 만들어 보겠습니다.

mkdir -p ~/.ssh
cat > ~/.ssh/config << 'EOF'
Host local-ssh
    HostName 127.0.0.1
    User labex
    IdentityFile ~/.ssh/id_rsa
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null
EOF

## Set proper permissions
chmod 600 ~/.ssh/config

SSH 구성 항목을 사용하도록 인벤토리를 업데이트합니다.

cat > inventory << 'EOF'
[local]
localhost ansible_connection=local

[ssh_local]
local-ssh

[virtual]
virtual-host ansible_host=10.10.10.10
EOF

다시 연결을 테스트합니다.

ansible ssh_local -m ping

모든 연결을 테스트하는 플레이북 생성

모든 연결을 테스트하는 포괄적인 플레이북을 만들어 보겠습니다.

cat > connection_test.yml << 'EOF'
---
- name: Test Local Connection
  hosts: local
  gather_facts: no
  tasks:
    - name: Ping local
      ping:
      register: local_ping
    
    - name: Display local ping result
      debug:
        var: local_ping

- name: Test SSH Connection
  hosts: ssh_local
  gather_facts: no
  tasks:
    - name: Ping via SSH
      ping:
      register: ssh_ping
    
    - name: Display SSH ping result
      debug:
        var: ssh_ping
EOF

플레이북을 실행합니다.

ansible-playbook connection_test.yml

로컬 및 SSH 호스트 모두에 대한 성공적인 연결이 표시되어야 합니다.

PLAY [Test Local Connection] ********************************************

TASK [Ping local] ******************************************************
ok: [localhost]

TASK [Display local ping result] ****************************************
ok: [localhost] => {
    "local_ping": {
        "changed": false,
        "ping": "pong"
    }
}

PLAY [Test SSH Connection] **********************************************

TASK [Ping via SSH] ****************************************************
ok: [local-ssh]

TASK [Display SSH ping result] *****************************************
ok: [local-ssh] => {
    "ssh_ping": {
        "changed": false,
        "ping": "pong"
    }
}

PLAY RECAP *************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
local-ssh                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

성공적인 출력은 유효한 호스트에 대한 'UNREACHABLE!' 오류를 수정했음을 확인합니다. 연결할 수 없는 유일한 호스트는 존재하지 않기 때문에 의도적으로 virtual-host입니다.

이제 다음을 통해 'UNREACHABLE!' 오류를 성공적으로 진단하고 수정했습니다.

  1. 직접 SSH 연결 테스트
  2. 암호 없는 인증을 위한 SSH 키 설정
  3. 적절한 연결 매개변수를 사용하여 Ansible 인벤토리 구성
  4. 사용자 지정 SSH 구성 사용
  5. 포괄적인 플레이북으로 연결 확인

'UNREACHABLE!' 오류 방지를 위한 모범 사례 구현

이제 즉각적인 'UNREACHABLE!' 오류를 수정했으므로, 앞으로 이를 방지하기 위한 모범 사례에 집중해 보겠습니다. 여기에는 적절한 인벤토리 관리, 연결 구성 및 오류 처리 기술이 포함됩니다.

강력한 인벤토리 구조 생성

잘 구성된 인벤토리는 문제 해결을 더 쉽게 만듭니다. 더 구조화된 인벤토리 디렉토리를 만들어 보겠습니다.

cd ~/project/ansible-lab
mkdir -p inventory/{group_vars,host_vars}

이제 기본 인벤토리 파일을 만들어 보겠습니다.

cat > inventory/hosts << 'EOF'
## Production Servers
[production]
## prod-server ansible_host=prod.example.com

## Development Servers
[development]
## dev-server ansible_host=dev.example.com

## Local Connections
[local]
localhost ansible_connection=local

## SSH Connections
[ssh_local]
local-ssh ansible_host=127.0.0.1
EOF

다음으로, SSH 연결 그룹에 대한 그룹 변수를 만들어 보겠습니다.

cat > inventory/group_vars/ssh_local.yml << 'EOF'
---
ansible_connection: ssh
ansible_user: labex
ansible_ssh_private_key_file: ~/.ssh/id_rsa
ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
EOF

새 인벤토리 디렉토리를 사용하도록 Ansible 구성을 업데이트합니다.

cat > ansible.cfg << 'EOF'
[defaults]
inventory = ./inventory/hosts
host_key_checking = False
retry_files_enabled = True
retry_files_save_path = ~/.ansible/retry-files
timeout = 30
connect_timeout = 30
command_timeout = 30

[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
control_path_dir = ~/.ansible/cp
EOF

재시도 로직을 사용한 연결 테스트 플레이북 생성

Ansible 을 사용하면 실패한 작업을 재시도할 수 있습니다. 재시도 로직이 있는 플레이북을 만들어 보겠습니다.

cat > connection_test_with_retry.yml << 'EOF'
---
- name: Test All Connections
  hosts: all
  gather_facts: no
  tasks:
    - name: Ping hosts
      ping:
      register: ping_result
      retries: 3
      delay: 5
      until: ping_result is not failed
      ignore_unreachable: yes
      
    - name: Display ping status
      debug:
        msg: "Connection to {{ inventory_hostname }} was successful"
      when: ping_result is success
      
    - name: Report unreachable hosts
      debug:
        msg: "Host {{ inventory_hostname }} is unreachable"
      when: ping_result is unreachable
EOF

새 인벤토리 구조로 플레이북을 실행합니다.

ansible-playbook connection_test_with_retry.yml

localhost 및 local-ssh 에 대한 성공적인 연결을 보여주는 출력이 표시되어야 합니다.

'UNREACHABLE!' 오류를 정상적으로 처리

'UNREACHABLE!' 오류를 정상적으로 처리하고 보고서를 생성하는 더 고급 플레이북을 만들어 보겠습니다.

cat > connection_report.yml << 'EOF'
---
- name: Test Connections and Generate Report
  hosts: all
  gather_facts: no
  tasks:
    - name: Try to connect to hosts
      ping:
      register: ping_result
      ignore_unreachable: yes
      
    - name: Create reachable hosts list
      set_fact:
        reachable_hosts: "{{ (reachable_hosts | default([])) + [inventory_hostname] }}"
      when: ping_result is success
      delegate_to: localhost
      delegate_facts: true
      
    - name: Create unreachable hosts list
      set_fact:
        unreachable_hosts: "{{ (unreachable_hosts | default([])) + [inventory_hostname] }}"
      when: ping_result is unreachable
      delegate_to: localhost
      delegate_facts: true

- name: Generate Connection Report
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Display reachable hosts
      debug:
        msg: "Reachable hosts: {{ reachable_hosts | default([]) | join(', ') }}"
      
    - name: Display unreachable hosts
      debug:
        msg: "Unreachable hosts: {{ unreachable_hosts | default([]) | join(', ') }}"
      
    - name: Write report to file
      copy:
        content: |
          Connection Report
          -----------------
          Reachable hosts: {{ reachable_hosts | default([]) | join(', ') }}
          Unreachable hosts: {{ unreachable_hosts | default([]) | join(', ') }}
          
          Generated on: {{ ansible_date_time.iso8601 }}
        dest: ~/project/ansible-lab/connection_report.txt
      register: report
      
    - name: Show report location
      debug:
        msg: "Report saved to {{ report.dest }}"
EOF

보고서 플레이북을 실행합니다.

ansible-playbook connection_report.yml

보고서를 확인해 보겠습니다.

cat ~/project/ansible-lab/connection_report.txt

연결 가능한 호스트와 연결할 수 없는 호스트를 나열하는 보고서가 표시되어야 합니다.

Ansible 인벤토리 플러그인 사용

Ansible 은 호스트를 동적으로 관리하기 위해 인벤토리 플러그인을 제공합니다. 이를 시연하기 위해 간단한 스크립트를 만들어 보겠습니다.

cat > inventory_script.py << 'EOF'
#!/usr/bin/env python3

import json
import socket

def is_host_reachable(host, port=22, timeout=1):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except:
        return False

## Define our hosts
hosts = {
    'localhost': '127.0.0.1',
    'local-ssh': '127.0.0.1',
    'virtual-host': '10.10.10.10'
}

## Check reachability and build inventory
inventory = {
    'all': {
        'hosts': list(hosts.keys())
    },
    'reachable': {
        'hosts': []
    },
    'unreachable': {
        'hosts': []
    },
    '_meta': {
        'hostvars': {}
    }
}

for hostname, ip in hosts.items():
    reachable = is_host_reachable(ip)
    group = 'reachable' if reachable else 'unreachable'
    inventory[group]['hosts'].append(hostname)
    
    inventory['_meta']['hostvars'][hostname] = {
        'ansible_host': ip,
        'reachability_checked': True,
        'is_reachable': reachable
    }

print(json.dumps(inventory, indent=2))
EOF

chmod +x inventory_script.py

동적 인벤토리 스크립트를 테스트합니다.

./inventory_script.py

연결 가능한 호스트 또는 연결할 수 없는 호스트로 분류된 호스트를 보여주는 JSON 출력이 표시되어야 합니다.

이 동적 인벤토리를 사용하여 플레이북을 실행해 보겠습니다.

ansible-playbook -i ./inventory_script.py connection_test.yml --limit reachable

이렇게 하면 스크립트가 연결할 수 있다고 결정한 호스트에만 연결을 시도하여 'UNREACHABLE!' 오류를 완전히 방지할 수 있습니다.

이러한 모범 사례는 Ansible 연결을 관리하고 프로덕션 환경에서 'UNREACHABLE!' 오류를 방지하기 위한 강력한 프레임워크를 제공합니다.

요약

이 랩에서는 Ansible 에서 'UNREACHABLE!' 오류를 식별, 문제 해결 및 방지하는 방법을 배웠습니다. 다음을 수행했습니다.

  1. 기본적인 Ansible 환경을 설정하고 'UNREACHABLE!' 오류를 직접 경험했습니다.

  2. 오류 메시지를 분석하고 연결 문제의 일반적인 원인을 이해했습니다.

  3. 다양한 문제 해결 기술을 사용하여 연결 문제를 진단했습니다.

  4. 다음을 포함하여 오류를 수정하기 위한 솔루션을 구현했습니다.

    • 암호 없는 인증을 위한 SSH 키 구성
    • 적절한 인벤토리 파일 설정
    • SSH 구성 옵션 사용
  5. 다음과 같은 향후 'UNREACHABLE!' 오류를 방지하기 위한 모범 사례를 적용했습니다.

    • 구조화된 인벤토리 구성 생성
    • 재시도 로직 구현
    • 오류 처리 전략 개발
    • 호스트 연결 가능성을 확인하기 위한 동적 인벤토리 스크립트 사용

이러한 기술은 안정적인 Ansible 배포를 유지하고 발생하는 모든 연결 문제를 신속하게 해결하는 데 도움이 됩니다. 'UNREACHABLE!' 오류의 근본 원인을 이해하고 적절한 예방 조치를 구현함으로써 인프라 자동화가 원활하고 효율적으로 실행되도록 할 수 있습니다.