자주 묻는 질문

자주 묻는 질문 (Frequently Asked Questions)

Ansible을 쓰다 보면 반복적으로 나오는 질문들이 있어요. 모듈이 어디로 갔는지, 느릴 때는 어떻게 해야 하는지, 인벤토리 변수는 어떻게 다루는지 같은 것들이요. 여기서는 흔히 묻는 질문과 그 답을 정리해요.

출처: 문서

본문

자주 묻는 질문들과 그 답변을 모아 놓았어요.

모든 모듈은 어디로 갔나요?

2019년 7월에 컬렉션(collections)이 Ansible 콘텐츠 전달의 미래가 될 것이라고 발표했어요. 컬렉션은 플레이북, 롤, 모듈, 플러그인을 담을 수 있는 Ansible 콘텐츠의 배포 형식이에요. Ansible 2.9에서 컬렉션 지원을 추가했고, Ansible 2.10에서 대부분의 모듈을 주 ansible/ansible 저장소에서 분리해 컬렉션으로 옮겼어요. 컬렉션은 Ansible 팀, Ansible 커뮤니티 또는 Ansible 파트너가 유지보수할 수 있어요. 이제 ansible/ansible 저장소에는 모듈 코드를 관리 노드로 복사하는 것 같은 기본 기능·함수의 코드가 들어 있어요. 이 코드를 ansible-core라고 불러요(2.10에서는 잠시 ansible-base라고 불렸어요).

  • 컬렉션 사용법은 'Ansible 컬렉션 사용하기' 문서를 참고해요.
  • 컬렉션 개발 방법은 '컬렉션 개발하기' 문서를 참고해요.
  • 기존 컬렉션에 기여하는 방법은 각 컬렉션 저장소의 지침을 보거나, Ansible이 유지보수하는 컬렉션에 기여하는 방법을 다루는 문서를 참고해요.

특정 모듈은 어디로 갔나요?

특정 모듈을 찾고 있다면 runtime.yml 파일을 확인해 보세요. 여기에는 주 ansible/ansible 저장소에서 분리한 각 모듈의 첫 행선지가 나열되어 있어요. 일부 모듈은 그 이후로 다시 옮겨졌을 수도 있어요. Ansible Galaxy에서 검색하거나 채팅 채널 중 한 곳에 물어봐도 돼요.

느린 디스크를 가진 시스템에서 Ansible을 어떻게 빠르게 할까요?

Raspberry PI처럼 디스크가 느린 시스템에서는 Ansible이 느리게 느껴질 수 있어요. 개선 방법에 대한 힌트는 'libyaml을 사용할 수 없으면 Ansible이 느릴 수 있다' 문서를 참고해요.

태스크나 전체 play의 PATH 또는 다른 환경 변수를 어떻게 설정할까요?

환경 변수 설정은 environment 키워드로 할 수 있어요. play의 태스크 단위나 다른 레벨에서 사용할 수 있어요.

shell:
  cmd: date
environment:
  LANG=fr_FR.UTF-8
hosts: servers
environment:
  PATH: "{{ ansible_env.PATH }}:/thingy/bin"
  SOME: value

참고: 2.0.1부터 gather_facts의 setup 태스크도 play의 environment 지시어를 상속받아요. play 레벨에서 설정한다면 |default 필터를 써서 오류를 피해야 할 수 있어요.

머신마다 다른 사용자 계정이나 포트로 로그인해야 한다면 어떻게 해야 하나요?

인벤토리 파일에 인벤토리 변수를 설정하는 것이 가장 쉬운 방법이에요.

예를 들어 다음 호스트들이 서로 다른 사용자 이름과 포트를 쓴다고 가정해 보세요.

[webservers]
asdf.example.com  ansible_port=5000   ansible_user=alice
jkl.example.com   ansible_port=5001   ansible_user=bob

원한다면 사용할 연결 타입도 지정할 수 있어요.

[testcluster]
localhost           ansible_connection=local
/path/to/chroot1    ansible_connection=chroot
foo.example.com     ansible_connection=paramiko

이 값을 그룹 변수로 두거나 group_vars/ 파일에 저장할 수도 있어요. 변수를 구성하는 방법에 대한 더 자세한 내용은 나머지 문서를 참고해요.

Ansible이 연결을 재사용하게 하거나, Kerberized SSH를 활성화하거나, 로컬 SSH 설정 파일을 인식하게 하려면 어떻게 하나요?

설정 파일에서 기본 연결 타입을 ssh로 바꾸거나 -cssh를 써서 Python paramiko 라이브러리 대신 네이티브 OpenSSH로 연결하게 할 수 있어요. Ansible 1.2.1 이상에서는 OpenSSH가 ControlPersist 옵션을 지원할 만큼 충분히 새 버전이면 기본으로 ssh를 사용해요.

Paramiko는 처음 시작할 때 좋지만, OpenSSH 타입은 많은 고급 옵션을 제공해요. 이 연결 타입을 쓴다면 ControlPersist를 지원할 만큼 충분히 최신인 머신에서 Ansible을 실행하고 싶을 거예요. 더 오래된 클라이언트도 관리할 수 있어요. RHEL 6, CentOS 6, SLES 10 또는 SLES 11을 쓴다면 OpenSSH 버전이 아직 조금 오래됐으니, 더 오래된 노드를 관리하더라도 Fedora나 openSUSE 클라이언트에서 관리하는 것을 고려하거나 그냥 paramiko를 쓰세요.

