LabHub
배우기 러닝패스 코스

Ansible 실전 · 필터가 없으면 만든다 · 이론

플러그인 — 어디서 도는가가 설계의 절반이다

LabHub 에서 이어서 보기

한 줄 요약

앤서블을 늘리는 자리는 여러 개지만 가르는 기준은 하나다 — 필터·테스트·룩업은 플레이북을 실행하는 컨트롤러에서 돌고, 모듈은 대상 서버로 복사되어 거기서 돈다. 무엇을 어느 자리에 둘지는 이 한 줄에서 거의 다 나온다.

왜 이게 필요했나

플레이북이 자라면 어느 팀에나 같은 모양의 자리가 생긴다. 서비스 이름을 파일 이름으로 쓸 수 있게 다듬는 일, 포트 번호를 등급으로 나누는 일, 사내 어딘가에 있는 표에서 값을 찾아오는 일. 표준 필터로 되지 않으면 사람들은 두 가지 중 하나를 한다.

첫째는 Jinja2 를 억지로 늘리는 것이다. {{ name | lower | replace(' ', '-') | replace('.', '-') | replace('_', '-') | trim('-') }} 같은 줄이 생기고, 여섯 달 뒤 누구도 그 줄을 고치려 하지 않는다. 테스트도 못 한다 — 그 로직은 플레이북 안에만 있고 따로 불러 볼 방법이 없다.

둘째는 셸로 내려가는 것이다. shell: echo {{ name }} | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9]/-/g' 로 쓰고 결과를 register 로 받는다. 이 한 줄에 앤서블이 주던 것이 전부 사라진다. 멱등성이 없고(언제나 changed), 점검 모드에서는 건너뛰어지고, 대상에 trsed 가 있다는 가정이 생기고, 왕복이 한 번 늘어난다. 값을 계산하는 일에 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() 을 쓴다.

언제 필터 대신 모듈인가

판단 기준은 간단하다.

현장에서 만나는 모습

첫째, 필터 하나가 팀의 표준이 된다. 서비스 이름을 슬러그로 바꾸는 필터를 만들면 그다음부터는 쿠버네티스 라벨도, 파일 이름도, 로그 태그도 같은 규칙으로 만들어진다. 규칙이 코드 한 곳에 모이는 것이 이 일의 진짜 값어치다. 규칙을 바꿔야 할 때 고칠 자리가 하나다.

둘째, 시험할 수 있게 된다. 플러그인은 그냥 파이썬 파일이라 파이썬 시험 도구로 직접 부를 수 있다. 플레이북 안의 Jinja2 한 줄은 그렇게 할 수 없다. 로직이 복잡해질수록 이 차이가 커진다.

셋째, 컬렉션으로 옮길 때 이름이 바뀐다. 처음에는 filter_plugins/ 에 두었다가 여러 저장소에서 쓰게 되면 컬렉션으로 옮기게 되는데, 그때 호출부가 전부 FQCN 으로 바뀐다. 처음부터 이름에 팀 접두어를 붙여 두면 옮길 때 충돌이 줄어든다.

넷째, 사용자 모듈은 생각보다 자주 필요하지 않다. 사내 API 를 부르는 일이라면 uri 모듈로 대개 된다. 모듈을 직접 만들 값어치가 있는 자리는 "여러 번 불러도 한 번만 바꿔야 하는 복잡한 상태" 를 다룰 때다. 그때는 supports_check_mode 를 반드시 선언한다 — 선언하지 않으면 --check 에서 그 태스크는 실패가 아니라 침묵으로 건너뛰어진다. 계획을 보러 점검 모드를 돌린 사람은 그 태스크가 무엇을 할지 끝내 알지 못한다.

참고 문서

다음 실습에서 할 것

네 종류를 직접 만들어 한 플레이북에서 잇는다. filter_plugins/ 에 슬러그 필터를 만들고, 경계 두 개를 인자로 받는 두 번째 필터를 더하고, test_plugins/ 에 참거짓만 돌려주는 테스트를 만들고, lookup_plugins/ 에 컨트롤러의 JSON 등록부를 읽는 룩업을 만든다. 같은 필터를 컬렉션 안(plugins/filter/)으로 옮겨 FQCN 으로 부르고, 마지막에 library/ 에 사용자 모듈을 만들어 대상에서 돌리며 두 번째 실행이 changed=0 이 되는지, 점검 모드에서 파일이 만들어지지 않는지까지 확인한다.