TFAS (TerraForm Addicted School)의 내용을 정리한 글입니다.
심각한 테라폼 중독입니다 - 책으로 스터디를 진행합니다.

테라폼을 사용하다 보면 비슷한 리소스 구성을 반복해서 작성하게 되는 경우가 많다. 이런 반복을 줄이고 인프라 코드를 재사용 가능한 단위로 관리할 수 있게 해주는 것이 바로 모듈이다.
테라폼의 모듈은 테라폼의 여러 구성 파일을 하나의 디렉터리에 모은 것으로 같이 사용하는 리소스들을 하나의 꾸러미로 담아 제공하는 일종의 패키지와 같다. 테라폼 모듈은 테라폼에서 리소스를 패키징하고 반복적으로 사용할 수 있는 가장 주요한 방법이다.
모듈 사용법
테라폼 명령을 실행할 수 있는 디렉토리를 루트 모듈이라고 한다. 이를 테라폼 실행 환경이라고도 부른다. 루트 모듈은 하위 모듈을 가질 수 있으며, 하나의 모듈을 여러 루트 모듈에서 재사용하는 것도 가능하다. 하위 모듈은 여러 테라폼 프로젝트에서 재사용할 수 있는 템플릿의 형태로 존재한다.
모듈은 크게 두 가지로 나눌 수 있다.
- 커스텀 모듈: 사용자가 직접 작성한 모듈이다.
- 공개 모듈: 테라폼 레지스트리에 공개된 모듈이다.
공개 모듈을 사용하는 경우, 테라폼 프로바이더와 마찬가지로 초기화 시점에. terraform 해당 모듈을 다운로드한다.
모듈 호출
모듈을 사용한다는 것은 모듈에서 정의한 변숫값을 입력하여 해당 리소스를 루트 모듈에 포함시키는 것을 의미한다. 예를 들어 AWS의 Auto Scaling Group을 사용해 EC2를 프로비저닝 하는 모듈이 있다면, 다음과 같이 호출할 수 있다.
module "instance" {
source = "./ec2-asg"
minimum_count = 3
maximum_count = 50
desired_count = 4
instance_type = "t2.micro"
}
source는 하위 모듈의 경로를 지정한다. 로컬 디렉터리 경로를 입력하거나, GitHub에서 호출하는 경우 해당 저장소명을 입력하면 된다.
공개 모듈
Terraform Registry
registry.terraform.io
테라폼 레지스트리에서 사람들이 올린 공개 모듈을 호출할 수 있다. 테라폼 레지스트리에서 필요한 모듈을 검색하여 요구사항에 맞는 모듈을 찾아낸다. 레지스트리에서 모듈을 검색해 들어가면 해당 모듈을 어떻게 설정할 수 있고, 어떤 변수를 가지고 있으며, 모듈에 들어가는 입력값과 프로비저닝 이후 출력되는 값을 확인할 수 있다.
공개 모듈의 경우 버전을 제공하기 때문에 특정한 버전의 모듈을 사용하기 위해선 version 인수를 지정해주어야 한다.
module "instance2" {
source = "./ec2-asg"
version = "5.8.1"
# version = "~> 5.8.1" # 비관적 제약조건
minimum_count = 3
maximum_count = 50
desired_count = 4
instance_type = "t2.micro"
}
providers 메타인수 사용
모듈 블록에는 count, for_each와 같은 메타인수 사용이 가능하다.
모듈 사용 시 중요한 메타인수는 providers 가 있는데, providers 메타인수는 프로바이더 설정을 하위 모듈로 넘겨주는 역할을 한다. 루트 모듈에서 여러 프로바이더를 정의하고 사용 가능한 것과 마찬가지로, 하위 모듈에서도 여러 프로바이더를 정의할 수 있다.
두 개의 AWS 리전을 사용하는 모듈의 경우 AWS 프로바이더를 두개 지정하고 해당 프로바이더를 하위 모듈로 넘겨야 한다.
provider "aws" {
region = "ap-northeast-2"
alias = "seoul"
}
provider "aws" {
region = "ap-northeast-1"
alias = "tokyo"
}
module "example" {
source = "./ec2-asg"
providers = {
aws = aws.seoul
aws = aws.tokyo
}
}
커스텀 모듈을 사용하자
모듈을 직접 만드는 이유
테라폼 레지스트리에 공개 모듈이 있는데 커스텀 모듈을 어째서 만드는 걸까? 공개 모듈은 범용 사례에 맞춰 만들어졌기 때문에 조금이라도 범용성을 넘어서는 요구사항이 있을 경우 테라폼 코드로 관리하는 게 어려울 수 있다.
공개 모듈은 모듈에 투자하는 초기 투자 비용이 낮다. 테라폼 숙련도가 낮고 시간과 인력이 부족하면 공개 모듈을 사용하는 것이 좋다. 다만 여유가 있으면 커스텀 모델을 작성하는 것을 권장한다.
공개 모듈은 오픈소스로 되어있다. 특정 요구사항을 만족하기 위해 만들어진 것이 아니라 범용성 위해 만들어져 있다. 그렇기 때문에 모듈의 요구사항이 복잡하거나 커스텀한 경우 사용하기 어려운 단점이 있다. 그런 특정한 사용 사례를 만족시키기 위해 공개 모듈을 사용한다면 복잡한 모듈의 내부를 해부하느라 시간을 보낼 수 있다. 또한 오픈소스의 특성상 관리를 하지 않게 되는 경우 추가 패치가 없을 수 있다.
커스텀 모듈을 직접 만들어 사용하면 여러 사용사례에 맞춰 유연하게 모듈을 구성할 수 있다. 사내 보안 정책, 네트워크 설계, 태그 정책 등 조직의 고유한 요구사항을 반영할 수 있다. 테라폼을 관리하는 엔지니어팀에서 일관된 패턴과 표준을 적용하여 재사용이 가능하며 여러 프로젝트에 걸쳐 동일한 방식으로 활용할 수 있다.
모듈을 만드는 방법
그렇다면 어떻게 모듈을 만들 수 있을까? 아래 단계를 차례대로 진행한다면 직접 모듈을 구성할 때 쉽게 접근하고 생성할 수 있다.
1. 요구사항 정리 및 입력값 정하기
테라폼은 인프라를 만들고 관리하는 도구이며 실제 만들어지는 인프라가 본질이다. 따라서 리소스를 어떻게 만들고 관리할 것인지에 대한 요구사항은 반드시 정리해야 하며 어떤 입력값을 이용해 리소스의 상태를 변경하거나 생성할 수 있을지 결정해야 한다.
예를 들어 Amazon ElastiCache의 Redis OSS 리소스를 만드는 모듈을 구성한다고 했을 때 요구사항을 가정한다면 다음과 같다.
- 레디스 리소스를 생성, 수정, 삭제할 수 있어야 한다.
- 태그엔 반드시 Name, Env, Team 이 포함되어야 하며, Env 태그는 사내 환경 이름 중 하나만 입력할 수 있다.
- 포트는 6379만 허용하며 다른 포트는 허용하지 않은다.
- 서브넷 그룹, 보안 그룹 등을 리소스 별로 지정할 수 있지만 그렇지 않는 경우 적절한 기본값이 존재해야 한다.
- 엔진 버전, 노드 타입, 샤드 수, 레플리카 수도 쉽게 입력받을 수 있어야 한다.
결국 테라폼을 아무리 잘 사용하고 잘 알더라도 생성할 리소스에 대한 이해도가 없다면 리소스 생성에 제약이 걸린다.
이처럼 모듈에 대한 요구사항이 정리되었다면 해당 리소스를 어떻게 운영할지, 어떤 파일에서 어떤 값을 수정하여 해당 리소스에 영향을 미치게 할 것인지도 설계하는 게 좋다.
2. 입력값을 모듈에게 전달할 방법 정하기
입력값을 앞서 정했다면 이를 어떻게 모듈에 전달할 것인지 정해야 한다. 명세를 하나의 파일에서 할 수도 있고 여러 파일들을 통해 관리할 수 있다. 명세 파일을 어떻게 가져와 모듈에 맞게 변환할지 정의할 수도 있다. 하나의 vpc에서 생성하는지 여러 vpc 에서 생성하는지에 따라 여러 방법이 있을 수 있다.
3. 모듈 만들기
- 변수 임시 설정
앞서 정의한 입력값은 변수 블록으로 구성할 수 있다. 변수 블록 선언 시 설명, 타입 등을 지정 가능한데 모듈 작성 초기부터 구체적인 값을 지정하고 모듈을 만들면 모듈을 만드는 도중에 변경이 있는 경우 변수 블록도 계속해서 수정을 해야 하기 때문에 번거로울 수 있다 따라서 큰 틀에서 입력값이 있을 것으로 예상하고 임시로 설정하고 진행하는 것이 좋다.
- 모듈 속 공통 태그 설정
리소스를 만들 때 태그를 반드시 지정하는 것이 좋다. 모듈 내에서 공통으로 사용할 태그를 선언해 두면 리소스를 관리함에 있어 편하게 사용이 가능하다.
- 리소스 생성에 필요한 블록 설정
필요한 로직에 맞는 블록을 선언한다. 테라폼에서 제공하는 여러 블록은 여러 방식으로 구성할 수 있다. 모듈 작성에 사용할 수 있는 기본적인 모듈 구조부터 시작해서 리소스를 작성해 나갈 수 있다.
4. 유효성 검사
리소스 생성에 대한 코드가 완성되었다면 입력값을 제한하거나 하는 요구사항을 유효성 검사를 통해서 확인할 수 있다. 임시로 설정해 둔 변수 블록에 각 타입을 지정하는 것도 유효성 검사에서 확인하고 검증할 수 있다.
5. 모듈 출력 설정하기
모듈에서 생성된 리소스의 출력값을 모듈 밖에서 참조하기 위해서는 출력 블록을 선언해야 한다. 당장 해당 모듈의 출력값이 필요하지 않다면 선언되지 않을 수 있다. 나중에 특정한 값을 참조해야 하는 시점이 온다면 그때 추가해도 충분하다.
모듈 기본 구조
앞서 커스텀 모듈을 작성하는 이유와 커스텀 모듈을 작성하는 순서에 알아봤다. 커스텀 모듈을 만들 때 강제적으로 적용해야 하는 구조가 있는 것은 아니다. 다만 관습적으로 사용하는 기본적인 구조가 있기 때문에 이 구조에서부터 모듈을 작성해 보자.
.
|- main.tf
|- variables.tf
|- outputs.tf
|- locals.tf
|- modules/
| |- AAAA
| | |- main.tf
main.tf - 엔트리 포인트 정의
모듈을 작성할 때 리소스 생성하는 엔트리 포인트로 main.tf 파일을 생성하는 것을 권장한다. 해당 모듈의 설정과 실행에 필요한 사항인 프로바이더 설정, 모듈 설정등을 하나의 파일에서 관리하고 역할을 명확하게 하기 위해 main.tf로 사용하는 것이다. 이때 main.tf 는 반드시 사용해야 하는 파일 이름이 아니다. 경우에 따라 더 적절한 이름이 있는 경우 파일명 변경을 고려해도 좋다.
모듈의 규모가 작으면 main.tf 안에서 모든 리소스를 정의할 수 있다. 모듈의 규모가 큰 경우 리소스 정의를 다른 파일로 분리할 수 있다. 모듈 전체에서 사용할 테라폼 프로바이더나 하위 모듈에 관한 내용은 main.tf 에 두는 것이 좋다.
리전별로 AWS 프로바이더 두 개를 지정하고 S3 버킷 이름의 접두사를 입력받아 리전마다 S3 생성하는 모듈의 엔트리 포인트는 다음과 같이 정의할 수 있다.
provider "aws" {
alias = "seoul"
region = "ap-northeast-2"
}
provider "aws" {
alias = "tokyo"
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "bucket_seoul" {
provider = aws.seoul
bucket = "${var.bucket_prefix}-seoul"
}
resource "aws_s3_bucket" "bucket_tokyo" {
provider = aws.tokyo
bucket = "${var.bucket_prefix}-tokyo"
}
variables.tf - 변수 정의
모듈의 입력 변수를 variables.tf 파일에 모아 관리하는 것이 좋다. 모듈은 입력을 받아 리소스를 생성하고 출력하는데, 입력으로 사용할 변수들을 하나의 파일에 모아 관리하면 필요한 변수와 설명을 확인하기 쉽다.
variable "bucket_prefix" {
type = string
default = "버킷의 접두사"
}
outputs.tf - 출력값 정의
모듈을 통해 인프라를 조작한 후 결과를 외부에 출력하기 위해 출력 블록을 사용한다.
output "bucket_seoul_id" {
value = aws_s3_bucket.bucket_seoul.id
description = "서울 버킷 ID"
}
output "bucket_tokyo_id" {
value = aws_s3_bucket.bucket_tokyo.id
description = "도쿄 버킷 ID"
}
이렇게 정의한 출력값은 module.example.bucket_seoul_id와 module.example.bucket_tokyo_id와 같은 리소스 주소로 접근할 수 있다.
locals.tf - 로컬 변수 정의
모듈 밖으로는 출력되지 않지만 모듈 안 여러 곳에서 재사용할 수 있는 값들을 로컬 변수로 정의할 수 있다. 실행 환경의 변수는 사용자가 조작하여 설정할 수 있기 때문에, 내부적으로만 사용하는 값은 변수 블록 대신 로컬 블록으로 사용하는 것이 적절하다.
# locals.tf
locals {
bucket_prefix = "terraform-s3"
}
# main.tf
provider "aws" {
alias = "seoul"
region = "ap-northeast-2"
}
provider "aws" {
alias = "tokyo"
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "bucket_seoul" {
provider = aws.seoul
bucket = "${local.bucket_prefix}-seoul"
}
resource "aws_s3_bucket" "bucket_tokyo" {
provider = aws.tokyo
bucket = "${local.bucket_prefix}-tokyo"
}
중첩 모듈
모듈 안에 서브 디렉터리로 모듈을 정의하여 사용할 수 있는데, 이를 중첩 모듈이라고 한다. 컬렉션을 순회하며 반복적으로 리소스를 생성해야 할 때, for_each나 for를 사용해서 구현할 수 있지만 오류의 소지가 크다. 이럴 때 중첩 모듈을 사용하면 루트 모듈의 가독성을 높일 수 있다.
일반적으로 중첩 모듈은 /modules 서브 디렉터리에 두는 것이 관례이다.
# modules/nested-module/main.tf
resource "aws_instance" "example" {
ami = "ami-123123123123"
instance_type = "t3.micro"
tags = {
Name = var.instance_name
}
}
resource "aws_s3_bucket" "example" {
bucket = var.bucket_name
}
resource "aws_s3_bucket_acl" "example" {
bucket = aws_s3_bucket.example.id
acl = "private"
}
# modules/nested-module/variables.tf
variable "instance_name" {
type = string
}
variable "bucket_name" {
type = string
}
# modules/nested-module/outputs.tf
output "instance_id" {
value = aws_instance.example.id
}
output "bucket_arn" {
value = aws_s3_bucket.example.arn
}'School > TFAS' 카테고리의 다른 글
| [TFAS] 챕터 11 YAML 파일로 관리하는 VPC 모듈 만들기 (0) | 2025.12.04 |
|---|---|
| [TFAS] 테라폼 기능별 실무 사례 (챕터 6, 7, 8, 9) (1) | 2025.12.01 |
| [TFAS] 챕터 4 테라폼 기본 문법 (1) | 2025.11.25 |
| [TFAS] 챕터 3 테라폼 작동 방식 (0) | 2025.11.24 |
| [TFAS] 챕터 2 우리는 왜 테라폼을 쓰는가? (0) | 2025.11.15 |