Ansible 실전 · 필터가 없으면 만든다 · 이론
플러그인 — 어디서 도는가가 설계의 절반이다
한 줄 요약
앤서블을 늘리는 자리는 여러 개지만 가르는 기준은 하나다 — 필터·테스트·룩업은 플레이북을 실행하는 컨트롤러에서 돌고, 모듈은 대상 서버로 복사되어 거기서 돈다. 무엇을 어느 자리에 둘지는 이 한 줄에서 거의 다 나온다.
왜 이게 필요했나
플레이북이 자라면 어느 팀에나 같은 모양의 자리가 생긴다. 서비스 이름을 파일 이름으로 쓸 수 있게 다듬는 일, 포트 번호를 등급으로 나누는 일, 사내 어딘가에 있는 표에서 값을 찾아오는 일. 표준 필터로 되지 않으면 사람들은 두 가지 중 하나를 한다.
첫째는 Jinja2 를 억지로 늘리는 것이다. {{ name | lower | replace(' ', '-') | replace('.', '-') | replace('_', '-') | trim('-') }} 같은 줄이 생기고, 여섯 달 뒤 누구도 그 줄을 고치려 하지 않는다. 테스트도 못 한다 — 그 로직은 플레이북 안에만 있고 따로 불러 볼 방법이 없다.
둘째는 셸로 내려가는 것이다. shell: echo {{ name }} | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9]/-/g' 로 쓰고 결과를 register 로 받는다. 이 한 줄에 앤서블이 주던 것이 전부 사라진다. 멱등성이 없고(언제나 changed), 점검 모드에서는 건너뛰어지고, 대상에 tr 과 sed 가 있다는 가정이 생기고, 왕복이 한 번 늘어난다. 값을 계산하는 일에 SSH 왕복을 쓰는 셈이다.
플러그인은 이 자리를 위한 것이다. 값을 계산하는 일은 컨트롤러에서 파이썬으로 하고, 대상은 건드리지 않는다. 그러면 그 로직은 파일 하나에 모이고, 이름이 생기고, 따로 시험할 수 있게 된다.
어떻게 동작하나
앤서블의 플러그인은 종류가 열 개가 넘지만, 플레이북을 쓰는 사람이 직접 만들게 되는 것은 대개 넷이다.
| 종류 | 부르는 법 | 도는 자리 | 무엇을 돌려주나 |
| --- | --- | --- | --- |
| 필터(filter) | 값 \| 이름 | 컨트롤러 | 아무 값 |
| 테스트(test) | 값 is 이름 | 컨트롤러 | 참 또는 거짓 |
| 룩업(lookup) | lookup('이름', 인자) | 컨트롤러 | 리스트 |
| 모듈(module) | 태스크의 키 | 대상 | JSON(changed 포함) |
앞의 셋은 템플릿 엔진이 값을 만들 때 불린다. 그래서 대상에 아무것도 설치할 필요가 없고, SSH 왕복도 늘지 않는다. 대신 대상의 상태를 볼 수 없다 — 대상의 디스크가 얼마나 남았는지 필터는 알 수 없다. 그것은 팩트나 모듈의 일이다.
모듈은 반대다. 파일이 대상으로 복사되어 대상의 파이썬으로 실행된다. 그래서 상태를 바꿀 수 있고, 바꿀 수 있다는 것은 멱등성과 점검 모드를 스스로 책임져야 한다는 뜻이다.
디렉터리와 클래스 규약
앤서블은 설정 파일에 등록하라고 하지 않는다. 대신 자리와 이름으로 찾는다.
플레이북 옆: filter_plugins/ → class FilterModule 의 filters() 가 돌려주는 사전 test_plugins/ → class TestModule 의 tests() 가 돌려주는 사전 lookup_plugins/ → class LookupModule(LookupBase) 의 run() library/ → 모듈. 파일 이름이 곧 모듈 이름컬렉션 안: plugins/filter/ plugins/test/ plugins/lookup/ plugins/modules/여기서 두 가지가 자주 사람을 잡는다. 하나는 파일 이름은 자유지만 클래스 이름은 규약이라는 것이다. FilterModule 이 아니면 아무 오류 없이 그냥 못 찾는다. 다른 하나는 플레이북 옆이라는 말이 문자 그대로라는 것이다. 애드혹 명령(ansible -m debug -a ...)에서는 이 탐색이 걸리지 않으므로, 새 필터를 애드혹으로 시험해 보고 "안 되네" 하고 결론 내리는 일이 흔하다.
컬렉션 안에 두면 자리가 바뀐다. filter_plugins/ 가 아니라 plugins/filter/ 이고, 부를 때는 네임스페이스.이름.필터 같은 FQCN 을 쓴다. 컬렉션은 <검색경로>/ansible_collections/<네임스페이스>/<컬렉션이름>/ 이라는 세 층 아래에 있어야 하고, 한 층만 어긋나도 역시 조용히 못 찾는다.
필터가 지켜야 할 것
필터는 Jinja2 가 값을 만들 때 부르는 함수다. 그래서 순수 함수여야 한다 — 같은 입력에 언제나 같은 값을 내고, 바깥 세상을 건드리지 않아야 한다. 이유는 성능이 아니라 예측 가능성이다. 템플릿은 언제 몇 번 평가될지 정해져 있지 않다. 조건이 걸린 태스크에서는 평가되지 않을 수도 있고, 여러 호스트에서 각각 평가될 수도 있다. 필터가 파일을 쓰거나 시각을 읽으면 결과가 실행마다 달라지고, 그 태스크는 영영 멱등할 수 없다.
잘못된 입력을 만났을 때는 AnsibleFilterError 로 끊는다. 조용히 빈 문자열을 돌려주는 필터는 더 나쁘다 — 틀린 값이 그대로 설정 파일에 들어가고, 아무도 신고하지 않는다.
룩업이 만드는 사고
룩업은 컨트롤러에서 돈다. 그래서 lookup('file', '/etc/app/secret') 은 대상 서버의 그 파일이 아니라 컨트롤러의 그 파일을 읽는다. 문서가 이 점을 굵게 적어 두었는데도 가장 자주 걸리는 자리다. 개발자의 노트북에서는 잘 되다가 CI 러너에서 파일이 없어 실패하는 일, 또는 반대로 CI 러너의 파일이 대상에 배포되는 일이 여기서 나온다.
룩업은 반드시 리스트를 돌려준다. with_ 반복이 원래 룩업 위에 세워진 기능이라 그렇다. 값 하나만 필요하면 | first 를 붙이거나 query() 대신 lookup() 을 쓴다.
언제 필터 대신 모듈인가
판단 기준은 간단하다.
- 대상의 상태를 읽어야 하는가 → 모듈(또는 팩트). 필터는 볼 수 없다.
- 대상의 상태를 바꿔야 하는가 → 모듈. 필터가 파일을 쓰면 그 순간 순수 함수가 아니다.
- 값을 변형할 뿐인가 → 필터.
- 참거짓 하나로 답이 나오는가 → 테스트.
when:과select/reject에서 그대로 쓰인다. - 컨트롤러 쪽 자료를 찾아와야 하는가 → 룩업.
현장에서 만나는 모습
첫째, 필터 하나가 팀의 표준이 된다. 서비스 이름을 슬러그로 바꾸는 필터를 만들면 그다음부터는 쿠버네티스 라벨도, 파일 이름도, 로그 태그도 같은 규칙으로 만들어진다. 규칙이 코드 한 곳에 모이는 것이 이 일의 진짜 값어치다. 규칙을 바꿔야 할 때 고칠 자리가 하나다.
둘째, 시험할 수 있게 된다. 플러그인은 그냥 파이썬 파일이라 파이썬 시험 도구로 직접 부를 수 있다. 플레이북 안의 Jinja2 한 줄은 그렇게 할 수 없다. 로직이 복잡해질수록 이 차이가 커진다.
셋째, 컬렉션으로 옮길 때 이름이 바뀐다. 처음에는 filter_plugins/ 에 두었다가 여러 저장소에서 쓰게 되면 컬렉션으로 옮기게 되는데, 그때 호출부가 전부 FQCN 으로 바뀐다. 처음부터 이름에 팀 접두어를 붙여 두면 옮길 때 충돌이 줄어든다.
넷째, 사용자 모듈은 생각보다 자주 필요하지 않다. 사내 API 를 부르는 일이라면 uri 모듈로 대개 된다. 모듈을 직접 만들 값어치가 있는 자리는 "여러 번 불러도 한 번만 바꿔야 하는 복잡한 상태" 를 다룰 때다. 그때는 supports_check_mode 를 반드시 선언한다 — 선언하지 않으면 --check 에서 그 태스크는 실패가 아니라 침묵으로 건너뛰어진다. 계획을 보러 점검 모드를 돌린 사람은 그 태스크가 무엇을 할지 끝내 알지 못한다.
참고 문서
- 플러그인 개발 안내: https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html
- 모듈과 플러그인을 로컬에 추가하기: https://docs.ansible.com/ansible/latest/dev_guide/developing_locally.html
- 룩업 플러그인: https://docs.ansible.com/ansible/latest/plugins/lookup.html
- 컬렉션 디렉터리 구조: https://docs.ansible.com/ansible/latest/dev_guide/developing_collections_structure.html
- 모듈 개발 안내: https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html
다음 실습에서 할 것
네 종류를 직접 만들어 한 플레이북에서 잇는다. filter_plugins/ 에 슬러그 필터를 만들고, 경계 두 개를 인자로 받는 두 번째 필터를 더하고, test_plugins/ 에 참거짓만 돌려주는 테스트를 만들고, lookup_plugins/ 에 컨트롤러의 JSON 등록부를 읽는 룩업을 만든다. 같은 필터를 컬렉션 안(plugins/filter/)으로 옮겨 FQCN 으로 부르고, 마지막에 library/ 에 사용자 모듈을 만들어 대상에서 돌리며 두 번째 실행이 changed=0 이 되는지, 점검 모드에서 파일이 만들어지지 않는지까지 확인한다.