Ansible 실전 · 같은 이름을 심을 수 있는 자리가 스물두 개다 · 이론
변수 우선순위 - 표를 외우기 전에 재 보라
한 줄 요약
Ansible 에서 같은 이름의 변수를 심을 수 있는 자리는 스물두 개이고, 그 순서를 외우는 것보다 자리를 줄이는 설계가 사고를 더 확실히 막는다.
왜 이게 필요했나
"분명히 group_vars 에 포트를 8080 으로 적었는데 9090 으로 뜬다." 이런 신고는 거의 언제나 같은 모양이다. 누군가 급할 때 명령줄에 -e 를 붙여 돌렸고, 그 사실이 어디에도 남지 않았다. 또는 롤 안에 vars/main.yml 이 있었고 그것이 플레이의 vars_files 를 이겼다.
이 문제가 어려운 이유는 틀린 값이 오류로 나타나지 않기 때문이다. 플레이북은 성공하고, 파일은 만들어지고, 다만 내용이 다르다. 문법 검사도 린트도 이걸 잡지 못한다. 잡을 수 있는 것은 "지금 이 자리에서 이 이름이 무엇으로 풀리는가"를 실제로 재 보는 일뿐이다.
그래서 이 모듈은 공식 문서의 목록을 옮겨 적는 대신, 같은 이름을 한 자리씩 더 심어 가며 승자가 어떻게 바뀌는지 측정한다. 한 번 재 본 사람은 표를 외우지 않아도 다음에 같은 상황에서 무엇을 먼저 의심해야 할지 안다.
어떻게 동작하나
공식 문서는 자리를 낮은 것부터 높은 것까지 스물두 개로 늘어놓는다. 실습에서 재는 열두 자리를 낮은 것부터 적으면 이렇다.
| 순위 | 자리 | 성격 |
| --- | --- | --- |
| 1 | 롤 defaults/main.yml | "밖에서 덮어쓰라" 고 내미는 값 |
| 2 | 인벤토리 group_vars/all | 모든 호스트의 바탕값 |
| 3 | 인벤토리 group_vars/<그룹> | 그룹별 값 |
| 4 | 인벤토리 host_vars/<호스트> | 호스트별 값 |
| 5 | 플레이 vars: | 이 플레이에서만 |
| 6 | 플레이 vars_files: | 이 플레이가 읽어 들인 파일 |
| 7 | 롤 vars/main.yml | "밖에서 건드리지 말라" 는 롤 내부 값 |
| 8 | 블록 vars: | 블록 안에서만 |
| 9 | 태스크 vars: | 태스크 하나에서만 |
| 10 | set_fact | 실행 중에 세운 값 |
| 11 | 롤 호출 인자 | 롤을 부르며 넘긴 값 |
| 12 | 명령줄 -e | 무엇이든 이긴다 |
여기서 놀라는 자리가 셋이다.
첫째, vars_files 가 플레이 vars: 보다 높다. 같은 플레이 안에서 바로 위에 적은 vars: 가 아래에서 읽어 들인 파일에 진다. 순서를 눈으로 읽는 감각과 반대다.
둘째, 롤의 defaults 와 vars 는 정반대다. 같은 롤 디렉터리 안에 나란히 있지만 defaults 는 맨 아래, vars 는 한참 위다. 이 차이가 곧 롤 저자의 의도다 - 밖에서 바꿔도 되는 값은 defaults 에, 바꾸면 안 되는 값은 vars 에 둔다. 남의 롤을 쓰다가 "아무리 덮어써도 안 바뀐다" 면 그 값은 vars/main.yml 에 있다.
셋째, 범위가 좁을수록 높다. 플레이 < 블록 < 태스크 순서는 외울 것이 아니라 규칙이다. 좁은 자리에 적은 사람이 더 구체적인 의도를 가졌다고 보는 것이다.
측정에 쓰는 도구도 알아 둘 값어치가 있다. 인벤토리 안에서의 승부는 ansible-inventory --list 가 이미 합쳐진 결과를 JSON 으로 보여 준다. 플레이 안에서의 승부는 debug 태스크 하나를 끼워 넣고 돌려 보는 것이 가장 빠르다.
ansible-inventory -i inventory --list # 인벤토리 세 자리가 합쳐진 결과ansible-inventory -i inventory --graph # 그룹 구조인벤토리를 디렉터리로 줄 때 걸리는 함정이 하나 있다. 디렉터리 안의 .ini 확장자 파일은 기본 설정에서 무시된다(INVENTORY_IGNORE_EXTS). 파일 하나를 -i 로 줄 때는 잘 되던 hosts.ini 가 디렉터리 안에 들어가는 순간 사라지는 것이다. 호스트가 하나도 안 보이면 이것부터 의심한다.
현장에서 만나는 모습
첫째, 딕셔너리는 합쳐지지 않는다. svc_limits: {cpu: "1", memory: 1Gi} 를 낮은 자리에 두고 높은 자리에서 svc_limits: {memory: 2Gi} 를 주면 결과는 {memory: 2Gi} 다. cpu 는 사라진다. 합치고 싶으면 combine 필터로 명시해야 한다. 전역 설정 hash_behaviour = merge 로 바꾸는 방법도 있지만, 그 설정은 저장소 전체의 동작을 바꾸므로 공식 문서도 권하지 않는다.
둘째, -e 는 되돌릴 수 없다. 명령줄 값은 어느 자리보다 높고 플레이북 안에서 덮을 방법이 없다. 편리해서 쓰기 시작하면 결국 "누가 언제 무엇을 줬는지" 가 기록에서 사라진다. 사고 대응용으로만 쓰고, 쓴 사실을 남기는 팀 규칙이 필요하다.
셋째, 자리를 줄이는 설계. 표를 아는 것보다 강한 방어는 같은 이름을 심을 자리를 줄이는 것이다. 흔한 규칙은 셋이다 - 환경별 값은 인벤토리 group_vars 한 곳에만 둔다, 롤은 밖에서 바꿔도 되는 값만 defaults 에 둔다, -e 는 예외 상황에만 쓴다. 이 셋을 지키면 우선순위 표를 몰라도 사고가 거의 나지 않는다.
넷째, vars_prompt. 실행할 때 사람에게 물어 받는 자리도 있다(플레이 vars: 와 vars_files: 사이). 자동화 파이프라인에서는 실행이 멈춰 버리므로 CI 에 들어갈 플레이북에는 쓰지 않는다. 이 실습도 자동 채점이라 다루지 않는다.
다음 실습에서 할 것
svc_tier 라는 이름 하나를 열두 자리에 한 자리씩 더 심어 가며 승자가 어떻게 바뀌는지 직접 잰다. 인벤토리 세 자리는 ansible-inventory --list 로, 나머지는 플레이북을 돌려 산출물로 확인한다. 마지막에는 측정한 순서를 사람이 읽을 수 있는 표로 남기고, 딕셔너리가 합쳐지지 않는 것과 combine 으로 합치는 것을 나란히 확인한다.
참고 문서
- [Using Variables](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html)
- [Controlling how Ansible behaves: precedence rules](https://docs.ansible.com/ansible/latest/reference_appendices/general_precedence.html)
- [How to build your inventory](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html)
- [combine filter](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/combine_filter.html)
- [Ansible Configuration Settings](https://docs.ansible.com/ansible/latest/reference_appendices/config.html)