paramiko를 기본으로 유지하는 이유는, 이런 엔터프라이즈 운영체제에 Ansible을 처음 설치하는 사용자에게 더 나은 경험을 제공하기 때문이에요.

직접 접근할 수 없는 서버에 접근하기 위한 점프 호스트를 어떻게 구성하나요?

ansible_ssh_common_args 인벤토리 변수에 ProxyCommand를 설정할 수 있어요. 이 변수에 지정된 인자는 관련 호스트에 연결할 때 sftp/scp/ssh 커맨드라인에 추가돼요. 다음 인벤토리 그룹을 생각해 보세요.

[gatewayed]
foo ansible_host=192.0.2.1
bar ansible_host=192.0.2.2

다음 내용을 가진 group_vars/gatewayed.yml을 만들 수 있어요.

ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p -q [email protected]"'

Ansible은 gatewayed 그룹의 어떤 호스트에든 연결할 때 이 인자를 커맨드라인에 추가해요. (이 인자는 ansible.cfgssh_args에 추가로 사용되므로, 전역 ControlPersist 설정을 ansible_ssh_common_args에 반복할 필요는 없어요.)

ssh -W는 OpenSSH 5.4 이상에서만 사용할 수 있다는 점을 기억하세요. 더 오래된 버전에서는 배스천 호스트에서 nc %h:%p 또는 이와 동등한 명령을 실행해야 해요.

이전 Ansible 버전에서는 ~/.ssh/config에서 하나 이상의 호스트에 적절한 ProxyCommand를 구성하거나 ansible.cfg에서 ssh_args를 설정해 전역으로 구성해야 했어요.

Ansible이 죽은 대상(target)을 적시에 인식하게 하려면 어떻게 하나요?

SSH 연결 플러그인의 ssh_args 매개변수에 -o ServerAliveInterval=NumberOfSeconds를 추가할 수 있어요. 이 옵션이 없으면 SSH, 그리고 따라서 Ansible은 TCP 연결이 타임아웃될 때까지 기다려요. 다른 해결책은 전역 SSH 설정에 ServerAliveInterval을 추가하는 거예요. ServerAliveInterval의 좋은 값은 여러분이 정하기 나름이지만, ServerAliveCountMax=3이 SSH 기본값이므로 설정한 값에 3을 곱한 뒤에 SSH 세션이 종료된다는 점을 기억하세요.

클라우드 제공자(EC2, openstack 등)의 서버에 대한 ansible 실행 속도를 어떻게 높이나요?

클라우드 제공자의 머신 그룹(fleet)을 노트북에서 관리하려고 하지 마세요. 대신 그 클라우드 제공자 안의 관리 노드에 먼저 연결한 뒤 거기서 Ansible을 실행하세요.

원격 머신의 /usr/bin/python에 Python 인터프리터가 없으면 어떻게 하나요?

Ansible 모듈을 어떤 언어로든 쓸 수 있지만, 대부분의 Ansible 모듈은 Python으로 작성되어 있어요. Ansible이 동작하게 하는 핵심 모듈도 포함이죠.

기본적으로 Ansible은 원격 시스템에 /usr/bin/python이 있다고 가정하고, 그것이 Python2(버전 2.6 이상) 또는 Python3(3.5 이상)이라고 간주해요.

어느 호스트에서든 ansible_python_interpreter 인벤토리 변수를 설정하면 Ansible이 그 값으로 Python 인터프리터를 자동으로 대체해요. 즉 시스템의 /usr/bin/python이 호환되는 Python 인터프리터를 가리키지 않는다면, 시스템에서 원하는 어떤 Python이라도 가리킬 수 있어요.

일부 플랫폼은 기본으로 Python 3만 설치되어 있을 수 있어요. /usr/bin/python으로 설치되어 있지 않다면 ansible_python_interpreter를 통해 인터프리터 경로를 설정해야 해요. 대부분의 핵심 모듈은 Python 3에서 동작하지만, 그렇지 않은 특수 목적 모듈이 있거나 엣지 케이스에서 버그를 만날 수도 있어요. 임시 해결책으로 관리 호스트에 Python 2를 설치하고 ansible_python_interpreter로 Ansible이 그 Python을 쓰도록 설정할 수 있어요. 모듈 문서에 Python 2가 필요하다는 언급이 없다면, 버그 트래커에 버그를 보고해서 차기 릴리스에서 호환성 문제가 고쳐지도록 할 수도 있어요.

Python 모듈의 shebang 줄을 바꾸지 마세요. Ansible이 배포 시점에 자동으로 처리해줘요.

또한 이 방법은 어떤 인터프리터에도 동작해요. 예를 들어 ruby는 ansible_ruby_interpreter, perl은 ansible_perl_interpreter 같은 식으로요. 그래서 어떤 스크립팅 언어로 작성한 커스텀 모듈에도 쓸 수 있고 인터프리터 위치를 제어할 수 있어요.

