배경

Module 구조는 프로젝트를 진행할수록 단어 정의와 파일 위치가 계속 헷갈렸다. 특히 provider는 어디에 두는지, 변수 파일은 어느 쪽에 있어야 하는지, module과 이를 호출하는 코드는 어떻게 나누는지가 명확하지 않았다.

Terraform module은 반복해서 만드는 인프라 구성을 하나의 단위로 묶는 방법이다. 여기서는 재사용되는 module과 그것을 호출하는 source(root module)를 분리해서 이해한다.

구조

기본적인 골격은 다음과 같다. module/은 재사용할 인프라 정의를 담고, source/는 환경별 값과 provider 설정을 넣어 module을 호출한다.

terraform-project/
├── module/
│   ├── README.md
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
└── source/
    ├── versions.tf
    ├── providers.tf
    └── main.tf

각 파일 별 역할은 다음과 같다.

경로파일명역할
modulemain.tf실제 Terraform 리소스와 module 내부 로직을 정의하는 파일이다.
modulevariables.tfsource 쪽에서 전달받을 입력 변수를 선언한다. type, description, validation을 함께 둘 수 있다.
moduleoutputs.tf생성한 리소스의 ID, ARN, endpoint처럼 호출하는 쪽에서 다시 사용할 값을 반환한다.
sourcemain.tfmodule을 호출하고, 환경에 맞는 변수값을 전달하는 파일이다.
sourceproviders.tfroot module에서 provider의 인증 방식, region, alias 등 실제 연결 설정을 정의하는 파일이다. 재사용 child module에는 일반적으로 provider block을 두지 않고 호출하는 쪽의 설정을 전달받는다.
sourceversions.tfTerraform CLI 버전(required_version)과 필요한 provider의 source·버전 제약(required_providers)을 선언하는 파일이다.

변수와 provider의 흐름

source/main.tf는 module의 variables.tf에 선언된 입력값을 전달한다. module은 그 값을 이용해 리소스를 만들고, 필요한 결과는 outputs.tf로 source에 돌려준다.

Provider 설정은 보통 root module인 source/에 둔다. 이렇게 하면 같은 module을 dev, stage, prod처럼 다른 region·계정·인증 정보에서 재사용할 수 있다.

# source/providers.tf
provider "aws" {
  region = "ap-northeast-2"
}
 
# source/main.tf
module "network" {
  source = "../module"
 
  name = "hello"
  env  = "dev"
}
# module/variables.tf
variable "name" {
  type = string
}
 
variable "env" {
  type = string
}

source 경로는 module block이 있는 파일을 기준으로 해석된다. 위 예시처럼 source와 module이 같은 상위 디렉터리 아래에 있다면 ../module을 사용한다.

아래는 값만 바꿔 재사용하는 템플릿 형태다. 대괄호 안의 값은 모두 변수로 유지한다.

module "[MODULE_NAME]" {
  # 모듈 정의
  source = "../module"
 
  # 변수 정의
  name = "hello" # module/variables.tf에 정의되어 있어야 함
  env  = "dev"   # module/variables.tf에 정의되어 있어야 함
}

versions.tf 예시

terraform {
  required_version = ">= 1.6.0"
 
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

공유 module은 최소 호환 provider 버전을 선언하고, 실제 환경의 최대 버전 제약과 lock file 관리는 root module에서 하는 편이 충돌을 줄이는 데 도움이 된다.

외부 Repo 참고

원격 Module을 참고할 때는 Terraform Registry나 Git repository를 사용할 수 있다. 공용 module은 source와 버전을 함께 고정해, 의도하지 않은 변경이 반영되지 않도록 한다.

1. Terraform Registry

registry.terraform.io

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"
 
  name = "example"
  cidr = "10.0.0.0/16"
}

Registry module을 선택할 때 쓰는 템플릿은 다음과 같다.

module "[REMOTE_MODULE_NAME]" {
  source  = "[NAMESPACE]/[NAME]/[MODULE]"
  version = "[VERSION]"
}
2. Git

Git Repo를 직접 지정할 때는 ?ref=로 태그, 브랜치 또는 커밋을 지정할 수 있다.

module "network" {
  source = "git::https://github.com/example/terraform-aws-network.git//modules/vpc?ref=v1.2.0"
}

Git source를 직접 지정하는 템플릿은 다음과 같다.

module "[GIT_REMOTE_MODULE_NAME]" {
  source = "git::https://[GIT_REPO_NAME]//[MODULE_PATH]"
}

태그, 브랜치 또는 커밋으로 버전을 지정할 때는 ?ref= 뒤의 변수를 사용한다.

module "[GIT_REMOTE_MODULE_NAME]" {
  source = "git::https://[GIT_REPO_NAME]//[MODULE_PATH]?ref=[OPTIONAL_VALUE]"
}

Module을 수정한 뒤에는 terraform fmt, terraform validate와 함께 Terraform Test를 실행해 입력값과 output의 호환성을 확인한다.