Automation/Terraform

[Terraform 4장] How to Create Reusable Infrastructure with Terraform Modules

[앙금빵] 2025. 1. 5.

본 글은 Terraform Up& Running 책에 대하여 4장에 대한 내용 정리 글이다.


Terraform를 사용해 효율적으로 재사용 가능한 인프라를 구성하려면, 여러 환경(예: 스테이징, 운영환경)에서 동일한 코드를 반복해서 복사·붙여넣지 않고도 인프라를 손쉽게 배포할 수 있어야 한다. 이를 위해 가장 중요한 개념이 모듈(Module)이다.

 

스테이징과 프로덕션 환경의 유사성

  • 일반적으로 팀 내부 테스트를 위한 staging 환경과 실제 사용자를 위한 운영 환경으로 분리하여 운영한다.
  • 두 환경은 거의 동일해야 하며, 비용 절감을 위해 스테이징에서 리소스 규모가 운영환경 대비 규모가 작을 수 있다.

 

여러 환경에 동일한 코드를 반복해 작성하면 유지보수와 변경 관리가 복잡해지게 된다. 프로그래밍 언어에서 함수형 구조로 코드를 재사용하듯이, Terraform에서는 모듈로 코드를 추상화하여 재사용한다. 모듈을 활용하면 스테이징과 프로덕션 환경에서 같은 웹 서버, 데이터베이스 설정 등을 손쉽게 공유할 수 있다.

 

아래 그림과 같이 Staging, Prod 환경의 공통 코드를 한 군데 모듈에 모아두고, 그 모듈을 여러 환경(스테이징, 프로덕션 등)에서 호출만 하는 방식이다.

 

4-1. 테라폼 모듈

Terraform에서의 모듈(Module)이란 Terraform 구성 파일(.tf)들이 모여 있는 폴더 자체를 의미한다. 루트 모듈(Root Module)과 재사용 가능한 모듈(Reusable Module)의 차이점은 아래와 같다.

  • 루트 모듈: terraform apply를 직접 실행하는 모듈.
  • 재사용 가능한 모듈: 다른 모듈(또는 루트 모듈)에서 호출되어 재사용되는 모듈.

 

<예시 1. 기본 변수>

variable "cluster_name" {
  description = "The name to use for all the cluster resources"
  type        = string
}

variable "db_remote_state_bucket" {
  description = "The name of the S3 bucket for the database's remote state"
  type        = string
}

variable "db_remote_state_key" {
  description = "The path for the database's remote state in S3"
  type        = string
}

 

여기서 헷갈릴 수 있는 부분을 정리하면, Terraform에서 변수(variables.tf)는 “이 모듈이 어떤 값을 입력으로 받을 수 있다”는 선언 역할만 한다는 것이다. 실제로 그 변수에 할당될 구체적인 값은 모듈을 호출하는 쪽(예: 스테이징 환경의 stage/services/webserver-cluster/main.tf 등)에서 정의한다.

 

물론 모듈쪽 variable.tf 파일 내 default 설정을 통해 모듈을 호출하는 쪽에서 별다른 지정을 하지 않는 경우 기본값이 설정되어질 수 있도록 할 수 있다. 그러나 책의 예시에서는 환경마다 다른 값을 반드시 지정해주어야 하므로, default를 생략하고 모듈 호출 시 직접 입력하도록 의도하였다.

 

modules/…/main.tf 구성내용

이 부분(모듈 내부의 main.tf)이 곧 스테이징·프로덕션 등 여러 환경에서 공통으로 활용될 템플릿이며, 해당 모듈이 “어떤 리소스(EC2, ALB 등)를 어떻게 생성할 것인지”를 정의한 청사진(blueprint)역할을 한다.

 

modules/…/main.tf 의 역할을 정리해보자면,

(1) stage나 prod 같은 루트 모듈(환경별 디렉터리)에서 module "webserver_cluster" { ... } 블록으로 변수를 넘겨주면,

(2) 이 값들이 modules/…/main.tf 내 리소스 정의에서 사용하는 var.<변수명>에 매핑되어,

(3) 최종적으로 어떤 이름, 어떤 인스턴스 타입, 몇 대의 인스턴스를 띄울지 등 리소스의 구체적 모습이 결정되어 진다.

아래 alb 보안그룹 예시처럼, aws_security_group 리소스를 정의할 때 var.cluster_name이라는 입력 변수로부터 받은 값을 활용해 최종 리소스 이름을 결정한다. 만약 루트 모듈(stage/.../main.tf)에서 cluster_name = "webservers-stage"라고 전달했다면, 최종적으로 생성되는 Security Group 이름은 "webservers-stage-alb"가 된다.

 

