See what will change before you change it
한국어 원문으로 표시합니다.
목표
점검 모드와 diff 를 '플래그 두 개' 가 아니라 '태스크마다 정해 주는 행동' 으로 다룹니다. 거짓 실패를 직접 만들어 고치고, 승인에 붙일 계획 산출물과 드리프트 게이트까지 만듭니다.
왜 중요한가
운영 서버에 플레이북을 처음 거는 날, 이 플레이북이 스무 대에 무엇을 할지 아무도 모릅니다. --check 는 그 자리에 놓인 도구인데 두 번 배신합니다 — 점검 모드가 깨끗했는데 진짜 실행이 실패하고, 점검 모드가 빨갛게 죽었는데 진짜 실행은 멀쩡합니다. 두 배신의 원인은 같습니다. 점검 모드는 실제로 실행하지 않은 채 결과를 추측하는 모드이고, 추측의 품질은 모듈마다 다릅니다. 그래서 점검 모드를 믿을 수 있게 만드는 일은 플래그를 붙이는 일이 아니라 태스크마다 '너는 점검 모드에서 어떻게 행동해라' 를 정해 주는 일입니다. 그 일을 마치고 나면 dry run 은 배포 전에 보는 그림을 넘어, 승인 근거이자 드리프트를 잡는 게이트가 됩니다.
단계
/root/anschk/hosts.ini를 만드세요 —[web]에web1(ansible_host=127.0.0.1,ansible_port=2222),[all:vars]로ansible_user=root. 그다음/root/anschk/site.yml을 만드세요: 플레이 변수app_env(기본lab)·app_dir_mode(기본"0755")·motd_owner(기본unset) 를 두고, 태스크 넷으로/root/anschk/app디렉터리를app_dir_mode권한으로 만들고,/root/anschk/app/app.conf에env=<app_env>와listen=8080두 줄을 권한 0644 로 쓰고,/root/anschk/app/motd에Welcome=labhub와Owner=unset두 줄을 권한 0644 로 쓰고, 마지막으로motd의^Owner=줄을Owner=<motd_owner>로 맞춥니다(그 파일이 없으면 만들도록create를 켜세요 — 점검 모드에서 앞 태스크가 파일을 실제로 만들지 않기 때문입니다). 아직 수렴시키지 말고--check --diff로만 돌려 출력을/root/anschk/out/check1.txt에 저장하세요.site.yml을--diff와 함께 기본값으로 한 번 실제 실행해 출력을/root/anschk/out/converge.txt에 저장하세요. 그다음 값 셋을 명령줄로 덮어써서--check --diff로 다시 돌리세요 —app_env=stage,app_dir_mode=0750,motd_owner=platform-team. 그 출력을/root/anschk/out/diff.txt에 저장하세요. 실제 파일은 그대로env=lab·권한 0755·Owner=unset이어야 합니다./root/anschk/probe.yml을 만드세요.ansible.builtin.command로getent passwd root를 실행해pw로 register 하고(changed_when: false), 그 값과 지금이 점검 모드인지를ansible.builtin.debug로check_mode=<참거짓> pwline=<읽은 값>형태로 한 줄에 냅니다. 이 플레이북을--check로 돌린 출력을 표준 오류까지/root/anschk/out/probe-check.txt에 저장하되, 점검 모드에서도 실제 값이 찍혀야 합니다. 건너뛰어지는 태스크가 하나도 없어야 합니다./root/anschk/patch.yml을 만드세요 — 태스크 둘입니다. 하나는/root/anschk/app/fresh.conf에env=lab과listen=9090두 줄을 권한 0644 로 쓰고, 다른 하나는 그 파일의^listen=줄을listen=9443으로 고칩니다. 먼저 이 상태 그대로--check로 돌려 실패 출력을 표준 오류까지/root/anschk/out/false-failure.txt에 저장하세요. 그다음 뒤 태스크에 점검 모드에서는 건너뛰는 가드를 달고 다시--check로 돌려 그 출력을/root/anschk/out/false-fixed.txt에 저장하세요. 두 번 모두 점검 모드이므로/root/anschk/app/fresh.conf는 끝까지 만들어지면 안 됩니다./root/anschk/dryrun.yml을 만드세요./root/anschk/app/never.conf에feature=on한 줄을 권한 0644 로 쓰는 태스크 하나인데,--check없이 평소대로 돌려도 절대 파일을 만들지 않고 바뀔 것이라는 보고만 해야 합니다. 같은 플레이북에 태스크를 하나 더 두세요 —/root/anschk/app/dryrun-ran.txt에real-run한 줄을 권한 0644 로 쓰는 평범한 태스크입니다. 이 플레이북을 아무 플래그 없이 실행해 출력을/root/anschk/out/dryrun.txt에 저장하세요. 끝나면/root/anschk/app/dryrun-ran.txt는 있고/root/anschk/app/never.conf는 없어야 합니다./root/anschk/undef.yml을 만드세요 — 정의되지 않은 변수(missing_var)를 내용에 쓰는 copy 태스크 하나입니다./root/anschk/badmod.yml도 만드세요 — 모듈 이름을ansible.builtin.coppy로 오타 낸 태스크 하나입니다. 그다음/root/anschk/gates.sh를 만들어 두 플레이북 각각에--syntax-check·--list-tasks·--check를 차례로 걸고 종료 코드만/root/anschk/out/gates.txt에 한 줄씩 적으세요:<파일이름> syntax-check=<코드> list-tasks=<코드> check=<코드>. 스크립트를 실행해 파일을 남기세요./root/anschk/plan.sh를 만드세요.ansible.posix.json을 stdout 콜백으로 지정해site.yml을--check --diff -e app_env=stage로 돌리고 그 JSON 전체를/root/anschk/out/plan.raw.json에 저장한 뒤, 거기서 바뀔 태스크의 이름만 뽑아 JSON 배열로/root/anschk/out/plan.json에 저장합니다. 스크립트를 실행해 두 파일을 남기세요. 배열에는 이름이 정확히 하나만 들어 있어야 합니다./root/anschk/drift-gate.sh를 만드세요.site.yml을 점검 모드로 돌려 요약의changed가 0 이면 첫 줄이CLEAN으로 시작하는 메시지를 내고 0 으로 끝나고, 1 이상이면 첫 줄이DRIFT로 시작하는 메시지를 내고 1 로 끝납니다. 점검 모드 실행 자체가 실패하면DRIFT-UNKNOWN으로 시작하는 메시지를 내고 1 로 끝냅니다. 스크립트에 준 인자는 그대로ansible-playbook에 넘어가야 합니다. 수렴된 상태에서 인자 없이 돌린 출력을/root/anschk/out/gate-clean.txt에,-e app_env=stage를 주고 돌린 출력을/root/anschk/out/gate-dirty.txt에 저장하세요.
참고
- 먼저 1단계에서 인벤토리와 플레이북을 만드세요. 이 파드의 sshd 는 127.0.0.1:2222 에 떠 있고 키 인증이 이미 됩니다.
- 명령 힌트:
ansible-playbook -i hosts.ini site.yml --check --diff가 기본 도구이고,ansible-doc -t callback -l로 쓸 수 있는 콜백을 봅니다. 출력은> 파일 2>&1로 표준 오류까지 받습니다. - 명령 힌트: 매직 변수
ansible_check_mode는 지금이 점검 모드인지 알려 줍니다. 태스크에 붙이는check_mode키는 그 태스크만 점검 모드 밖으로 빼거나(false) 안에 가둡니다(true). - 흔한 실수: 경고와 오류가 표준 오류로 나오는 것을 모르고
> 파일만 걸어 빈 파일을 남기는 것. - 흔한 실수: 실패로 끝나는 명령의 출력을 저장하다가
set -e때문에 거기서 스크립트가 멈추는 것. - 흔한 실수:
check_mode: false를 대상을 바꾸는 태스크에 붙여 dry run 자체를 거짓말로 만드는 것. - 이 파드에는 감사 로그나 외부 승인 시스템이 없어, 승인 절차는 계획 산출물을 파일로 남기는 데까지만 다룹니다. capability 도 없어 서비스 재시작 같은 태스크는 다루지 않고 파일과 디렉터리만 씁니다.
- 점검 모드로 검증하기 · ansible-playbook 옵션 · lineinfile 모듈 · copy 모듈 · 조건문
만들자마자 점검 모드로 먼저 본다
/root/anschk/hosts.ini 를 만드세요 — [web] 에 web1(ansible_host=127.0.0.1, ansible_port=2222), [all:vars] 로 ansible_user=root. 그다음 /root/anschk/site.yml 을 만드세요: 플레이 변수 app_env(기본 lab)·app_dir_mode(기본 "0755")·motd_owner(기본 unset) 를 두고, 태스크 넷으로 /root/anschk/app 디렉터리를 app_dir_mode 권한으로 만들고, /root/anschk/app/app.conf 에 env=<app_env> 와 listen=8080 두 줄을 권한 0644 로 쓰고, /root/anschk/app/motd 에 Welcome=labhub 와 Owner=unset 두 줄을 권한 0644 로 쓰고, 마지막으로 motd 의 ^Owner= 줄을 Owner=<motd_owner> 로 맞춥니다(그 파일이 없으면 만들도록 create 를 켜세요 — 점검 모드에서 앞 태스크가 파일을 실제로 만들지 않기 때문입니다). 아직 수렴시키지 말고 --check --diff 로만 돌려 출력을 /root/anschk/out/check1.txt 에 저장하세요.
점검 모드는 대상에 변경을 가하지 않고, 변경이 일어났을 것 같으면 그 태스크를 changed 로 보고합니다. 그래서 아직 아무것도 없는 상태에서 돌리면 만들어질 것 전부가 changed 로 나옵니다. --diff 를 함께 주면 생길 파일의 내용이 + 줄로 보이는데, 없던 파일이 생기는 diff 는 앞쪽 범위가 0 으로 찍힙니다 — 그 표시가 '이때는 정말 파일이 없었다' 는 증거로 남습니다. 출력은 표준 출력과 표준 오류를 함께 받는 편이 안전합니다.
한 번 수렴시키고 값 셋을 바꿔 세 종류의 diff 를 본다
site.yml 을 --diff 와 함께 기본값으로 한 번 실제 실행해 출력을 /root/anschk/out/converge.txt 에 저장하세요. 그다음 값 셋을 명령줄로 덮어써서 --check --diff 로 다시 돌리세요 — app_env=stage, app_dir_mode=0750, motd_owner=platform-team. 그 출력을 /root/anschk/out/diff.txt 에 저장하세요. 실제 파일은 그대로 env=lab·권한 0755·Owner=unset 이어야 합니다.
--diff 는 점검 모드 전용이 아닙니다 — 진짜 실행에 붙이면 바꾸면서 무엇을 바꿨는지 보여 줍니다. 그 두 쓰임을 한 단계에서 나란히 봅니다. 세 값을 바꾸면 diff 도 세 종류가 나옵니다: 파일 내용이 바뀌는 diff, 파일 내용이 아니라 속성만 바뀌어 JSON 이 비교되는 diff, 그리고 한 줄만 바뀌는 diff. 명령줄 변수는 플레이 변수보다 우선하므로 -e 이름=값 을 세 번 주면 됩니다. 마지막에 실제 파일을 눈으로 확인하는 것을 잊지 마세요.
점검 모드에서 건너뛰어진 조회 태스크를 되살린다
/root/anschk/probe.yml 을 만드세요. ansible.builtin.command 로 getent passwd root 를 실행해 pw 로 register 하고(changed_when: false), 그 값과 지금이 점검 모드인지를 ansible.builtin.debug 로 check_mode=<참거짓> pwline=<읽은 값> 형태로 한 줄에 냅니다. 이 플레이북을 --check 로 돌린 출력을 표준 오류까지 /root/anschk/out/probe-check.txt 에 저장하되, 점검 모드에서도 실제 값이 찍혀야 합니다. 건너뛰어지는 태스크가 하나도 없어야 합니다.
점검 모드 지원 여부는 모듈이 정합니다. 명령 모듈은 그 명령이 무엇을 할지 알 수 없으니 지원하지 않고, 지원하지 않는 모듈의 태스크는 그냥 건너뛰어집니다. 건너뛰면 register 변수가 비고 뒤 태스크가 무너집니다 — 먼저 그대로 한 번 돌려 pwline= 뒤가 비는 것을 눈으로 보세요. 되살리는 키는 태스크에 붙이는 한 줄이고, 붙이는 기준은 하나입니다: 이 태스크가 대상을 바꾸지 않는다고 확신하는가.
점검 모드의 거짓 실패를 만들어 보고 가드로 고친다
/root/anschk/patch.yml 을 만드세요 — 태스크 둘입니다. 하나는 /root/anschk/app/fresh.conf 에 env=lab 과 listen=9090 두 줄을 권한 0644 로 쓰고, 다른 하나는 그 파일의 ^listen= 줄을 listen=9443 으로 고칩니다. 먼저 이 상태 그대로 --check 로 돌려 실패 출력을 표준 오류까지 /root/anschk/out/false-failure.txt 에 저장하세요. 그다음 뒤 태스크에 점검 모드에서는 건너뛰는 가드를 달고 다시 --check 로 돌려 그 출력을 /root/anschk/out/false-fixed.txt 에 저장하세요. 두 번 모두 점검 모드이므로 /root/anschk/app/fresh.conf 는 끝까지 만들어지면 안 됩니다.
점검 모드에서는 앞 태스크가 파일을 실제로 만들지 않습니다. 그러니 그 파일이 있다고 전제한 뒤 태스크는 없는 파일을 고치려다 죽습니다 — 플레이북이 틀린 게 아니라 점검 모드의 한계입니다. 실패 메시지에 그 사실이 그대로 적혀 나오니 먼저 읽어 보세요. 고치는 흔한 방법은 뒤 태스크에 조건을 하나 다는 것이고, 지금이 점검 모드인지는 매직 변수 하나가 알려 줍니다. 앞 태스크에 check_mode: false 를 붙이는 방법도 있지만 그 순간 dry run 이 아니게 되므로 이 단계에서는 쓰지 않습니다.
실제 실행에서도 절대 바꾸지 않는 태스크를 만든다
/root/anschk/dryrun.yml 을 만드세요. /root/anschk/app/never.conf 에 feature=on 한 줄을 권한 0644 로 쓰는 태스크 하나인데, --check 없이 평소대로 돌려도 절대 파일을 만들지 않고 바뀔 것이라는 보고만 해야 합니다. 같은 플레이북에 태스크를 하나 더 두세요 — /root/anschk/app/dryrun-ran.txt 에 real-run 한 줄을 권한 0644 로 쓰는 평범한 태스크입니다. 이 플레이북을 아무 플래그 없이 실행해 출력을 /root/anschk/out/dryrun.txt 에 저장하세요. 끝나면 /root/anschk/app/dryrun-ran.txt 는 있고 /root/anschk/app/never.conf 는 없어야 합니다.
앞 단계에서 쓴 키와 같은 이름의 키인데 값이 반대입니다. 그 키를 태스크에 달면 그 태스크만 언제나 dry run 으로 고정됩니다. 아직 켜면 안 되는 태스크를 플레이북에 미리 적어 두고 영향만 보고 싶을 때, 또는 위험한 태스크를 사람이 확인하기 전까지 묶어 둘 때 쓰는 자리입니다. 'changed 로 보고되는데 파일은 없다' 는 어색한 상태가 바로 이 키가 하는 일입니다.
세 도구의 종료 코드가 어디서 갈리는지 표로 세운다
/root/anschk/undef.yml 을 만드세요 — 정의되지 않은 변수(missing_var)를 내용에 쓰는 copy 태스크 하나입니다. /root/anschk/badmod.yml 도 만드세요 — 모듈 이름을 ansible.builtin.coppy 로 오타 낸 태스크 하나입니다. 그다음 /root/anschk/gates.sh 를 만들어 두 플레이북 각각에 --syntax-check·--list-tasks·--check 를 차례로 걸고 종료 코드만 /root/anschk/out/gates.txt 에 한 줄씩 적으세요: <파일이름> syntax-check=<코드> list-tasks=<코드> check=<코드>. 스크립트를 실행해 파일을 남기세요.
앞의 두 도구는 대상에 붙지도 않고 템플릿을 펼치지도 않습니다. 변수는 태스크가 실제로 점검 모드로 실행될 때 비로소 펼쳐집니다 — 그래서 두 플레이북의 결과가 서로 다른 모양으로 갈립니다. 어느 쪽이 어디서 처음 잡히는지가 이 단계의 답입니다. 종료 코드는 명령 바로 뒤에 $? 로 받습니다. 출력은 버리고 코드만 남기면 됩니다. CI 를 짤 때 이 표가 순서를 정해 줍니다 — 값싼 것부터 거는 이유가 여기 있습니다.
승인에 붙일 계획 산출물을 만든다
/root/anschk/plan.sh 를 만드세요. ansible.posix.json 을 stdout 콜백으로 지정해 site.yml 을 --check --diff -e app_env=stage 로 돌리고 그 JSON 전체를 /root/anschk/out/plan.raw.json 에 저장한 뒤, 거기서 바뀔 태스크의 이름만 뽑아 JSON 배열로 /root/anschk/out/plan.json 에 저장합니다. 스크립트를 실행해 두 파일을 남기세요. 배열에는 이름이 정확히 하나만 들어 있어야 합니다.
화면에 흘러가는 초록·노랑 글자는 승인 근거로 남지 않습니다. 콜백을 바꾸면 실행 전체가 구조화된 JSON 하나로 나옵니다 — 환경변수 ANSIBLE_STDOUT_CALLBACK 으로 지정합니다. 어떤 콜백이 있는지는 ansible-doc -t callback -l 로 봅니다. JSON 안에서 태스크는 .plays[].tasks[] 아래에 있고, 태스크 이름은 .task.name, 호스트별 결과는 .hosts 아래에 있습니다. app_env 만 바꿨으니 바뀔 태스크는 하나뿐입니다 — 나머지는 이미 그 상태라 changed 가 아닙니다.
점검 모드를 게이트로 바꾼다
/root/anschk/drift-gate.sh 를 만드세요. site.yml 을 점검 모드로 돌려 요약의 changed 가 0 이면 첫 줄이 CLEAN 으로 시작하는 메시지를 내고 0 으로 끝나고, 1 이상이면 첫 줄이 DRIFT 로 시작하는 메시지를 내고 1 로 끝납니다. 점검 모드 실행 자체가 실패하면 DRIFT-UNKNOWN 으로 시작하는 메시지를 내고 1 로 끝냅니다. 스크립트에 준 인자는 그대로 ansible-playbook 에 넘어가야 합니다. 수렴된 상태에서 인자 없이 돌린 출력을 /root/anschk/out/gate-clean.txt 에, -e app_env=stage 를 주고 돌린 출력을 /root/anschk/out/gate-dirty.txt 에 저장하세요.
수렴이 끝났는데도 점검 모드가 바뀔 것을 찾아낸다면 누군가 손으로 고쳤거나 플레이북이 스스로 수렴하지 못한다는 뜻입니다 — 그 조건에서 실패로 끝나는 스크립트를 CI 에 걸면 드리프트가 다음 배포 전에 드러납니다. 요약 줄에서 숫자를 뽑을 때는 changed= 뒤의 수를 보면 되고, 실행이 실패해 요약이 아예 없는 경우도 따로 다뤄야 합니다 — 숫자를 못 읽었는데 조용히 통과시키면 게이트가 아니라 장식이 됩니다. 스크립트 인자를 그대로 넘기는 방법은 "$@" 입니다. 게이트가 실패로 끝나므로 출력을 저장할 때 셸이 거기서 멈추지 않게 하세요.