LabHub

Terraform 실전 · 모듈화 · 이론

모듈 — 재사용보다 경계가 먼저다

LabHub 에서 이어서 보기

한 줄 요약

모듈은 코드를 짧게 만드는 장치가 아니라 변경이 번져 나가는 반경을 제한하는 장치다.

왜 이게 필요했나

같은 스택을 dev·stage·prod 에 만든다고 하자. 처음에는 디렉터리를 세 벌 복사하는 것이 가장 빠르다. 실제로 빠르다. 문제는 6개월 뒤다. prod 에 급하게 넣은 설정 하나가 dev 에는 없고, stage 에는 반쯤 들어가 있다. 세 디렉터리는 이제 서로 다른 생물이 되어 있고, "prod 에서만 나는 문제"의 원인을 찾으려면 세 파일을 나란히 놓고 diff 를 떠야 한다.

여기서 진짜 비용은 중복된 줄 수가 아니다. 변경을 세 번 해야 한다는 사실, 그리고 세 번 중 한 번을 빠뜨려도 아무도 모른다는 사실이다. 모듈은 이 문제를 "구현은 한 벌, 입력만 세 벌"로 뒤집는다. 그러면 변경은 한 곳에서 일어나고, 환경 사이의 차이는 코드가 아니라 값으로 드러난다.

어떻게 동작하나

모듈은 그냥 .tf 파일이 들어 있는 디렉터리다. 특별한 문법은 없고 관례가 있을 뿐이다.

| 파일 | 역할 |
| --- | --- |
| variables.tf | 이 모듈이 받는 입력. 모듈의 공개 API |
| main.tf | 구현. 밖에서는 보이지 않아야 하는 부분 |
| outputs.tf | 밖으로 내보내는 값. 모듈의 반환값 |

세 파일로 나누는 이유는 취향이 아니라 읽는 순서 때문이다. 남의 모듈을 처음 볼 때 사람이 알고 싶은 것은 "무엇을 넣으면 무엇이 나오나"이고, 구현은 그다음이다.

기억할 성질이 네 가지 있다.

첫째, 모듈은 캡슐이다. 모듈 안의 리소스는 밖에서 직접 참조할 수 없다. module.primary.local_file.box.filename 같은 주소는 존재하지 않는다. 값을 밖으로 꺼내려면 반드시 output 을 통과해야 한다. 귀찮아 보이지만, 이 제약 덕분에 모듈 안의 리소스 이름을 바꿔도 사용하는 쪽이 깨지지 않는다.

둘째, 주소가 바뀐다. 모듈 안 리소스의 상태 주소에는 module.<호출이름>. 접두어가 붙는다. 중첩하면 module.bundle.module.left.local_file.box 처럼 계속 쌓인다. 리팩터링이 곧 주소 변경이라는 사실이 3편의 상태 수술과 연결된다.

셋째, 프로바이더는 루트가 주입한다. 자식 모듈 안에 provider 블록을 두면 그 모듈을 쓰는 쪽이 프로바이더 설정을 바꿀 수 없고, 더 나쁜 것은 그 모듈 호출을 코드에서 지울 수 없게 된다는 점이다. 리소스를 파괴하려면 프로바이더 설정이 필요한데 그 설정이 사라진 모듈 안에 있기 때문이다. 그래서 모듈에는 required_providers 만 두고 실제 설정은 루트에 둔다.

넷째, 같은 소스를 여러 번 호출한다. 인스턴스를 늘리고 싶다고 모듈 디렉터리를 복사하면 처음의 복붙 문제로 돌아간다. module "primary"module "secondary" 처럼 같은 source 를 이름만 달리해 호출하는 것이 정답이다.

현장에서 만나는 모습

첫째, 나누는 기준은 "함께 바뀌는가"다. 리소스 종류별로 나누면(모두 vpc 모듈, 모두 iam 모듈) 겉보기에는 깔끔하지만 실제 변경은 늘 여러 모듈에 걸쳐 일어난다. 함께 태어나고 함께 죽는 것들을 한 모듈에 묶어야 한다. 상태 파일을 나누는 기준도 같다 — 폭발 반경과 apply 시간이 기준이지 폴더 미학이 아니다.

둘째, 검증은 모듈 경계에서 하는 것이 가장 싸다. 잘못된 값이 모듈 안으로 들어가면 오류는 저 아래 리소스에서 난다. variablevalidation 블록으로 입구에서 막으면 오류 메시지에 "어느 모듈의 어느 변수가 왜 거부됐는지"가 그대로 찍힌다. 디버깅 시간이 몇 배 차이 난다.

셋째, 모듈은 버전을 갖는다. 로컬 상대 경로는 학습과 단일 저장소에서는 편하지만, 여러 팀이 쓰는 모듈이라면 레지스트리와 버전 고정이 필요하다. 그러지 않으면 남의 커밋 하나가 우리 prod 계획을 바꾼다.

다음 실습에서 할 것

filebox 라는 작은 자식 모듈을 입력·구현·출력 세 파일로 만들고, 같은 소스를 primary·secondary 두 이름으로 호출한다. 모듈 안 리소스의 상태 주소가 어떻게 달라지는지 직접 확인하고, 모듈 안에서 다시 모듈을 부르는 중첩 구조를 만든다. 마지막에는 변수 검증으로 잘못된 값을 모듈 경계에서 거부하고, 세 모듈의 출력을 하나의 맵으로 모은다.