CLOUDWAVE

IaC : Terraform - 테라폼 모듈(module)로 재사용가능한 인프라 구성

갬짱 2025. 2. 7. 23:45

 

각 환경( 스테이징, 프로덕션 .. )의 인프라는 거의 동일하지만 미세한 차이가 있는 정도

⇒ 코드를 복사하여 붙여넣기

⇒ HCL에서 중복되는 코드를 모듈로 만들어서 여러 위치에서 재사용

( 범용프로그래밍 언어에서 동일한 코드를 함수내부에 배치하여 재사용하는 개념과 유사 )

 

코드 이전작업없이 모듈을 참조하여 동일한 코드환경을 가지게 됨( 재사용성 )

⇒ 모듈이란 여러환경에서 동일한 인프라환경을 구축하기 위해 사용하는 기술

 

 

 

기능 디렉토리별로 모듈을 만든다고 생각함( services/web-cluster, services/data-store 등 )

  • root 모듈 : terraform apply 명령을 수행하는 위치, 직접 적용을 실행하는 모듈
  • other 모듈 : 테라폼 구성파일세트, 루트 모듈 안에서 module 블록을 사용하여 하위 모듈을 참조하고 사용하는 대상 → 재사용가능 외부 디렉토리, terraform registry(공식 저장소), git 등에 존재함

 

모듈기본

(1) 모듈설정

  • module이라는 최상위 폴더를 만들고 웹서버구축하는 테라폼 파일을 모두 배치
    tfd이후 복사 cp -r stage module
    stage/services/webserver-cluster의 모든 파일 → module/services/webservercluster의 경로로 이동

 

  • 공급자 설정 제거 → 루트 모듈에서만 존재해야함
    vi module/services/webserver-cluster/main.tf
    provider “aws” 블럭제거 & 기존 리소스 정의내용 유지

 

(2) 모듈호출

[ 모듈호출구문 ]

module "<NAME>" {
 source = "<SOURCE>"
 [CONFIG ...]
}
  • NAME : 테라폼 전체 코드에서 모듈을 참조하는 식별자
  • source : 모듈코드를 찾을 수 있는 경로 새로운 모듈을 적용(apply)하기 전에 항상 terraform init명령을 실행( initializing modules = 다운로드 )
  • config : 모듈과 관련된 인수 → 모듈에서 사용할 변수값을 지정( apply환경에 맞게 동적 설정 )

[ 루트모듈 : stage/services/webserver-cluster/main.tf ]

provider "aws" {
 region = "ap-northeast-2"
}

module "webserver_cluster" {
 source = "../../../modules/services/webserver-cluster"
}

프로바이더는 caller(root module)에서 정의 & 나머지 리소스 정의 코드는 모듈을 호출하는 것으로 대체

module의 source : local의 모듈 경로 혹은 외부에서 사용할 모듈주소

 

모듈입력

[ 변수사용 메커니즘 ]

: 모듈 디렉토리 하에서 입력변수파일을 정의(variables.tf) & 사용(main.tf)루트 모듈에서 호출하여 사용할때 변수값 치환 및 지정가능

( 범용프로그래밍 언어의 함수가 매개변수를 가지는 것처럼 모듈에도 입력변수를 적용가능 )

 

(1) 데이터베이스 원격 상태에 대한 입력 변수 추가 ( 클러스터명, 공유저장소 버킷 영역 변수화 )

환경에 따라 다른 값으로 설정되는 변수( ex. 프로덕션 환경과 스테이지 환경에서 리소스 상태파일 저장소 루트(db_remote_state_key)는 상이함 )

 

모듈내 변수파일( modules/services/webserver-cluster/variables.tf )에서 변수로 선언

 

modules/services/webserver-cluster/variables.tf

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
}

 

모듈내 실행파일(modules/services/webserver-cluster/main.tf)에서 변수참조식으로 이를 활용 ( var.변수명 )

 

(2) 리소스 환경 구성에 대한 입력변수 추가 ( 인스터스 타입, ASG 최대최소값 변수화 )

환경에 따라 다른 값으로 구축가능한 변수( ex. 스테이지 환경에서는 비용절감을 위해 더 적은 웹서버 클러스터 실행, 소규모 타입으로 구성 ↔ 실제 프로덕션 환경에서는 대량 트래픽을 처리하기 위해 많은 웹서버 클러스터 실행, 대규모 타입으로 구성 )

 

모듈내 변수파일( modules/services/webserver-cluster/variables.tf )에서 변수로 선언

 

모듈내 실행파일(modules/services/webserver-cluster/main.tf)에서 변수참조식으로 이를 활용 ( var.변수명 )

 

(3) 루트 모듈에서 반드시 선언한 변수값을 정의

 

stage/services/webserver-cluster/main.tf

해당모듈 내에 변수값을 정의

입력 변수는 모듈의 API로, 모듈이 다양한 환경에서 어떻게 작동할지 제어

 

 

모듈지역화

: 모듈내에서만 변수를 사용가능하도록 제어( 입력값의 오류방지 )

중복값에 대한 효율적인 처리를 위해 모듈변수를 사용하되( 코드를 DRY로 유지 ) 변수를 외부에 노출하고 싶지 않은 경우( 오류방지, 보안 등.. )

  • 선언 : locals 블록항목에서 변수명&값 설정( main.tf하단 혹은 별도의 locals.tf로 분리 )
  • 참조 : local.<NAME>

 

LB의 port = LB에 적용되는 security그룹의 ingress port = 80으로 지정

