Ansible이란?
Ansible은 IaC( Infrastructure as Code, 인프라스트럭처를 코드로 관리) 오픈 소스 IT 자동화 도구이다.
Yaml을 기반으로 비교적 쉬운 문법과 다양한 관리 기능을 제공하여 서버 관리, 설정 관리, 애플리케이션 배포 등 다양한 작업을 자동화할 수 있다.
IaC는 다양한 도구가 있는데, 개인적으로 몇가지 특징을 정리하다가
이미 표 정리가 잘되어있는 글이 있어 아래에 추가하였다. (상세한 내용은 출처의 글에 잘 작성되어있다)
위의 표에 추가로 내가 이전에 Linux 환경에서 Ansible을 쓰면서 느꼈던 특징을 작성하였다.
- Agent가 없는 구조로 관리 대상 서버에 별도의 Agent가 구동하지 않는다.
- 다만, Python 설치가 Control Node와 Managed Node 모두 필수이며,
Managed Node로 명령을 전달하기 위한 SSH나 WinRM 등 접속 프로토콜과 인증이 필요하다. - Yaml을 사용하기에 K8S나 Pipeline 등에 익숙한 엔지니어라면 쉽게 적응이 가능
- 다양한 모듈을 제공하며, 이에 대한 사용법 예시가 문서로 잘 정리되어있다.
Windows에서의 Ansible
나는 개인적으로 여러 회사를 다니면서 대부분 Linux 환경에서 개발/작업을 해왔기 때문에,
Ansible도 당연히 Linux에서만 사용해왔다.
하지만 최근 재직 중인 회사에서는 Devops로 근무하면서 Windows 환경에 Visual Studio를 사용하게 되면서,
기존에 사용하던 여러 도구(Container, vim, K8S 등)를 Windows 환경에서 테스트하고 사용해야 하는 경우가 자주 생겼다.
그래서 이번에 진행하는 Ansible 스터디도 당연히 Windows 환경을 포함해서 테스트를 진행하였고,
테스트 중 알게된 제약사항이 있었다.
Control Node는 Window에서 지원되지 않음
나는 당연히 Ansible이 python을 기반으로 만들어졌고 동작하기에 Windows를 지원할 거라고 생각했고
스터디 전에 간단한 검색을 해봤을 때도 Windows의 WinRM이라는 프로토콜을 통해서 관리하는 블로그 글을 꽤 볼 수 있었다.
하지만 공식 문서를 확인해보니 Windows는 Control Node로는 지원되지 않는다는 것을 알게되었다.
거기에 추가로 WSL(Windows Subsystem for Linux)에서 Ansible을 사용하는 것도 Production에서는 권장하지 않는다고 한다.
Windows에 Ansible 설치 시도
python을 사용하는데 왜 굳이 이렇게까지 안된다고 강조를 해놨을까 궁금해서 Windows 10 Pro 22H2(19045.3930)에 한번 설치를 시도해봤다.
C:\Users\user\Desktop\ansible>pip install ansible
Collecting ansible
Obtaining dependency information for ansible from https://files.pythonhosted.org/packages/32/f0/f7a66e975b9edf511e7223fe8d5642b16f1147087d4acc3d1be788267b41/ansible-9.1.0-py3-none-any.whl.metadata
Downloading ansible-9.1.0-py3-none-any.whl.metadata (7.9 kB)
...
...
Downloading cffi-1.16.0-cp312-cp312-win_amd64.whl (181 kB)
---------------------------------------- 182.0/182.0 kB 11.4 MB/s eta 0:00:00
Downloading MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl (16 kB)
Installing collected packages: resolvelib, PyYAML, pycparser, packaging, MarkupSafe, jinja2, cffi, cryptography, ansible-core, ansible
Successfully installed MarkupSafe-2.1.3 PyYAML-6.0.1 ansible-9.1.0 ansible-core-2.16.2 cffi-1.16.0 cryptography-41.0.7 jinja2-3.1.3 packaging-23.2 pycparser-2.21 resolvelib-1.0.1
[notice] A new release of pip is available: 23.2.1 -> 23.3.2
[notice] To update, run: python.exe -m pip install --upgrade pip
설치는 무난하게 잘 되는데, 단순 help를 보려고 명령어를 실행하면 아래처럼 에러가 발생한다.
C:\Users\user\Desktop\ansible>ansible
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "C:\Python312\Scripts\ansible.exe\__main__.py", line 4, in <module>
File "C:\Python312\Lib\site-packages\ansible\cli\__init__.py", line 42, in <module>
check_blocking_io()
File "C:\Python312\Lib\site-packages\ansible\cli\__init__.py", line 34, in check_blocking_io
if not os.get_blocking(fd):
^^^^^^^^^^^^^^^^^^^
OSError: [WinError 1] 잘못된 기능입니다
실제 설치한 ansible package 소스를 보면 아래와 같이 ansible CLI에서 사전에 stdin/stdout/stderr를 확인해 block I/O를 사용하는지 점검하고 있다.
block I/O란 간단하게 개념만 설명하면 I/O 작업을 진행하는 중에 사용자 프로세스가 해당 I/O작업이 끝날 때까지 다른 작업을 하지 못하도록하고 기다리게하는 방식이다.
# C:\Python312\Lib\site-packages\ansible\cli\__init__.py
def check_blocking_io():
"""Check stdin/stdout/stderr to make sure they are using blocking IO."""
handles = []
for handle in (sys.stdin, sys.stdout, sys.stderr):
# noinspection PyBroadException
try:
fd = handle.fileno()
except Exception:
continue # not a real file handle, such as during the import sanity test
if not os.get_blocking(fd):
handles.append(getattr(handle, 'name', None) or '#%s' % fd)
if handles:
raise SystemExit('ERROR: Ansible requires blocking IO on stdin/stdout/stderr. '
'Non-blocking file handles detected: %s' % ', '.join(_io for _io in handles))
check_blocking_io()
Ansible은 네트워크를 통해 명령을 내리고 해당 명령이 완료될 때까지 기다려야하므로, socket fd의 I/O blocking에 대해서는 어느정도 납득이 가는 조치이다. 하지만 나는 Windows에서 돌려보고 싶었기 때문에 해당 점검 함수를 주석처리하고 다시 실행해보았다.
C:\Users\user\Desktop\ansible>ansible
ERROR: Ansible requires the locale encoding to be UTF-8; Detected 949.
실행은 되는데 인코딩 점검에서 에러가 난다. 한글과 같은 다양한 문자를 커버하면 유지보수 범위가 넓어지니 이것도 이해할만한 내용이다. 인코딩을 바꾸려고 보니 chcp와 같은 명령어로는 적용되지 않아서, 해당 부분도 주석처리해보았다.
C:\Windows\system32>ansible
ERROR: No module named 'fcntl'
fcntl는 Linux/Unix 시스템에서 파일 Locking 등에 사용되는 녀석인데, Python에서도 Linux밖에 지원이 안되는 것 같다.
https://docs.python.org/ko/3/library/fcntl.html
사용되는 소스코드를 보면 Socket 파일 Locking에 사용하고 있는 것을 볼 수 있었다.
# C:\Python312\Lib\site-packages\ansible\cli\scripts\ansible_connection_cli_stub.py
...
@contextmanager
def file_lock(lock_path):
"""
Uses contextmanager to create and release a file lock based on the
given path. This allows us to create locks using `with file_lock()`
to prevent deadlocks related to failure to unlock properly.
"""
lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o600)
fcntl.lockf(lock_fd, fcntl.LOCK_EX)
yield
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
os.close(lock_fd)
...
socket_path = unfrackpath(cp % dict(directory=tmp_path))
lock_path = unfrackpath("%s/.ansible_pc_lock_%s" % os.path.split(socket_path))
with file_lock(lock_path):
if not os.path.exists(socket_path):
messages.append(('vvvv', 'local domain socket does not exist, starting it'))
original_path = os.getcwd()
r, w = os.pipe()
pid = fork_process()
if pid == 0:
try:
os.close(r)
wfd = os.fdopen(w, 'w')
process = ConnectionProcess(wfd, play_context, socket_path, original_path, task_uuid, ansible_playbook_pid)
process.start(options)
이렇게 Linux/Unix를 기반으로 가정하고 개발했기 때문에 Windows에서 Control Node로는 동작 자체가 불가능하다는 것을 알 수 있다.
WSL에서 Ansible Production을 권장하지 않음
이 부분도 잘 이해가 되지 않았는데 몇 년 전부터 github에서 논의된 내용을 보면 WSL자체가 개발용으로 제공되는 것이기 때문에 그런 것으로 보이며, WSL이 아무래도 Windows에서 동작하는 것이다보니 별의 별 이슈가 발생하기 때문인 것으로 보인다.
https://learn.microsoft.com/ko-kr/windows/wsl/troubleshooting
(추가) Ansible의 동작으로 인한 Windows 제약
위의 테스트 후에 스터디 도전과제를 좀 하다가 fcntl 빼면 그래도 돌아가지 않을까? 하는 생각이 갑자기 들어서 도전과제는 때려치고 ansible package 코드를 수정해서 동작시켜보려고 하면서 얻은 삽질이다.
결론은 그래도 Windows에서는 안된다.
개인적인 생각으로 가장 문제가 되는 부분은 Ansible 대부분의 동작 원리가 fork로 동작하는 것이라는 점이다.
이는 Control Node와 Managed Node 양쪽 모두에게 적용되는데,
실제 ansible playbook을 통해 관리 작업을 수행해보면 ansible-playbook 하나만 생겼다가 하위 process가
아래와 같이 process fork를 통해서 생성되고 fork된 process가 작업을 수행하고 종료한다.
Ansible 코드를 보면 명령어 실행 등 주요 동작이 fork에 크게 의존한다는 것을 볼 수 있다.
이는 Linux/Unix와 Windows간의 동작에 중요한 차이점으로 일단 Windows에는 fork가 없다.
다만 유사한 동작을 하는 Windows API가 CreateProcess가 있는데 fork와는 큰 차이점이 있다.
- 공유 메모리 및 상태
- fork: 부모와 자식 프로세스가 메모리 및 상태를 공유.
- CreateProcess: 부모와 자식 프로세스는 별도의 메모리 및 상태를 가짐.
- PID 반환
- fork: 부모에서는 자식 프로세스의 PID를 반환하고, 자식에서는 0을 반환.
- CreateProcess: 부모와 자식 프로세스는 각각 다른 PID를 가짐.
이 fork는 사실 Linux 프로그램을 Windows 포팅할 때 가장 애먹는 부분이며,
이러한 점들로 인해 Windows에서 Ansible의 Control Node 사용은 Windows용으로 Ansible을 새로 만드는게 아니면 기대하지 않는게 좋을 것 같다고 생각한다.
이러한 차이점 때문에 Windows를 Managed Node로 지원하는 것도 늦게 지원되고, 현재도 다양한 이슈가 나오는 것이라고 볼 수 있다.
- fork에 대한 상세 동작을 보고 싶을 때 참고자료 : https://www.sysnet.pe.kr/2/0/12811#ref_list
Windows 환경에서의 Ansible 테스트 내용 요약
- Ansible은 Windows에서는 Control Node로 동작하지 않는다.
- stdin/stdout/stderr를 통한 I/O Blocking을 지원해야 한다.
- Linux/Unix 전용인 fcntl 모듈을 사용하기 때문에 Windows에서는 사용이 불가능하다.
- (추가) Ansible 동작 자체가 fork에 크게 의존하기 때문에 Windows에서 동작시키기가 불가능하다.
- Ansible은 UTF-8 인코딩이 필수로 요구된다.
Windows를 Managed Node로 사용할 때 주의점
- SSH로 컨트롤 하고 싶을 경우 최신 버전의 openssh를 설치해야 한다.
- Windows 기능추가로 설치하면 너무 오래된 버전이 설치되어서 지원이 안된다.
- 또한 실험기능이라고 명시했기에 이슈가 발생할 수 있음을 염두에 둔다.
(대부분의 블로그 자료는 WinRM을 사용하고 있다.) - https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH
- 파일 경로에 주의할 것
- python 이기 때문에 C:\Temp가 아닌 C:\\또는 C:/Temp를 사용해야 한다.
- https://docs.ansible.com/ansible/latest/os_guide/windows_usage.html#path-formatting-for-windows
참고용 Window test yml
hosts: windows
vars:
ansible_connection: ssh
ansible_port: 22
ansible_shell_type: cmd
tasks:
- name: test powershell
win_shell: |
get-host
register: result_get_host
- name: display result_get_host
debug:
var: result_get_host
# 실행 시
ansible-playbook test.yml -u user -k
# 실행 결과 예시
root@ersia:~/ansible# root@ersia:~/ansible# ansible-playbook test.yml -u user -k
SSH password:
PLAY [windows] *************************************************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************************************************
[WARNING]: Error during machine sid retrieval: "0"개의 인수가 있는 "FindOne"을(를) 호출하는 동안 예외가 발생했습니다. "대상 컴퓨터의 단층 이름을 검색할 수 없습 니다(2138)."
...
...
TASK [display result_get_host] *********************************************************************************************************************************
ok: [srv1] => {
"result_get_host": {
"changed": true,
"cmd": "get-host",
...
...
"stdout_lines": [
"",
"",
"Name : ConsoleHost",
"Version : 5.1.19041.3930",
"InstanceId : 562eb652-4719-4783-b2ee-0337a53c1d6e",
"UI : System.Management.Automation.Internal.Host.InternalHostUserInterface",
"CurrentCulture : ko-KR",
"CurrentUICulture : en-US",
"PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy",
"DebuggerEnabled : True",
"IsRunspacePushed : False",
"Runspace : System.Management.Automation.Runspaces.LocalRunspace",
"",
"",
""
]
}
}
PLAY RECAP *****************************************************************************************************************************************************
srv1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
참고자료
- Ansible 공식 문서 : https://docs.ansible.com/ansible/latest/index.html
- Ansible Github : https://github.com/ansible
- fork man page : https://linux.die.net/man/2/fork
- https://velog.io/@kimh4nkyul/DevOps-Terraform-Ansible-그리고-Chef-어느-것이-폐쇄망에-적합한가