모듈 shebang 줄에 env를 넣으면(#!/usr/bin/env <other>) 동작하지 않고 하나의 문자열(env<other> 사이 공백 포함)로 평가된다는 점을 기억하세요. 인자는 의도된 것이 아니며 지원되지 않아요.

Ansible 설치 중 필요한 패키지 의존성은 어떻게 처리하나요?

Ansible을 설치하는 동안 No package 'libffi' foundfatal error: Python.h: No such file or directory 같은 오류를 만날 때가 있어요. 이런 오류는 대개 Ansible이 필요로 하는 패키지의 의존성인 패키지가 빠졌기 때문에 발생해요. 예를 들어 libffi 패키지는 pynacl과 paramiko의 의존성이에요(Ansible -> paramiko -> pynacl -> libffi).

이런 의존성 문제를 해결하려면 yum, dnf, apt 같은 OS 네이티브 패키지 매니저로 필요한 패키지를 설치하거나, 패키지 설치 가이드에 언급된 대로 설치해야 할 수 있어요.

해당 의존성과 설치 방법은 각 패키지의 문서를 참고하세요.

일반적인 시스템 이슈

virtualenv에서 실행하기

컨트롤 노드의 virtualenv에 Ansible을 간단히 설치할 수 있어요.

$ virtualenv ansible
$ source ./ansible/bin/activate
$ pip install ansible

Python 2 대신 Python 3에서 실행하고 싶다면 조금 바꿀 수 있어요.

$ virtualenv -p python3 ansible
$ source ./ansible/bin/activate
$ pip install ansible

pip로는 사용할 수 없는 라이브러리(예: SELinux가 활성화된 Red Hat Enterprise Linux나 Fedora 같은 시스템의 SELinux Python 바인딩)가 필요하다면, 그것들을 virtualenv에 설치해야 해요. 두 가지 방법이 있어요.

  • virtualenv를 만들 때 --system-site-packages를 지정해서 시스템 Python에 설치된 라이브러리를 사용하게 할 수 있어요.
$ virtualenv ansible --system-site-packages
  • 시스템에서 그 파일들을 수동으로 복사해요. 예를 들어 SELinux 바인딩은 이렇게 할 수 있어요.
$ virtualenv ansible --system-site-packages
$ cp -r -v /usr/lib64/python3.*/site-packages/selinux/ ./py3-ansible/lib64/python3.*/site-packages/
$ cp -v /usr/lib64/python3.*/site-packages/*selinux*.so ./py3-ansible/lib64/python3.*/site-packages/

macOS를 컨트롤 노드로 실행하기

macOS를 컨트롤 노드 머신으로 사용하는 시스템에서 Ansible을 실행할 때 다음 오류가 발생할 수 있어요.

오류 +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug. ERROR! A worker was found in a dead state

일반적으로 권장되는 해결책은 셸에 다음 환경 변수를 설정하는 거예요.

$ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

macOS를 대상으로 실행하기

macOS Monterey 12, macOS Ventura 13 이상을 SSH로 관리할 때 다음 오류가 발생할 수 있어요.

오류 "eDSPermissionError" DS Error: -14120 (eDSPermissionError)

이것은 원격 사용자에 대한 '전체 디스크 접근 허용(Allow full disk access)'이 활성화되지 않았음을 나타내는 신호예요.

자세한 내용은 공식 Apple 사용자 가이드 문서를 확인해 보세요.

BSD에서 실행하기

BSD 호스트를 Ansible로 관리하는 방법을 참고하세요.

Solaris에서 실행하기

기본적으로 Solaris 10 이하는 POSIX 호환이 아닌 셸을 실행해서 Ansible이 쓰는 기본 tmp 디렉토리(~/.ansible/tmp)를 올바르게 확장하지 못해요. Solaris 머신에서 모듈 실패가 보인다면 이것이 원인일 가능성이 높아요. 해결책이 몇 가지 있어요.

  • remote_tmp를 사용 중인 셸에서 올바르게 확장되는 경로로 설정할 수 있어요(C shell, fish shell, Powershell 플러그인 문서 참고). 예를 들어 ansible 설정 파일에 이렇게 설정할 수 있어요.
remote_tmp=$HOME/.ansible/tmp

Ansible 2.5 이상에서는 인벤토리에서 호스트별로도 설정할 수 있어요.

solaris1 ansible_remote_tmp=$HOME/.ansible/tmp
  • ansible_shell_executable을 POSIX 호환 셸의 경로로 설정할 수 있어요. 예를 들어 많은 Solaris 호스트는 /usr/xpg4/bin/sh에 POSIX 셸이 있으므로 인벤토리에서 이렇게 설정할 수 있어요.
solaris1 ansible_shell_executable=/usr/xpg4/bin/sh

(bash, ksh, zsh도 설치되어 있다면 POSIX 호환일 거예요.)

z/OS에서 실행하기

  • 일반적으로 z/OS는 Ansible 컨트롤 노드로 사용할 수 없어요. 자세한 내용은 'z/OS를 컨트롤 노드로 사용하기' 문서를 참고해요.
  • 대상 호스트의 기본 위치에서 Python 인터프리터 경로를 찾지 못하면 다음 오류가 발생할 수 있어요.

오류 /usr/bin/python: FSUM7351 not found

Ansible은 원격 호스트에서 모듈을 실행하려면 Python 인터프리터가 필요하며, '기본' 경로 /usr/bin/python에서 확인해요. z/OS에서는 Python 3 인터프리터(IBM Open Enterprise SDK for Python)가 종종 다른 경로, 일반적으로 /usr/lpp/cyp/v3r12/pyz 같은 곳에 설치돼요.

Python 인터프리터의 경로는 Ansible 인벤토리 변수 ansible_python_interpreter로 구성할 수 있어요. 예를 들어:

zos1 ansible_python_interpreter:/usr/lpp/cyp/v3r12/pyz

자세한 내용은 '원격 머신의 /usr/bin/python에 Python 인터프리터가 없으면 어떻게 하나요?'를 참고해요.

  • ANSIBLE_PIPELINING이 활성화되지 않았거나, Ansible 파이프라이닝이 활성화됐지만 PYTHONSTDINENCODING 속성이 올바르게 설정되지 않았을 때 다음 오류가 발생할 수 있어요.

오류 SyntaxError: Non-UTF-8 code starting with '\x81' in file <stdin> on line 1, but no encoding declared; see https://peps.python.org/pep-0263/ for details

아래의 16진수 '\x81'은 오류를 일으키는 소스에 따라 달라질 수 있어요.

Ansible 파이프라이닝이 활성화되면 Ansible은 모든 모듈 코드를 Python의 stdin 파이프를 통해 원격 대상으로 전달하고 한 번의 호출로 실행해요. 파이프라이닝에 대한 자세한 내용은 'Pipelining' 문서를 참고해요.

z/OS 관리 노드에서 수행하는 모든 태스크의 환경에 다음을 포함하세요.

PYTHONSTDINENCODING: "cp1047"
  • 특정 언어 환경(LE) 구성은 z/OS 시스템(IBM Open Enterprise SDK for Python)에서 Python이 요구하는 자동 변환과 자동 파일 태깅 기능을 활성화해요. z/OS 관리 노드의 원격 환경을 설정할 때 다음 구성을 포함하세요.
_BPXK_AUTOCVT: "ON"
_CEE_RUNOPTS: "FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)"

_TAG_REDIR_ERR: "txt"
_TAG_REDIR_IN: "txt"
_TAG_REDIR_OUT: "txt"

Ansible은 다음 옵션으로 원격 환경 변수를 구성할 수 있어요.

  • 인벤토리 — inventory.yml, group_vars/all.yml 또는 host_vars/all.yml
  • 플레이북 — 플레이북 맨 위의 environment 변수
  • 블록 또는 태스크 — environment 키워드

자세한 내용은 '원격 환경 설정하기' 문서를 참고해요.

'z/OS UNIX 호스트를 Ansible로 관리하기' 문서를 참고하세요.

fakeroot에서 실행하기

fakeroot는 기본적으로 완전하거나 POSIX 호환인 시스템을 만들지 않기 때문에 몇 가지 문제가 발생해요. Ansible이 쓰는 기본 tmp 디렉토리(~/.ansible/tmp)를 올바르게 확장하지 못하는 것으로 알려져 있어요. 모듈 실패가 보인다면 이것이 원인일 가능성이 높아요. 간단한 해결책은 올바르게 확장되는 경로로 remote_tmp를 설정하는 거예요(사용 중인 셸 플러그인 문서 참고).

예를 들어 ansible 설정 파일(또는 환경 변수를 통해)에서 이렇게 설정할 수 있어요.

remote_tmp=$HOME/.ansible/tmp

콘텐츠를 재사용/재배포 가능하게 만드는 가장 좋은 방법은 무엇인가요?

아직 안 했다면 플레이북 문서에서 '롤(Roles)'에 대해 모두 읽어 보세요. 이것은 플레이북 콘텐츠를 독립적으로 만들 수 있게 해주며, 콘텐츠를 공유하기 위한 Git 서브모듈 같은 것과 잘 작동해요.

이 플러그인 유형들 중 일부가 낯설게 보인다면, Ansible을 확장하는 방법에 대한 자세한 내용은 API 문서를 참고해요.

설정 파일은 어디에 있고 무엇을 구성할 수 있나요?

'Ansible 구성하기(Configuring Ansible)' 문서를 참고하세요.

cowsay를 어떻게 비활성화하나요?

cowsay가 설치되어 있으면 Ansible은 플레이북을 실행할 때 여러분의 하루를 더 즐겁게 만들려고 합니다. 전문적인 소 없는 환경에서 일하고 싶다면, cowsay를 제거하거나 ansible.cfg에서 nocows=1을 설정하거나 ANSIBLE_NOCOWS 환경 변수를 설정하면 돼요.

export ANSIBLE_NOCOWS=1

모든 ansible_ 변수 목록을 어떻게 볼 수 있나요?

Ansible은 기본으로 관리 대상 머신에 대한 '팩트(facts)'를 수집하며, 이 팩트들은 플레이북과 템플릿에서 접근할 수 있어요. 머신에 대해 사용 가능한 모든 팩트의 목록을 보려면 setup 모듈을 임시 액션(ad hoc)으로 실행할 수 있어요.

ansible -m setup hostname

이것은 해당 호스트에 대해 사용 가능한 모든 팩트의 딕셔너리를 출력해요. 출력을 페이저로 파이프하고 싶을 수도 있겠네요. 이것은 인벤토리 변수나 내부 '마법' 변수는 포함하지 않아요. '팩트' 이상이 필요하다면 다음 질문을 참고하세요.

호스트에 대해 정의된 모든 인벤토리 변수를 어떻게 볼 수 있나요?

다음 명령을 실행하면 호스트의 인벤토리 변수를 볼 수 있어요.

ansible-inventory --list --yaml

호스트에 특화된 모든 변수를 어떻게 볼 수 있나요?

팩트와 다른 원천을 포함할 수 있는 모든 호스트 특화 변수를 보려면:

ansible -m debug -a "var=hostvars['hostname']" localhost

팩트 캐시를 사용하지 않는다면, 위 태스크에 포함된 팩트를 위해 먼저 팩트를 수집하는 play를 사용해야 해요.

템플릿 안에서 그룹의 호스트 목록을 어떻게 반복하나요?

호스트 그룹 안의 호스트 목록을 반복하는 것은 꽤 흔한 패턴이에요. 서버 목록으로 템플릿 설정 파일을 채우기 위해서죠. 이렇게 하려면 템플릿에서 "$groups" 딕셔너리에 접근하면 돼요.

{% for host in groups['db_servers'] %}
    {{ host }}
{% endfor %}

이들 호스트의 팩트(예: 각 호스트 이름의 IP 주소)에 접근해야 한다면 팩트가 채워졌는지 확인해야 해요. 예를 들어 db_servers와 통신하는 play가 있는지 확인하세요.

- hosts:  db_servers
  tasks:
    - debug: msg="doesn't matter what you do, just that they were talked to previously."

그런 다음 템플릿 안에서 팩트를 이렇게 사용할 수 있어요.

{% for host in groups['db_servers'] %}
   {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}

변수 이름을 프로그래밍 방식으로 어떻게 접근하나요?

임의의 인터페이스의 ipv4 주소를 가져와야 하는데, 사용할 인터페이스가 롤 매개변수나 다른 입력으로 제공될 수 있는 경우가 있어요. 변수 이름은 "~"로 문자열을 더해서 만들 수 있어요.

{{ hostvars[inventory_hostname]['ansible_' ~ which_interface]['ipv4']['address'] }}

hostvars를 거치는 요령이 필요한 이유는 hostvars가 변수 전체 네임스페이스의 딕셔너리이기 때문이에요. inventory_hostname은 호스트 루프에서 현재 반복 중인 호스트를 나타내는 마법 변수예요.

위 예시에서 인터페이스 이름에 대시가 있다면 밑줄로 바꿔야 해요.

{{ hostvars[inventory_hostname]['ansible_' ~ which_interface | replace('_', '-') ]['ipv4']['address'] }}

dynamic_variables 문서도 참고하세요.

그룹 변수에 어떻게 접근하나요?

기술적으로는 직접 접근하지 않아요. Ansible은 실제로 그룹을 직접 사용하지 않아요. 그룹은 호스트 선택을 위한 레이블이자 변수를 일괄 할당하는 방법일 뿐이며, 일급 엔티티가 아니에요. Ansible은 호스트와 태스크만 신경 써요.

그렇긴 해도 그룹의 일부인 호스트를 선택해 변수에 접근할 수는 있어요. 아래의 '그룹의 첫 번째 호스트 변수' 예시를 참고하세요.

그룹의 첫 번째 호스트의 변수에 어떻게 접근하나요?

webservers 그룹의 첫 웹서버 ip 주소를 원한다면 어떻게 할까요? 그것도 할 수 있어요. 동적 인벤토리를 사용한다면 어떤 호스트가 '첫 번째'인지 일관되지 않을 수 있으니, 인벤토리가 정적이고 예측 가능하지 않다면 이런 방식은 피하는 게 좋아요. (AWX나 Red Hat Ansible Automation Platform을 사용한다면 데이터베이스 순서를 사용하므로, 클라우드 기반 인벤토리 스크립트를 써도 문제가 되지 않아요.)

어쨌든, 요령은 이래요.

{{ hostvars[groups['webservers'][0]]['ansible_eth0']['ipv4']['address'] }}

webservers 그룹의 첫 번째 머신의 호스트 이름을 꺼내 오는 방식이 보이죠. 템플릿에서 이걸 하고 있다면 Jinja2의 '#set' 지시어로 단순화할 수 있고, 플레이북에서는 set_fact로도 할 수 있어요.

- set_fact: headnode={{ groups['webservers'][0] }}

- debug: msg={{ hostvars[headnode].ansible_eth0.ipv4.address }}

괄호 문법을 점으로 바꾼 것을 볼 수 있는데, 이것은 어디서든 할 수 있어요.

파일을 대상 호스트에 재귀적으로 복사하려면 어떻게 하나요?

copy 모듈에 recursive 매개변수가 있어요. 하지만 많은 수의 파일에 대해 더 효율적인 작업을 원한다면 synchronize 모듈을 살펴보세요. synchronize 모듈은 rsync를 감싸요. 두 모듈 모두에 대한 정보는 모듈 인덱스를 참고하세요.

셸 환경 변수에 어떻게 접근하나요?

컨트롤 노드 머신에서: 컨트롤 노드의 기존 변수에 접근하려면 env lookup 플러그인을 사용해요. 예를 들어 관리 머신에서 HOME 환경 변수의 값을 접근하려면:

---
# ...
  vars:
     local_home: "{{ lookup('env','HOME') }}"

대상 머신에서: 환경 변수는 ansible_env 변수의 팩트를 통해 사용할 수 있어요.

{{ ansible_env.HOME }}

태스크 실행을 위한 환경 변수를 설정해야 한다면 '고급 플레이북' 섹션의 '원격 환경 설정하기'를 참고하세요. 대상 머신에 환경 변수를 설정하는 방법은 여러 가지가 있어요. template, replace, 또는 lineinfile 모듈을 사용해 파일에 환경 변수를 넣을 수 있어요. 편집할 정확한 파일은 OS·배포판·로컬 구성에 따라 달라요.

user 모듈용 암호화된 비밀번호를 어떻게 생성하나요?

Ansible 임시 명령이 가장 쉬운 옵션이에요.

ansible all -i localhost, -m debug -a "msg={{ 'mypassword' | password_hash('sha512', 'mysecretsalt') }}"

대부분의 Linux 시스템에서 사용할 수 있는 mkpasswd 유틸리티도 좋은 옵션이에요.

mkpasswd --method=sha-512

openssl 유틸리티도 또 다른 좋은 옵션이에요.

openssl passwd -6 -noverify

이것은 salt 값 없이 기본 라운드 5000으로 비밀번호의 SHA512 해시를 생성해요. 더 많은 옵션은 openssl passwd 문서를 확인하세요.

통합된 '문자열·비밀번호 해싱 및 암호화'를 사용해 비밀번호의 해시 버전을 생성할 수도 있어요. 플레이북이나 host_vars에 평문 비밀번호를 넣지 말고, '암호화된 변수와 파일 사용하기'를 사용해 민감한 데이터를 암호화하세요.

OpenBSD에서는 기본 시스템에 encrypt(1)이라는 비슷한 옵션이 있어요.

Ansible은 변수에 점 표기법과 배열 표기법을 허용해요. 어떤 표기법을 써야 하나요?

점 표기법은 Jinja에서 왔고 특수 문자가 없는 변수에는 잘 동작해요. 변수에 점(.), 콜론(:), 대시(-)가 있거나, 키가 밑줄 두 개로 시작하고 끝나거나, 키가 알려진 공공 속성들 중 하나를 사용한다면 배열 표기법을 쓰는 것이 더 안전해요. 알려진 공공 속성 목록은 '변수 사용하기' 문서를 참고하세요.

item[0]['checksum:md5']
item['section']['2.1']
item['region']['Mid-Atlantic']
It is {{ temperature['Celsius']['-3'] }} outside.

또한 배열 표기법은 동적 변수 구성을 허용해요. dynamic_variables 문서를 참고하세요.

'점 표기법'의 또 다른 문제는 일부 키가 Python 딕셔너리의 속성·메서드와 충돌해서 문제를 일으킬 수 있다는 점이에요.

  • item이 딕셔너리일 때 잘못된 문법의 예시:
item.update

이 변형은 update()가 딕셔너리의 Python 메서드이기 때문에 문법 오류를 일으켜요.

  • 올바른 문법의 예시:
item['update']

변수에서 태스크 인자를 일괄 설정하는 것이 언제 안전하지 못한가요?

딕셔너리 타입 변수에서 태스크의 모든 인자를 설정할 수 있어요. 이 기법은 일부 동적 실행 시나리오에서 유용할 수 있어요. 하지만 보안 위험을 도입해요. 권장하지 않으므로, Ansible은 이런 작업을 할 때 경고를 발행해요.

#...
vars:
  usermod_args:
    name: testuser
    state: present
    update_password: always
tasks:
- user: '{{ usermod_args }}'

이 특정 예시는 안전해요. 하지만 이렇게 태스크를 구성하는 것은 위험한데, usermod_args에 전달된 인자와 값이 손상된 대상 머신의 hostfacts에 있는 악의적인 값으로 덮어써질 수 있기 때문이에요. 이 위험을 완화하려면:

  • '변수 우선순위: 변수는 어디에 두어야 하나요?'에서 찾을 수 있는 우선순위 순서에 따라 hostfacts보다 높은 우선순위 레벨에서 일괄 변수를 설정하세요(위 예시는 play vars가 팩트보다 우선하므로 안전해요).
  • INJECT_FACTS_AS_VARS 설정을 비활성화해 팩트 값이 변수와 충돌하지 않게 하세요(이것은 원래 경고도 비활성화해요).

Ansible 교육을 받을 수 있나요?

네! 우리의 서비스와 교육 제공에 대한 정보는 서비스 페이지를 참고하세요. 더 자세한 내용은 [email protected]으로 이메일을 보내세요. 또한 정기적으로 무료 웹 기반 교육 클래스를 제공해요. 예정된 웨비나에 대한 더 많은 정보는 웨비나 페이지를 참고하세요.

웹 인터페이스 / REST API / GUI가 있나요?

네! 오픈소스 웹 인터페이스는 Ansible AWX예요. Ansible을 더 강력하고 쉽게 만들어 주는 지원되는 Red Hat 제품은 Red Hat Ansible Automation Platform이에요.

플레이북에서 비밀 데이터를 어떻게 지키나요?

Ansible 콘텐츠에 비밀 데이터를 두면서도 공개적으로 공유하거나 소스 컨트롤에 두고 싶다면 '암호화된 변수와 파일 사용하기' 문서를 참고하세요.

-v(verbose) 모드에서 결과나 주어진 명령을 보여주고 싶지 않은 태스크가 있다면, 다음 태스크 또는 플레이북 속성이 유용할 수 있어요.

- name: secret task
  shell: /usr/bin/do_something --value={{ secret_value }}
  no_log: True

이것은 verbose 출력을 유지하면서 민감한 정보를, 그렇지 않았으면 출력을 보고 싶어하는 다른 사람들로부터 숨기기 위해 사용할 수 있어요.

no_log 속성은 전체 play에도 적용할 수 있어요.

- hosts: all
  no_log: True

하지만 이렇게 하면 play 디버깅이 다소 어려워져요. 플레이북이 완성된 뒤에는 단일 태스크에만 적용하는 것을 권장해요. no_log 속성 사용이 ANSIBLE_DEBUG 환경 변수를 통해 Ansible 자체를 디버깅할 때 데이터가 표시되는 것을 막지는 않는다는 점을 기억하세요.

언제 {{ }}를 사용해야 하나요? 그리고 변수 또는 동적 변수 이름을 어떻게 보간하나요?

확고한 규칙은 'when:을 제외하고는 항상 {{}}를 사용하라'는 거예요. 조건문은 항상 Jinja2를 통해 실행되어 식을 해석하므로, when:, failed_when:, changed_when:은 항상 템플릿화되므로 {{}}를 추가하는 것을 피해야 해요.

대부분의 다른 경우에는 이전에 변수를 지정하지 않고 사용할 수 있었더라도(예: loop 또는 with_ 절) 항상 괄호를 사용해야 해요. 그렇게 하지 않으면 정의되지 않은 변수와 문자열을 구분하기 어려워졌거든요.

또 다른 규칙은 '콧수염(moustaches)은 쌓이지 않는다'는 거예요. 우리는 종종 이걸 봐요.

{{ somevar_{{other_var}} }}

위 코드는 기대한 대로 동작하지 않아요. 동적 변수를 사용해야 한다면 상황에 맞게 다음 중 하나를 사용하세요.

{{ hostvars[inventory_hostname]['somevar_' ~ other_var] }}

'비 호스트 변수'에는 vars lookup 플러그인을 사용할 수 있어요.

{{ lookup('vars', 'somevar_' ~ other_var) }}

키워드가 {{}}를 요구하는지, 아니면 템플릿화를 지원하는지 확인하려면 ansible-doc -t keyword <name>을 사용해요. 이것은 키워드 문서를 반환하는데, 값이 explicit({{}} 요구), implicit({{}} 가정, 필요 없음), static(템플릿화 미지원, 모든 문자가 문자 그대로 해석)인 template 필드를 포함해요.

태스크를 위임했을 때 원래 ansible_host를 어떻게 얻나요?

문서에서 말하듯이 연결 변수는 delegate_to 호스트에서 가져오므로 ansible_host는 덮어써져요. 하지만 hostvars를 통해 원래 값을 여전히 접근할 수 있어요.

original_host: "{{ hostvars[inventory_hostname]['ansible_host'] }}"

이것은 ansible_user, ansible_port처럼 덮어써지는 모든 연결 변수에 동작해요.

파일을 가져올 때 'protocol error: file name does not match request'를 어떻게 고치나요?

OpenSSH 7.9p1 릴리스부터 SCP 클라이언트에 버그가 있는데, SCP를 파일 전송 메커니즘으로 사용할 때 Ansible 컨트롤 노드에서 이 오류가 발생할 수 있어요.

오류 failed to transfer file to /tmp/ansible/file.txt protocol error: file name does not match request

이 릴리스들에서 SCP는 가져올 파일의 경로가 요청된 경로와 일치하는지 검증하려고 해요. 원격 파일 이름이 경로의 공백이나 비-ASCII 문자를 이스케이프하기 위해 따옴표가 필요하면 검증이 실패해요. 이 오류를 피하려면:

  • SFTP를 사용하고 있는지 확인하세요. SFTP는 보안, 속도, 신뢰성 면에서 최적의 전송 방법이에요. 다음 중 하나를 하고 있는지 확인해 보세요.

    • 기본 설정인 smart에 의존하는 것. ssh_transfer_method가 어디에도 명시적으로 설정되지 않았다면 이렇게 동작해요.
    • 인벤토리에 호스트 변수나 그룹 변수 설정: ansible_ssh_transfer_method: smart
    • 컨트롤 노드의 환경 변수 설정: export ANSIBLE_SSH_TRANSFER_METHOD=smart
    • Ansible을 실행할 때 환경 변수 전달: ANSIBLE_SSH_TRANSFER_METHOD=smart ansible-playbook
    • ansible.cfg 파일 수정: [ssh_connection] 섹션에 ssh_transfer_method=smart 추가.

    smart 설정은 전송에 sftp를 시도한 다음 scp, 그 다음 dd로 폴백해요. SFTP를 사용할 수 없을 때 전송이 실패하길 원한다면 [ssh_connection] 섹션에 ssh_transfer_method=sftp를 추가하세요.

  • SCP를 꼭 사용해야 한다면 -T 인자를 설정해 SCP 클라이언트가 경로 검증을 무시하게 하세요. 다음 중 한 가지로 할 수 있어요.

    • 호스트 변수나 그룹 변수 설정: ansible_scp_extra_args=-T
    • 환경 변수 export 또는 전달: ANSIBLE_SCP_EXTRA_ARGS=-T
    • ansible.cfg 파일 수정: [ssh_connection] 섹션에 scp_extra_args=-T 추가.

참고: -T를 사용할 때 invalid argument 오류가 보인다면, SCP 클라이언트가 파일 이름 검증을 수행하지 않는 것이므로 이 오류가 발생하지 않아요.

Ansible은 다중 요소 인증 2FA/MFA/생체인식/지문/USB키/OTP 등을 지원하나요?

아니요. Ansible은 여러 태스크를 여러 대상에 대해 실행하도록 설계되어 사용자 상호작용을 최소화해요. 대부분의 자동화 도구처럼, 인간 상호작용을 다루도록 설계된 대화형 보안 시스템과는 호환되지 않아요. 이런 시스템 대부분은 대상별로 이차 프롬프트를 요구해서 수천 개의 대상으로 확장하는 것을 막아요. 또한 만료 기간이 매우 짧아서 빈번한 재인증이 필요한데, 이것도 많은 호스트나 긴 태스크 집합에서 문제가 돼요.

이런 환경에서는 Ansible 실행 주변을 보호하면서도 그런 조치가 필요 없는 '자동화 사용자'를 쓰게 하는 것을 권장해요. AWX나 Red Hat Ansible Automation Platform을 사용하면 관리자가 인벤토리에 대한 RBAC 접근을 설정하고 자격 증명과 작업 실행을 관리할 수 있어요.

'validate' 옵션만으로는 부족해요. 어떻게 해야 하나요?

파일을 만들거나 갱신하는 많은 Ansible 모듈에는 validate 옵션이 있어 검증 명령이 실패하면 업데이트를 중단하게 해요. 이것은 최종 업데이트 전에 Ansible이 만드는 임시 파일을 사용해요. 많은 경우 특정 애플리케이션의 검증 도구가 특정 이름, 여러 파일 또는 이 단순한 기능에는 없는 다른 요소를 요구하기 때문에 이 방법이 동작하지 않아요.

이런 경우 검증과 복원을 직접 처리해야 해요. 다음은 block/rescue와 백업으로 이를 처리하는 간단한 예시인데, 대부분의 파일 기반 모듈이 지원해요.

- name: maintain config and backout if validation after change fails
  block:
    - name: do the actual update, works with copy, lineinfile and any action that allows for `backup`.
      template: src=template.j2 dest=/x/y/z backup=yes moreoptions=stuff
      register: updated

    - name: run validation, this will change a lot as needed. We assume it returns an error when not passing, use `failed_when` if otherwise.
      shell: run_validation_commmand
      become: true
      become_user: requiredbyapp
      environment:
        WEIRD_REQUIREMENT: 1
      when: updated is changed
  rescue:
    - name: restore backup file to original, in the hope the previous configuration was working.
      copy:
         remote_src: true
         dest: /x/y/z
         src: "{{ updated['backup_file'] }}"
      when: updated is changed
  always:
    - name: We choose to always delete backup, but could copy or move, or only delete in rescue.
      file:
         path: "{{ updated['backup_file'] }}"
         state: absent
      when: updated is changed

문서에 변경 사항을 어떻게 제출하나요?

Ansible 문서는 ansible/ansible-documentation 프로젝트 Git 저장소에 보관되어 있어요. 자세한 내용은 'Ansible 문서에 기여하기' 문서를 참고하세요.

ansible.legacyansible.builtin 컬렉션의 차이는 무엇인가요?

둘 다 실제 컬렉션은 아니에요. 코어 엔진이 가상으로 구성한 것(합성 컬렉션)이에요.

ansible.builtin 컬렉션은 ansible-core와 함께 제공되는 플러그인만 가리켜요.

ansible.legacy 컬렉션은 ansible.builtin의 상위 집합이에요(builtin의 플러그인을 ansible.legacy를 통해 참조할 수 있어요). 또한 구성된 경로와 인접 디렉토리에 '커스텀' 플러그인을 추가하고, 같은 이름을 가진 builtin 플러그인을 덮어쓸 수 있는 기능도 얻을 수 있어요.

또한 ansible.legacy는 FQCN을 지정하지 않을 때 기본으로 얻게 되는 것이에요. 그래서 이것은:

- shell: echo hi

실제로는 이것과 동등해요.

- ansible.legacy.shell: echo hi

물론 shell 모듈을 덮어쓰지 않았다면 ansible.builtin.shell로 쓸 수도 있어요. legacy가 builtin 컬렉션으로 해석되기 때문이에요.

내 질문이 여기에 없어요

질문에 대한 답을 찾지 못했다면 커뮤니티에 물어보세요! 자세한 내용은 Ansible 커뮤니케이션 가이드를 방문해 보세요.

더 알아보기 (Learn more)

  • 플레이북에 대한 소개는 플레이북 다루기 문서를 확인해요.
  • 플레이북에 대한 팁과 요령은 Ansible 팁과 요령 문서를 확인해요.
  • 질문이나 도움이 필요하면 Ansible 커뮤니케이션 가이드를 방문해 보세요.