LabHub
배우기 러닝패스 코스

Ansible 기초 · 큰 플레이북의 일부만 돌리기 · 이론

태그와 부분 실행 — 자른 실행은 최종 상태를 약속하지 않는다

LabHub 에서 이어서 보기

한 줄 요약

태그는 "이번에는 이것만" 을 고르는 장치이고, 붙은 자리에서 아래로 상속된다. 그런데 태그로 자른 실행은 플레이북이 약속한 최종 상태를 보장하지 않는다 — 그 사실을 아는 것이 태그 문법보다 중요하다.

왜 이게 필요했나

플레이북은 자란다. 처음엔 태스크 다섯 개였는데 반년 뒤엔 여든 개다. 그중 설정 파일 한 줄만 고치고 싶은 날이 온다. 전체를 돌리면 팩트 수집부터 패키지 확인까지 십오 분이 걸리고, 그사이 건드리고 싶지 않은 태스크가 스무 개 지나간다.

태그는 이 문제에 대한 답이다. 태스크에 이름표를 붙여 두고 --tags config 로 그 이름표가 붙은 것만 돌린다. 반대로 --skip-tags slow 로 느린 것만 뺄 수도 있다. 문법은 간단하고, 그래서 사람들은 문법을 배운 다음 날부터 운영에서 쓴다.

여기서 사고가 난다. 태그는 실행을 자르는 칼인데, 플레이북은 잘려도 되게 설계되어 있지 않다. 8번 태스크가 만든 디렉터리를 12번 태스크가 쓰고, 20번 태스크가 바꾼 설정을 핸들러가 받아 서비스를 다시 읽는다. --tags config 로 12번만 돌리면 디렉터리가 없어 실패하거나, 더 나쁘게는 성공한 척하면서 반쯤 맞는 상태를 남긴다.

그래서 이 모듈의 주제는 두 겹이다. 태그의 문법과 상속이 한 겹이고, 잘라도 되는 플레이북을 어떻게 알아보는가가 다른 한 겹이다.

어떻게 동작하나

태그는 네 자리에 붙고 아래로 상속된다.

| 붙는 자리 | 상속 범위 |
| --- | --- |
| 태스크 | 그 태스크 하나 |
| 블록 | 블록 안의 모든 태스크 |
| 플레이 | 그 플레이의 모든 태스크 |
| 롤 / import_tasks / include_tasks | 아래 설명 |

--list-tasks 를 돌려 보면 상속이 눈에 보인다. 플레이에 playtag 를 걸어 두면 모든 태스크의 태그 목록에 playtag 가 따라붙는다. 블록에 blocky 를 걸면 블록 안 두 태스크가 각각 blocky 를 갖는다.

import_tasksinclude_tasks 는 여기서 갈린다. import_tasks정적이다 — 플레이북을 읽을 때 태스크가 그 자리에 펼쳐지고, import 문에 붙은 태그가 펼쳐진 태스크 하나하나에 상속된다. include_tasks동적이다 — 실행 중에 비로소 태스크가 끼어든다. 그래서 include 문에 붙인 태그는 include 문 자체에만 붙고, 안으로 상속되지 않는다.

이 차이가 만드는 결과는 처음 보면 당황스럽다. --tags included 를 주면 include 문은 태그가 맞아 실행되고, 그 안에서 끼어든 태스크들은 included 태그가 없으니 전부 걸러진다. 아무 오류 없이 아무 일도 일어나지 않는다. 공식 문서는 include 안의 태스크에도 태그를 걸고 싶으면 apply 키워드를 쓰거나 아예 import_tasks 를 쓰라고 안내한다.

특수 태그 다섯 개가 있다.

| 태그 | 뜻 |
| --- | --- |
| always | --skip-tags always 로 명시적으로 빼지 않는 한 언제나 돈다 |
| never | 이름을 직접 부르지 않으면 절대 안 돈다 |
| tagged | 태그가 하나라도 붙은 태스크 전부 |
| untagged | 태그가 하나도 없는 태스크 전부 |
| all | 전부(기본값) |

always 는 인벤토리 사실 수집이나 공통 변수 설정처럼 어떤 선택에서도 빠지면 안 되는 태스크에 건다. never 는 위험한 태스크 — 데이터베이스를 지우거나 전체를 다시 배포하는 것 — 를 플레이북에 적어 두되 손이 미끄러져 돌지는 않게 한다. never 가 붙은 태스크에 danger 같은 이름표를 함께 달아 두면 --tags danger 로 부를 때만 돈다.

주의할 자리가 하나 있다. 플레이에 태그를 걸면 그 플레이의 모든 태스크가 '태그가 있는' 것이 된다. 그러면 --tags untagged 는 아무것도 고르지 않고, --tags tagged 는 전부를 고른다. 상속은 이렇게 특수 태그의 뜻까지 바꾼다.