⇒ 변수로 설정( 하드코딩X ) 그러나 모듈 이외의 임의영역에서 접근 및 설정불가하도록 지정 ( 루트모듈에서도 설정불가 )

 

[ 로컬변수 실습 ]

(1) 모듈main.tf 하단에 지역변수를 선언 → 별도의 locals.tf를 구축하여 값을 지정 및 선언하는 것이 효과적임

 

~/terraform-demo/environment/modules/services/webserver-cluster/main.tf

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

 

(2) 로컬변수를 랜더링하도록 수정

 

~/terraform-demo/environment/modules/services/webserver-cluster/main.tf

 

 

*modules/services/webserver-cluster/variables.tf 에 정의한 변수들과는 상이함 : 외부 입력불가능, local.변수로 참조( ≠ var.변수 )

 

 

모듈 출력

모듈에서 출력하는 output 값 지정( 생성한 리소스에 대한 추가정보 ) → 루트 모듈에서 이를 참조하고 활용할 수 있도록 하면 효율적

 

[ 모듈의 출력변수 정의 ] 모듈내 outputs.tf 혹은 main.tf

output "asg_name" {
 value = aws_autoscaling_group.example.name
 description = "The name of the Auto Scaling Group"
}

output.tf로 분리하여 정의가능하나 main.tf에 통합하여 정의하도록함

modules/services/webserver-cluster/main.tf

 

[ 모듈 출력변수 참조방식 ]

module . <MODULE_NAME> . <OUTPUT_NAME>

 

[ 모듈 출력변수 참조 ] 루트모듈 내 main.tf

해당 모듈의 해당 output변수값을 참조

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

stage/services/webserver-cluster/main.tf

 

 

루트 모듈에서 terraform apply 실행시 출력변수가 제대로 출력된다.

 

모듈 고려사항

(1) 파일경로

 

Terraform의 경로참조 표현식 path.<TYPE>

  • 경로.모듈 (path.module) : 표현식이 정의된 모듈의 파일 시스템 경로를 반환
  • 경로.루트 (path.root) : 루트 모듈의 파일 시스템 경로를 반환
  • 경로.cwd (path.cwd) : 현재 작업 디렉터리의 파일 시스템 경로를 반환 ( 일반적으로 path.root와 동일 )

 

templatefile함수에서 사용하는 파일은 항상 상대경로를 기준으로 지정(Terraform 코드는 각기 다른 디스크 레이아웃을 가진 여러 컴퓨터에서 실행될 수 있으므로 절대 파일 경로를 피하는게 좋음 )

Terraform은 현재 작업 디렉토리를 기준으로 경로를 해석 → 루트모듈이 존재하는 작업디렉토리에서 파일을 탐색하게 됨

resource "aws_launch_configuration" "example" {
  image_id        = "ami-024ea438ab0376a47"
  instance_type   = var.instance_type
  security_groups = [aws_security_group.instance.id]
  user_data       = templatefile("user-data.sh",{
  server_port = var.server_port
  db_address = data.terraform_remote_state.db.outputs.address
  db_port = data.terraform_remote_state.db.outputs.port
})
  lifecycle {
    create_before_destroy = true
  }
}

root모듈에 모듈에서 사용하는 파일을 다운로드하여 직접 참조

하지만 이는 바람직하지 않음 모듈내에 참조할 파일이 함께 제공되는 것이 일반적이고 이를 사용해야함 → 모듈경로를 기준으로 상대경로로 참조 ( 변경 및 오류 대응가능, 효율적 )

 

먼저 루트모듈경로로 복사해온 모듈의 파일은 삭제한다 -> 모듈내의 해당 user_data.sh를 사용한다.

 

경로참조 표현식에서 모듈 경로를 나타내는 ${path.module}을 이용하여 파일의 상대경로를 완성한다.

사실상 변경사항은 없다.

 

 

(2) 인라인 블록

리소스 내에 설정하는 인수

resource "type" "name" {
 <NAME> {
  [CONFIG...]
 }
}

NAME은 인라인 블록의 이름(예: ingress )이고 CONFIG는 해당 인라인 블록(예: from_port 및 to_port)과 관련된 하나 이상의 인수로 구성

 

인라인 블록과 별도의 리소스를 혼합하여 사용하려고 하면 Terraform의 설계 방식으로 인해 구성이 충돌하고 서로 덮어쓰는 오류가 발생 → 별도의 리소스 사용을 권장

[ 인라인 방식 ]

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
 }
}

 

[ 별도 모듈 방식 ]

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

resource "aws_security_group_rule" "allow_http_inbound" {
 type = "ingress"
 security_group_id = aws_security_group.alb.id
 from_port = local.http_port
 to_port = local.http_port
 protocol = local.tcp_protocol
 cidr_blocks = local.all_ips
}

resource "aws_security_group_rule" "allow_all_outbound" {
 type = "egress"
 security_group_id = aws_security_group.alb.id
 from_port = local.any_port
 to_port = local.any_port
 protocol = local.any_protocol
 cidr_blocks = local.all_ips
}

 

 

모듈버저닝

모듈에 태그를 붙여서 버전별로 관리 가능

동일한 모듈을 사용하여 여러 환경에 배포할때 적용된 환경에 따라 모듈 버전을 지정( 스테이징에서 한 버전(예: v0.0.2)을 사용하고 프로덕션에서 다른 버전 (예: v0.0.1)을 사용 )

git tag를 통해 업로드하는 모듈의 버전을 명시