LabHub
배우기 러닝패스 코스

Ansible 기초 · 고치기 전에 보는 법 · 이론

점검 모드는 무엇을 흉내 내고 무엇을 못 하나

LabHub 에서 이어서 보기

한 줄 요약

--check 는 "지금 돌리면 무엇이 바뀌는가" 를 묻는 모드이고, 그 답의 정확도는 모듈마다 다르다. 점검 모드를 신뢰할 수 있게 만드는 일은 플래그 하나를 붙이는 것이 아니라 태스크마다 점검 모드에서 어떻게 행동할지 정해 주는 일이다.

왜 이게 필요했나

운영 서버에 플레이북을 처음 거는 날의 공포는 구체적이다. 이 플레이북이 스무 대에 무엇을 할지 아무도 모른다. 누군가 손으로 고쳐 둔 설정이 있을 수도 있고, 여섯 달 전 사람이 넣은 태스크가 지금은 엉뚱한 일을 할 수도 있다. 그렇다고 한 대씩 눈으로 보면서 갈 수도 없다.

--check 는 그 자리에 놓인 도구다. 공식 문서는 이 모드를 dry run 이라고 부르고, 하는 일을 한 문장으로 정의한다. 원격 시스템에 변경을 가하지 않고, 변경이 일어났을 것 같으면 그 태스크를 changed 로 보고한다. --diff 를 함께 주면 무엇이 어떻게 달라지는지 줄 단위로 보여 준다.

그런데 이 도구는 두 번 배신한다. 한 번은 거짓 안심으로 — 점검 모드에서 깨끗했는데 진짜로 돌리니 실패한다. 또 한 번은 거짓 실패로 — 점검 모드에서 빨갛게 죽었는데 진짜로 돌리면 아무 문제가 없다. 두 배신 모두 원인은 같다. 점검 모드는 실제로 실행하지 않은 채 결과를 추측하는 모드이고, 추측의 품질은 모듈마다 다르다.

어떻게 동작하나

점검 모드는 태스크마다 따로 결정된다. 모듈이 점검 모드를 지원하면 실제 변경 대신 "바뀔 것이다" 를 보고한다. 지원하지 않으면 그 태스크는 건너뛰어진다(skipping). command·shell 이 대표적이다 — Ansible 은 그 명령이 무엇을 할지 알 수 없으므로 아예 안 돌린다.

여기서 첫 번째 사고가 난다. 조회만 하는 명령 태스크가 건너뛰어지면 그 태스크의 register 변수가 비고, 그 값을 쓰는 뒤 태스크가 줄줄이 무너진다. 그래서 읽기만 하는 태스크에는 check_mode: false 를 붙인다. 그 태스크는 점검 모드에서도 실제로 실행된다.

- name: 현재 설치된 판을 읽는다  ansible.builtin.command: myapp --version  register: current  changed_when: false  check_mode: false

붙이는 기준은 하나다. 이 태스크가 대상을 바꾸는가. 바꾸지 않는다고 확신할 때만 붙인다. check_mode: false 를 바꾸는 태스크에 붙이면 그 태스크는 dry run 에서도 진짜로 실행되고, 그 순간 dry run 은 거짓말이 된다.

반대 방향도 있다. check_mode: true 를 붙인 태스크는 평소에도 절대 변경하지 않는다. 실제 실행에서도 "바뀔 것이다" 만 보고한다. 아직 켜면 안 되는 태스크를 미리 적어 두고 영향만 보고 싶을 때, 또는 위험한 태스크를 사람이 확인하기 전까지 묶어 둘 때 쓴다.

거짓 실패는 순서 때문에 난다. 앞 태스크가 파일을 만들고 뒤 태스크가 그 파일을 고치는 플레이북을 생각해 보자. 점검 모드에서는 앞 태스크가 파일을 실제로 만들지 않으므로, 뒤 태스크는 없는 파일을 고치려다 실패한다. 이 실습 환경에서 lineinfile 은 정확히 Destination ... does not exist ! 로 죽는다. 플레이북이 틀린 게 아니라 점검 모드의 한계다.

고치는 방법은 셋이고 값이 다르다. 첫째, 뒤 태스크에 when: not ansible_check_mode 를 걸어 점검 모드에서는 건너뛴다 — 가장 흔하고 정직하다. 대신 그 태스크가 무엇을 바꿀지는 계획에서 빠진다. 둘째, 앞 태스크에 check_mode: false 를 붙여 실제로 만들게 한다 — dry run 이 아니게 되므로 권한다고 말하기 어렵다. 셋째, 애초에 그 파일을 한 모듈이 통째로 관리하게 설계를 바꾼다 — 가장 좋지만 가장 큰 변경이다.

--diff 는 형식이 정해져 있다. --- before+++ after 아래에 바뀌는 줄이 -+ 로 나온다. 내용이 아니라 속성만 바뀌는 경우에는 파일 내용 대신 속성 JSON 의 diff 가 나온다(디렉터리의 state: absentstate: directory 로 바뀌는 식). --diff 는 점검 모드 전용이 아니다 — 진짜 실행에 붙이면 바꾸면서 무엇을 바꿨는지 보여 준다. 사고 조사에서는 이쪽이 더 쓸모 있을 때가 많다.

