테라폼 기초 강좌 #8 모듈 기초: 리소스 묶음의 재사용과 레지스트리 모듈
버킷 하나로 시작한 코드가 시리즈를 지나며 인스턴스, 서브넷, 보안 그룹으로 불어났습니다. 이대로 실무 인프라를 담으면 main.tf는 수백 줄이 되고, “개발용과 운영용에 같은 구성을 두 벌” 같은 요구 앞에서는 결국 복사·붙여넣기가 시작됩니다. 함수 없이 스크립트 한 파일로 버티는 프로그래밍과 같은 상황입니다. 테라폼에서 함수에 해당하는 것이 모듈(module)입니다. 리소스 묶음에 이름을 붙여 재사용하는 단위이며, 이번 글에서 직접 만들고, 가져다 쓰고, 나누는 기준까지 정리하겠습니다.
모든 디렉터리는 이미 모듈입니다 #
모듈은 특별한 파일 형식이 아니라 .tf 파일이 들어 있는 디렉터리입니다. 그래서 지금까지 작업해 온 디렉터리도 이미 모듈이고, 테라폼은 이것을 루트 모듈(root module)이라고 부릅니다. 모듈의 인터페이스도 이미 배운 것들입니다. #3의 variable이 모듈의 입력이고, output이 모듈의 출력입니다. 그때는 사람이 값을 넣고 읽는 통로였지만, 모듈끼리 연결되면 호출하는 쪽이 variable에 값을 넣고 output으로 결과를 받아 가는 함수 시그니처가 됩니다. 새로 배울 것은 호출 문법 하나뿐입니다.
자식 모듈 만들기 #
정적 웹 사이트용 버킷 구성(버킷 + 버저닝 + 퍼블릭 차단)이 프로젝트마다 반복된다고 합시다. 이 묶음을 모듈로 빼냅니다. 관례상 modules/ 아래에 둡니다.
modules/
└── static-bucket/
├── variables.tf # 입력
├── main.tf # 리소스
└── outputs.tf # 출력# modules/static-bucket/variables.tf
variable "bucket_name" {
type = string
description = "생성할 버킷 이름"
}
# modules/static-bucket/main.tf
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "this" {
bucket = aws_s3_bucket.this.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# modules/static-bucket/outputs.tf
output "bucket_arn" {
value = aws_s3_bucket.this.arn
}모듈 안에서 리소스 이름을 this로 짓는 것은 커뮤니티 관례입니다. 모듈 이름이 이미 역할을 말해 주므로 안의 리소스에 또 역할 이름을 붙이지 않는 것입니다. 호출은 module 블록으로 합니다.
# 루트 모듈의 main.tf
module "assets" {
source = "./modules/static-bucket"
bucket_name = "my-assets-prod"
}
module "logs" {
source = "./modules/static-bucket"
bucket_name = "my-logs-prod"
}
output "assets_arn" {
value = module.assets.bucket_arn
}source가 모듈의 위치이고, 나머지 인수가 모듈의 variable에 들어갑니다. 같은 모듈을 다른 값으로 두 번 호출해 버킷 구성 두 벌이 나왔습니다. 모듈의 출력은 module.이름.출력이름으로 참조합니다. 한 가지 절차상 주의점은, 모듈을 추가하거나 source를 바꾸면 terraform init을 다시 실행해야 한다는 것입니다. init이 모듈을 설치하는 단계이기 때문이며, “Module not installed” 에러의 답은 대부분 init입니다.
레지스트리 모듈: 만들기 전에 찾아봅니다 #
모든 모듈을 직접 만들 필요는 없습니다. Terraform Registry에는 커뮤니티가 다듬어 온 공개 모듈이 있고, 특히 terraform-aws-modules 계열은 AWS 구성의 사실상 표준 모음입니다. VPC가 대표적입니다.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 6.0"
name = "main"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-2a", "ap-northeast-2c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.11.0/24", "10.0.12.0/24"]
}서브넷, 라우팅 테이블, NAT 게이트웨이까지 수십 개 리소스를 만드는 구성이 입력 몇 줄로 끝납니다. 레지스트리 모듈에서 지켜야 할 규칙은 version 고정입니다. provider와 달리 모듈은 version을 생략해도 동작하지만, 그러면 남이 올린 새 버전이 예고 없이 우리 인프라의 plan을 바꿉니다. 실전 시리즈에서 VPC를 다룰 때는 동작 원리를 이해하기 위해 직접 리소스로 만들어 본 뒤 모듈과 비교하겠습니다. 편리함에는 블랙박스라는 비용이 붙고, 기초를 아는 사람만 그 비용을 통제할 수 있기 때문입니다.
모듈을 나누는 기준 #
모듈을 배우면 모든 것을 모듈로 만들고 싶어지는데, 과도한 분리는 반복만큼 해롭습니다. 리소스 하나를 감싼 모듈은 인수를 전달만 하는 포장지라 코드만 늘리고, 반대로 인프라 전체를 하나의 거대 모듈에 넣으면 재사용 단위가 사라집니다. 쓸 만한 기준은 두 가지입니다.
- 반복이 실제로 두 번 이상 나타났을 때 그 묶음을 모듈로 만듭니다. 미래의 재사용을 상상하며 미리 만드는 모듈은 대개 인터페이스를 잘못 짚습니다.
- 함께 만들고 함께 지우는 단위로 자릅니다. “정적 사이트 버킷 세트”, “서비스 하나의 네트워크"처럼 수명을 같이하는 묶음이 좋은 모듈이고, 수명이 다른 것들(VPC와 그 위의 애플리케이션)을 한 모듈에 넣으면 작은 변경에도 큰 plan이 나옵니다.
이 기준으로 실전 시리즈에서 인프라 전체를 모듈로 구조화하는 리팩터링을 직접 해 보겠습니다.
정리 #
이번 글에서 다룬 내용입니다.
- 모듈은
.tf파일이 든 디렉터리이며, 지금까지의 작업 디렉터리도 루트 모듈입니다. variable이 입력, output이 출력입니다 - 자식 모듈은
module블록으로 호출하고module.이름.출력으로 결과를 받습니다. 모듈 추가 후에는 init이 필요합니다 - 모듈 내부 리소스 이름은
this로 짓는 것이 관례입니다 - 레지스트리의 terraform-aws-modules 계열은 검증된 표준 모음이며, 모듈에는 반드시 version을 고정합니다
- 모듈 분리는 반복이 실제로 나타났을 때, 수명을 같이하는 단위로 합니다
다음 글(#9 원격 state와 협업)은 기초 시리즈의 마지막 편입니다. #5에서 미룬 숙제, 노트북 로컬 디스크에 있는 state를 팀이 함께 쓰는 S3 백엔드로 옮기고 잠금까지 걸어 협업 준비를 마치겠습니다.