Ansible 기초 · 모듈과 셸 사이의 선 · 실습
셸로 나가야 한다면 무엇을 직접 책임져야 하나
목표
같은 명령을 command 와 shell 로 각각 던져 무엇이 갈리는지 숫자로 재고, 셸을 써야 할 때 보고 기준과 실패 기준과 파이프 종료 코드를 직접 세우는 법을 손으로 익힙니다. 마지막에는 가드 없이 셸로 나가는 태스크를 찾아내는 감사 도구를 만듭니다.
왜 중요한가
Ansible 을 처음 쓰면 플레이북이 SSH 로 실행되는 셸 스크립트가 되기 쉽습니다. 돌기는 도는데 '지금 이미 그 상태인가'·'이번에 무엇이 바뀌었나'·'실패인가 성공인가' 에 아무것도 답하지 못하는 플레이북이 됩니다. 모듈은 그 세 가지에 답하려고 만들어진 것이고, 그래서 같은 일을 하는 모듈이 있으면 그쪽이 먼저입니다. 그렇다고 셸을 영영 안 쓸 수는 없습니다 — 모듈이 없는 일은 늘 남습니다. 중요한 것은 셸로 나가는 순간 Ansible 이 대신 해 주던 판단이 전부 사라진다는 사실을 알고, 그 판단을 손으로 다시 써 넣는 것입니다. 이 실습은 그 판단 세 가지를 하나씩 세워 봅니다.
단계
1. /root/ansmod/hosts.ini 를 만드세요 — [web] 그룹에 web1·web2 를 넣고 둘 다 ansible_host=127.0.0.1, ansible_port=2222 를 갖게 하며, [all:vars] 로 ansible_user=root 를 둡니다. 그다음 ansible-doc -s ansible.builtin.command 의 출력을 /root/ansmod/out/doc-command.txt 에, ansible-doc -s ansible.builtin.shell 의 출력을 /root/ansmod/out/doc-shell.txt 에 저장하세요.
2. /root/ansmod/files/ 에 빈 파일 a.txt·b.txt·c.txt 세 개를 만드세요. /root/ansmod/boundary.yml 을 만들어 web1 에 네 태스크를 돌리세요 — ls /root/ansmod/files/*.txt 를 command 로 한 번(실패해도 넘어가게), ls /root/ansmod/files/*.txt | wc -l 을 shell 로 한 번, echo one two three | wc -w 를 command 로 한 번, 같은 것을 shell 로 한 번입니다. 네 결과를 /root/ansmod/out/boundary.txt 에 정확히 네 줄로 남기세요: glob command rc=<값> / glob shell stdout=<값> / pipe command stdout=<값> / pipe shell stdout=<값>.
3. /root/ansmod/report.yml 을 만드세요. web1 에서 ansible.builtin.command 로 id -un 을 실행해 who 로 register 하고, 그 반환값에서 네 칸만 뽑아 /root/ansmod/out/result.json 에 JSON 으로 저장하세요 — rc·stdout·changed 는 반환값 그대로, cmd 는 반환값의 인자 목록을 공백으로 이어 붙인 문자열입니다. 이 단계에서는 changed_when 을 달지 않습니다.
4. /root/ansmod/report.yml 에 태스크를 둘 더 넣으세요. 하나는 cat /etc/hostname 을 command 로 돌려 hn 으로 register 하고 changed_when: false 를 답니다. 다른 하나는 shell 로 grep -c "^nosuchuser:" /etc/passwd 를 돌려 hits 로 register 하고, changed_when: false 와 함께 종료 코드가 0 이나 1 이 아닐 때만 실패로 치는 failed_when 을 답니다. 그리고 /root/ansmod/out/result.json 에 세 칸을 더하세요 — hostname_changed(hn 의 changed), grep_rc(hits 의 rc), grep_failed(hits 의 failed). 플레이북은 끝까지 돌아야 합니다.
5. /root/ansmod/pipe.yml 을 만드세요. 같은 파이프라인 cat /root/ansmod/missing.txt | wc -l 을 두 번 돌립니다 — 한 번은 그냥 shell 로(bare 로 register), 한 번은 set -o pipefail 을 앞에 붙이고 executable 을 /bin/bash 로 지정해서(guarded 로 register). 둘 다 ignore_errors: true 와 changed_when: false 를 답니다. 결과를 /root/ansmod/out/pipe.json 에 네 칸으로 남기세요 — bare_rc·bare_failed·guarded_rc·guarded_failed. missing.txt 는 만들지 마세요.
6. /root/ansmod/modernize.yml 을 만드세요. command 도 shell 도 한 번도 쓰지 않고 다음을 하세요 — /root/ansmod/app 디렉터리를 권한 0750 으로 만들고, /root/ansmod/app/app.conf 에 env=lab 한 줄을 권한 0640 으로 쓰고, 두 경로의 상태를 모듈로 읽어 /root/ansmod/out/modernize.json 에 다섯 칸으로 남깁니다 — dir_mode·dir_isdir·conf_mode·conf_size·conf_checksum_len(체크섬 문자열의 길이).
7. /root/ansmod/bootstrap.yml 을 만드세요. ansible.builtin.raw 로 command -v python3 || echo NOPYTHON 을 실행해 register 하고, 줄 끝 공백과 CR 을 걷어낸 경로만 /root/ansmod/out/raw.txt 에 한 줄로 저장하세요(changed_when: false 를 답니다). 그리고 지금까지 만든 다섯 플레이북(boundary.yml·report.yml·pipe.yml·modernize.yml·bootstrap.yml)의 모든 태스크가 ansible.builtin. 으로 시작하는 FQCN 을 쓰도록 정리하세요.
8. 먼저 검사 대상이 될 /root/ansmod/legacy.yml 을 만드세요 — 태스크 네 개이고, ansible --version 을 command 로 돌리되 changed_when: false 를 단 것 하나, mkdir -p /root/ansmod/legacy/logs 를 shell 로 돌리는 것 하나, echo seeded > /root/ansmod/legacy/logs/stamp.txt 를 shell 로 돌리는 것 하나, ls /root/ansmod/legacy/logs 를 command 로 돌리는 것 하나입니다(뒤 셋에는 가드를 달지 않습니다). 그다음 /root/ansmod/shell-audit.sh <플레이북경로> 를 만드세요: 그 플레이북에서 command 또는 shell 을 쓰면서 changed_when 이 없는 태스크의 이름만 한 줄씩 사전순으로 출력합니다(짧은 이름과 FQCN 을 둘 다 인식해야 합니다). 마지막으로 ./shell-audit.sh /root/ansmod/legacy.yml 의 출력을 /root/ansmod/out/audit.txt 에 저장하세요.
참고
- 먼저 1단계에서 인벤토리를 만드세요. 이 파드의 sshd 는 127.0.0.1:2222 에 떠 있고 키 인증이 이미 됩니다.
- 명령 힌트:
ansible-doc -l ansible.builtin | grep -i <낱말>로 모듈을 찾고,ansible-doc -s <모듈>로 옵션 뼈대를 보고,ansible-inventory -i hosts.ini --graph로 인벤토리 해석 결과를 봅니다. - 명령 힌트:
yq -r '.[].tasks[] | keys | .[]' <플레이북>은 태스크가 쓰는 키를 전부 냅니다.jq . <파일>로 만든 JSON 이 진짜 JSON 인지 확인합니다. - 흔한 실수: 조회만 하는
command태스크에changed_when: false를 안 달아 매 실행이 changed 로 쌓이는 것. - 흔한 실수: 파이프를
shell에 넘기면서set -o pipefail을 빼, 앞 명령이 죽어도 태스크가 성공으로 지나가는 것. - 흔한 실수: 권한을
mode: 0640처럼 따옴표 없이 적어 8진수가 10진수로 읽히는 것. - 이 파드에는 capability 가 없어
systemctl·mount·sysctl -w는 동작하지 않습니다. 그래서 이 실습은 파일·디렉터리·조회 명령만 다룹니다 — 원리는 서비스 관리에서도 같습니다. - [command 모듈](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html) · [shell 모듈](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/shell_module.html) · [raw 모듈](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/raw_module.html) · [ansible-doc](https://docs.ansible.com/ansible/latest/cli/ansible-doc.html) · [오류 처리](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html)
단계 8개
- 대상을 적고 모듈을 문서에서 찾는다
- 같은 명령을 command 와 shell 로 던져 차이를 잰다
- 모듈이 돌려주는 JSON 을 register 로 받아 읽는다
- changed_when 과 failed_when 으로 보고 기준을 직접 세운다
- 파이프가 실패를 삼키는 것을 숫자로 확인하고 막는다
- 셸 세 줄을 file·copy·stat 모듈로 옮긴다
- raw 로 파이썬을 찾고 플레이북을 FQCN 으로 정리한다
- 가드 없이 셸로 나가는 태스크를 찾아내는 감사 도구