비밀이 든 파일에는 no_log: true 를 함께 건다. 그러지 않으면 diff 가 그 값을 로그에 그대로 찍는다.

세 도구는 자리가 다르다.

| 도구 | 하는 일 | 대상에 붙는가 | 템플릿을 펼치는가 |
| --- | --- | --- | --- |
| --syntax-check | YAML 과 플레이북 구조를 읽어 본다 | 아니다 | 아니다 |
| --list-tasks | 이번 선택으로 무엇이 돌지 목록만 낸다 | 아니다 | 아니다 |
| --check | 태스크를 점검 모드로 실제 실행한다 | 붙는다 | 펼친다 |

실측으로 확인한 차이가 이걸 분명히 보여 준다. 정의되지 않은 변수를 쓰는 플레이북은 --syntax-check--list-tasks종료 코드 0 으로 통과한다. 그 오류는 --check 에서 종료 코드 2 로 처음 드러난다. 반대로 모듈 이름 오타나 들여쓰기 사고는 세 도구가 모두 종료 코드 4 로 잡는다. 그래서 CI 는 셋을 순서대로 건다 — 값싼 것부터.

점검 모드를 승인 절차로 쓸 때는 사람이 읽을 산출물이 필요하다. 화면에 흘러가는 초록·노랑 글자는 승인 근거로 남지 않는다. ansible.posix.json 콜백을 stdout 콜백으로 지정하면 실행 전체가 JSON 하나로 나오고, 거기서 "바뀔 태스크의 이름 목록" 만 뽑아 변경 요청에 붙일 수 있다.

ANSIBLE_STDOUT_CALLBACK=ansible.posix.json \  ansible-playbook -i hosts.ini site.yml --check --diff > plan.json

한 걸음 더 가면 게이트가 된다. 수렴이 끝난 뒤 점검 모드로 한 번 더 돌려 바뀔 것이 하나라도 있으면 실패로 끝내는 스크립트를 CI 에 걸어 두면, 누군가 손으로 고친 서버가 다음 배포 전에 드러난다. 점검 모드가 '보는 도구' 에서 '지키는 도구' 로 바뀌는 자리다.

현장에서 만나는 모습

첫째, 점검 모드가 깨끗한데 진짜 실행이 실패한다. 대개 셸 태스크가 건너뛰어진 탓이다. 점검 모드에서 skipped 가 많다면 그 플레이북의 dry run 은 그만큼 덜 본 것이다. PLAY RECAP 의 skipped 숫자를 계획의 신뢰도로 읽는 습관이 도움이 된다.

둘째, 점검 모드가 빨간데 사실은 멀쩡하다. 처음 도입하는 팀이 여기서 가장 많이 포기한다. "우리 플레이북은 --check 가 안 된다" 는 결론에 이르고 dry run 을 아예 안 쓰게 된다. 실제로는 태스크 두어 개에 가드를 붙이면 끝나는 일이다.

셋째, diff 가 비밀을 로그에 흘린다. 인증서 키나 DB 비밀번호를 템플릿으로 배포하면서 --diff 를 걸면 CI 로그에 값이 그대로 남는다. no_log: true 를 붙이는 규칙을 처음부터 세워 두는 편이 낫다.

넷째, 승인이 화면 캡처로 오간다. dry run 결과를 이미지로 붙여 넣는 팀은 반년 뒤 "그때 무엇을 승인했는지" 를 찾지 못한다. JSON 계획 산출물 하나면 승인 이력이 검색 가능한 텍스트로 남는다.

다섯째, 이 실습 환경의 정직한 한계. 감사 로그나 외부 승인 시스템은 이 파드에 없다. 그래서 '승인 절차' 는 계획 산출물을 파일로 남기는 데까지만 다루고, 그 파일을 어디에 붙이는지는 읽기에서만 이야기한다. 서비스 재시작 같은 태스크도 capability 가 없어 다룰 수 없으므로, 점검 모드의 대상은 파일과 디렉터리로 한정한다.

참고 문서

다음 실습에서 할 것

플레이북을 만들자마자 --check 로 먼저 돌려 아무것도 안 생긴 것을 확인하고, 한 번 수렴시킨 뒤 값 셋을 바꿔 --check --diff 로 내용·권한·한 줄 세 종류의 diff 를 뽑는다. 읽기 전용 명령 태스크가 점검 모드에서 건너뛰어지는 것을 보고 check_mode: false 로 되살리고, 반대로 check_mode: true 로 실제 실행에서도 절대 바꾸지 않는 태스크를 만든다. 점검 모드의 거짓 실패를 일부러 만들어 보고 ansible_check_mode 가드로 고친다. 정의되지 않은 변수를 쓰는 플레이북에 --syntax-check·--list-tasks·--check 를 차례로 걸어 종료 코드가 어디서 처음 갈리는지 표로 남기고, JSON 콜백으로 승인용 계획 산출물을 만든 뒤, 바뀔 것이 있으면 1 로 끝나는 드리프트 게이트 스크립트를 직접 만든다.