<예시 1. alb 보안그룹 예시>

resource "aws_security_group" "alb" {
	name = "${var.cluster_name}-alb"
	
	ingress {
		from_port = 80
		to_port = 80
		protocol = "tcp"
		cidr_blocks = ["0.0.0.0/0"]
		}
		
	egress {
		from_port = 0
		to_port = 0
		protocol = "-1"
		cidr_blocks = ["0.0.0.0/0"]
		}
	}

 

4-3. 모듈 로컬 (Module Local)

  • 로컬 값(Local Values)은 모듈 내부에서 고정적으로 사용되는 값을 한 곳에서 관리하는 방법이다.
  • 같은 숫자나 문자열을 여러 리소스에서 반복할 때, 로컬 값으로 추상화하면 코드 중복과 실수를 크게 줄일 수 있다.
  • 입력 변수(Input Variables)를 최소화하여 불필요한 옵션 노출을 방지할 수 있게 된다.
  • 일반적으로 모듈 내부에서만 쓰는 고정·공통 값은 로컬 값으로 지정한다.

 

모듈 로컬은 단어 뜻 그래도 모듈 내부에서만 쓰이는 변수를 지칭한다. 모듈 외부에서 값을 주입하거나 변경할 수 없게 함으로써 잘못된 오버라이드 방지한다.

예를 들어, HTTP 80번 포트나 0.0.0.0/0 CIDR 같은 “고정값”을 여러 리소스에서 반복해 쓰는 경우,

하드코딩된 값을 직접 수정하다 보면 오탈자나 실수로 인한 에러가 발생할 수 있다. 이때, 로컬 값을 사용하면 이를 한 곳에서 정의를 함으로써 모듈 내부 전체에서 재사용이 가능토록 한다.

 

로컬 값은 locals { ... } 블록 내부에서 정의된다.

locals {
  http_port    = 80
  any_port     = 0
  any_protocol = "-1"
  tcp_protocol = "tcp"
  all_ips      = ["0.0.0.0/0"]
}

 

아래 예시는 ELB 보안 그룹에 local 항목을 활용한 결과이다. 이 결과물을 통해 여러 곳에 반복되던 80, 0.0.0.0/0, -1 등의 값을 모두 로컬 값으로 치환하여 관리가 되어질 수 있게 되어진다.

resource "aws_security_group" "alb" {
  name = "${var.cluster_name}-alb"

  ingress {
    from_port   = local.http_port
    to_port     = local.http_port
    protocol    = local.tcp_protocol
    cidr_blocks = local.all_ips
  }

  egress {
    from_port   = local.any_port
    to_port     = local.any_port
    protocol    = local.any_protocol
    cidr_blocks = local.all_ips
  }
}

 

로컬 값 vs 입력변수 차이점

  • 입력 변수(Input Variables)
    • 모듈을 사용하는 쪽(stg, prod)에서 값을 주입받음
    • 모듈 외부에서 환경(stg, prod)마다 서로 다른 값을 손쉽게 설정 가능
  • 로컬 값(Local Values)
    • 모듈 내부에서만 사용되며, 외부에서 변경 불가
    • “반복되는 값”이나 “중간 계산 결과” 등을 모듈 내에서만 재사용할 때 활용
    • 하드코딩 없이 모듈 코드 품질을 개선하고, DRY(Don’t Repeat Yourself) 원칙을 지킴

 

Module Output

  • 목적: 모듈 내부에서 생성되거나 계산된 값을 바깥(루트 모듈)으로 전달
  • 사용 형태: outputs.tf 파일에 output "<이름>" { value = ... } 형태로 정의
  • 접근 방법: 모듈을 호출하는 쪽에서는 module.<모듈_이름>.<output_이름> 구문으로 값을 가져옴

 

<예시: ALB dns 주소 확인>

출력 변수를 사용하여 모듈에서 생성한 ALB 주소를 외부에서 확인할 수 있다. 아래와 같이 모듈 내부, 외부를 구성함으로써 “모듈 → 루트 모듈” 순으로 정보가 전달되며 DNS 정보가 전달된다.

 

모듈 내부 (modules/…/outputs.tf)

output "alb_dns_name" {
  value       = aws_lb.example.dns_name
  description = "The domain name of the load balancer"
}

 

모듈 외부 (stage/…/outputs.tf 또는 prod/…/outputs.tf)

output "alb_dns_name" {
  value       = module.webserver_cluster.alb_dns_name
  description = "The domain name of the load balancer"
}

댓글