돌기 전에 목록을 본다. --list-tags 는 이 플레이북에 어떤 태그가 있는지 보여 주고, --list-tasks이번 선택으로 무엇이 돌지 순서대로 보여 준다. 둘 다 대상에 붙지 않고 아무것도 바꾸지 않는다. --tags 를 붙여 함께 주면 그 선택이 그대로 반영된 목록이 나온다 — 운영에서 태그를 처음 쓸 때는 이걸 먼저 보는 습관이 사고를 절반으로 줄인다.

중간부터 돌리는 길도 있다. --start-at-task "태스크 이름" 은 그 이름의 태스크부터 시작한다. 긴 플레이북이 40번에서 죽었을 때 1번부터 다시 돌리지 않으려고 쓴다. --step 은 태스크마다 사람에게 물어보며 진행한다 — 대화형이라 자동화에는 못 쓰지만 처음 보는 플레이북을 손으로 따라갈 때 쓸모가 있다.

--start-at-task 는 태그보다 더 위험하다. 앞 태스크가 만든 것도, 앞 태스크가 보낸 notify 도 전부 없는 상태로 중간부터 시작하기 때문이다. 끝났을 때 failed=0 이 나와도 시스템은 플레이북이 약속한 상태가 아니다.

부분 실행이 위험한 두 가지 방식을 정리하면 이렇다.

첫째, 의존 태스크가 빠진다. 3번 태스크의 set_fact 가 정한 값을 7번 태스크가 쓰는데 --tags 로 7번만 고르면 그 변수는 정의되지 않은 채다. 운이 좋으면 즉시 실패하고, 운이 나쁘면 기본값으로 엉뚱한 것이 들어간다.

둘째, 핸들러가 안 돈다. 핸들러는 notify 를 받아야 돈다. 설정을 바꾸는 태스크가 선택에서 빠지면 핸들러는 부를 사람이 없어 조용히 넘어간다. 거꾸로 설정 태스크만 고른 실행에서는 핸들러가 정상적으로 돈다 — 즉 핸들러의 실행 여부는 '누가 notify 했나' 로 정해지지 '태그' 로 정해지지 않는다. 이 둘을 헷갈리면 "태그를 걸었더니 서비스가 재시작되지 않았다" 를 엉뚱한 곳에서 찾게 된다.

현장에서 만나는 모습

첫째, --tags 로 첫 배포를 한다. 새 서버에 --tags config 만 걸면 디렉터리가 없어 실패한다. 태그는 이미 한 번 전체가 돌아간 시스템에 쓰는 도구다. 첫 수렴은 전체로 한다.

둘째, 태그가 문서를 대신하게 된다. 태그가 서른 개가 되면 어느 것을 걸어야 안전한지 아무도 모른다. 태그는 다섯 개 안팎으로 유지하고, 세트로 쓰는 조합은 스크립트나 Makefile 로 이름을 붙여 둔다.

셋째, never 를 안 걸어 사고가 난다. "전체 재설치" 태스크를 주석 처리해 두는 팀이 있다. 주석은 언젠가 풀린다. never 태그를 걸면 코드로 남되 실수로 돌지 않는다.

넷째, 중간부터 돌린 뒤 초록불을 믿는다. --start-at-task 로 되살린 배포가 성공으로 끝나도 앞쪽 태스크가 남겼어야 할 것은 없다. 복구 뒤에는 전체를 한 번 더 돌려 changed=0 을 확인하는 것이 실제 종료 조건이다.

다섯째, 이 실습 환경의 정직한 한계. --step 은 사람이 키를 눌러야 진행하는 대화형 모드라 채점기가 판정할 수 없어 실습에서 뺐다. 롤에 태그를 거는 이야기도 롤 자체가 ansible-advanced 의 주제라 여기서는 다루지 않는다.

참고 문서

다음 실습에서 할 것

태스크에 태그를 달아 --tags 로 좁히고, --list-tags--list-tasks --tags 로 돌기 전에 무엇이 돌지 먼저 보고, --skip-tags 로 빼 본다. 블록과 플레이에 태그를 걸어 상속이 목록에 어떻게 나타나는지 확인하고, alwaysnever 를 달아 특수 태그의 뜻을 직접 재고, import_tasksinclude_tasks 를 나란히 놓아 태그가 상속되는 쪽과 안 되는 쪽을 목록으로 가른다. --start-at-task 로 중간부터 돌린 뒤 설정이 옛 값 그대로이고 핸들러가 돌지 않은 것을 확인하고, 마지막으로 어떤 태그 선택이 완주할 수 있는지 판정하는 도구를 직접 만든다.