개요
Terraform과 같은 Infrastructure as Code(IaC) 도구를 사용한다면, 대규모 인프라를 효츌적으로 관리할 수 있다. 그러나, 단일 Terraform 프로젝트로 대규모 인프라를 관리하게 된다면, 많은 리소스들이 하나의 코드 베이스에 포함되어 관리가 복잡해질 수 있다. 이로 인해 코드의 가독성이 떨어지고, 유지보수가 어려워질 뿐만 아니라 재사용성 또한 감소하게 된다.
Terraform Module을 사용하게 된다면 이러한 문제를 해결할 수 있게 된다. 이번 게시글에서는 모듈이란 무엇인지, 어떻게 Terraform Module을 사용할 수 있는지에 대해 알아보도록하자.
Terraform Module
Terraform Module은 코드 베이스를 논리적으로 분리하여 각각의 인프라 리소스를 독립적으로 관리할 수 있게 해주는 문법이다.복잡한 인프라를 여러 프로젝트로 나누어 관리할 수 있게 되어 중복되는 코드를 줄여 재사용성과 유지보수성을 크게 향상시킬 수 있는 장점을 가지고 있다.
Terraform Module이 필요한 이유
예를 들어, 초기 설정을 관리하는 프로젝트, EC2 인스턴스를 생성하는 프로젝트, CloudWatch 설정을 구성하는 프로젝트를 별도로 관리한다고 가정해보자. 여기서, 단일 Terraform 프로젝트로 이러한 컴포넌트들을 관리하게 된다면, 프로젝트의 복잡성이 증가하고, terraform destroy
명령어 실행 시, 원치 않는 리소스까지 삭제될 위험이 있다.
이러한 문제점을 개선하기 위해, 우리는 Terraform Module을 이용하여 원하는 특정 리소스를 모듈 단위로 관리할 수 있도록 프로젝트를 명확하게 분리하는 작업을 진행할 예정이다.
Terraform Module 시작하기
Terraform 모듈은 아래와 같은 문법으로 사용하게 된다.
module "vpc" {
source = "../modules/vpc"
aws_region = var.aws_region
vpc_cidr = var.vpc_cidr
vpc_name = var.vpc_name
public_subnets_cidr = var.public_subnets_cidr
private_subnets_cidr = var.private_subnets_cidr
availability_zones = var.availability_zones
}
이 예시에서, source
매개 변수는 모듈의 위치를 지정한다. 여기서는 상위 폴더에 있는 modules/vpc
디렉토리에 있는 VPC 모듈을 참조하게 된다. 이 모듈은 VPC, Subnet 리소스를 생성하고 관리하게 되는데, 해당 변수들은 모듈에 필요한 입력값을 제공하고, 모듈 내에서는 이 변수들을 사용하여 리소스를 구성하게 된다.
1️⃣ Provider 정의하기
Terraform Module을 구성할 때, 가장 먼저 수행해야할 작업은 모듈을 위한 별도의 디렉토리를 생성하는 것이다. 모듈과 일반 Terraform 프로젝트 사이에는 구조적인 차이가 존재하지 않기 때문에, 모든 리소스를 정의하기 위한 기본 설정 파일인 versions.tf
를 준비하는 것이 중요하다. 이 파일에서는 사용할 클라우드 서비스 프로바이더(CSP)와 Terraform의 버전, 그리고 필요한 프로바이더를 명시하게 된다.
provider "aws" {
# provider는 terraform이 사용할 클라우드 서비스를 명시해준다.
region = var.aws_region
}
terraform {
# terraform 버전을 명시해준다.
required_version = ">= 1.3"
# 필수로 사용할 provider를 명시해준다.
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0.0"
}
}
}
모듈을 정의할 때 사용할 프로바이더와 버전을 명시함으로써, 모듈을 사용하는 프로젝트에서 해당 모듈에 대한 제약사항을 미리 인지할 수 있다. 이는 모듈의 호환성을 보장할 수 있게 되며, 해당 모듈을 호출하는 개발자가 모듈을 더욱 쉽게 관리할 수 있게 해주도록 도와주게 된다.
2️⃣ Variables 정의하기
Terraform Module이 VPC와 Subnet을 생성할 수 있도록, 필요한 인프라 리소스 정보를 외부에서 주입받을 수 있도록 variable
을 정의해야한다. 이렇게 구현하게 된다면 해당 모듈이 외부에서 원하는 동작을 명확하게 수행할 수 있게 되어 모듈의 재사용성과 유연성이 높아지게 된다.
variable "aws_region" {
description = "The AWS region to deploy to"
type = string
}
variable "vpc_cidr" {
description = "The CIDR block for the VPC"
type = string
}
variable "vpc_name" {
description = "The name of the VPC"
type = string
}
variable "public_subnets_cidr" {
description = "The CIDR blocks for the public subnet"
type = list(string)
}
variable "private_subnets_cidr" {
description = "The CIDR blocks for the private subnet"
type = list(string)
}
variable "availability_zones" {
description = "The availability zones to deploy to"
type = list(string)
}
3️⃣ Resource 정의하기
모든 필요 정보를 variable
에서 주입받았다면, 이제 VPC와 관련된 리소스를 실제로 정의해야한다. 먼저 VPC를 정의하고, 이 VPC 내에서 Public, Private Subnet을 정의하도록 하자.
##################
### VPC
##################
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
tags = {
Name = var.vpc_name
}
}
##################
### Subnet
##################
locals {
public_subnet_length = length(var.public_subnets_cidr)
private_subnet_length = length(var.private_subnets_cidr)
availability_zone_length = length(var.availability_zones)
}
resource "aws_subnet" "public" {
count = local.public_subnet_length
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnets_cidr[count.index]
map_public_ip_on_launch = true # Public IP를 자동으로 할당
availability_zone = var.availability_zones[count.index % local.availability_zone_length]
tags = {
Name = "public-subnet-${count.index + 1}"
NetworkType = "Public"
}
}
resource "aws_subnet" "private" {
count = local.private_subnet_length
vpc_id = aws_vpc.this.id
cidr_block = var.private_subnets_cidr[count.index]
availability_zone = var.availability_zones[count.index % local.availability_zone_length]
tags = {
Name = "private-subnet-${count.index + 1}"
NetworkType = "Private"
}
}
##################
### Route Table
##################
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
tags = {
Name = "public-route-table"
NetworkType = "Public"
}
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
}
resource "aws_route" "public" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
resource "aws_route_table_association" "public" {
count = local.public_subnet_length
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.this.id
tags = {
Name = "private-route-table"
NetworkType = "Private"
}
}
resource "aws_route_table_association" "private" {
count = local.private_subnet_length
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
모듈 내에서 생성하는 네트워크 관련 리소스들, 즉 Subnet과 Route Table에는 NetworkType
태그를 추가하였다. 이렇게 태그를 추가하게 되었을 때, 해당 태그를 바탕으로 리소스가 어떤 역할을 담당하는지 알 수 있을 뿐더러 Subnet에 대한 정보를 명확하게 알지 못하더라도 Terraform Data에서 태그를 바탕으로 필터링할 수 있어 더욱 편리하게 리소스를 관리할 수 있게 된다.
4️⃣ Resource 정보 내보내기
모든 리소스를 정의하였다면, 해당 모듈에서 생성된 다양한 정보를 외부로 전달해줄 수 있어야한다. 우리가 일반적으로 사용하였던 output
을 통해 생성된 리소스 정보를 외부로 보낼 수 있게 되는데, 일반적인 Terraform 프로젝트에서는 output
이 Terminal에 출력되는 용도로 사용하였지만, Module에서는 output
이 외부로 전달해주는 역할을 담당하게 된다.
이번에는 생성된 VPC와 Subnet과 관련된 정보를 외부로 전달하도록 구현해보자.
output "vpc_id" {
value = aws_vpc.this.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
value = aws_subnet.private[*].id
}
5️⃣ Module 호출하기
이렇게 vpc 모듈 구현이 완료되었으므로, 이 모듈을 사용하여 실제 프로젝트를 구성해야 할 것이다. 모듈을 호출하기 위해 module
문법을 사용하고, terraform init
명령어로 모듈을 초기화한 후, terraform apply
명령어로 인프라를 배포해보도록하자.
module "vpc" {
source = "../modules/vpc-module"
aws_region = "ap-northeast-2"
vpc_cidr = "10.0.0.0/16"
vpc_name = "tf-module-vpc"
public_subnets_cidr = ["10.0.16.0/20", "10.0.32.0/20"]
private_subnets_cidr = ["10.0.48.0/20", "10.0.64.0/20"]
availability_zones = ["ap-northeast-2a", "ap-northeast-2c"]
}
terraform init
명령어는 Terraform 프로젝트를 초기화하는데, 더불어 필요한 모듈 및 프로바이더 플러그인을 다운로드하게된다. 이후 terraform apply
를 실행하면, 설정된 VPC와 서브넷이 생성된다. 이 과정으로 통해, Terraform 모듈을 이용하여 인프라를 구성할 수 있게 되며, 성공적으로 1개의 VPC와 각 2개의 Public, Private Subnet이 생성되는 것을 확인할 수 있게 된다.
📚 Directory Structure
.
├── module-example
│ ├── main.tf
│ ├── provider.tf
│ └── variables.tf
└── modules
└── vpc
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf
최종적으로, 위와 같은 폴더 구조와 함께 VPC, Subnet 리소스가 생성된다.
결론
Terraform Module을 사용함으로써, 복잡한 인프라 리소르를 여러 프로젝트로 분리하여 효율적으로 관리할 수 있게 되었다. 이번 예시에서는 VPC 관련 모듈에 초점을 맞추었지만, 실제로는 VPC, EC2, ELB, ECR, ECS, Code Pipeline, Route 5r3 등 다양한 리소스를 포함하는 모듈을 생성하여 전체 백엔드 아키텍처를 설계할 수 있을 것이다.
이와 같이 여러 모듈을 하나에 프로젝트에 구성하게 되었을 때 발생하는 문제점을 여러 모듈로 분리함으로써 가독성 또한 증가하게 될 것이며, Terraform Module 또한 반복문으로 생성 가능하기 때문에 여러 리소스를 더욱 가변적으로 관리할 수 있게 되어, Terraform 프로젝트가 더욱 퀄리티가 높아지게 될 것이다.
'Infrastructure > Terraform' 카테고리의 다른 글
지구를 Terraform으로 물들이다. (Terraform 입문하기) (1) | 2024.02.18 |
---|---|
VPC Peering, 너는 누구니? (feat. terraform) (1) | 2024.01.07 |
[Terraform] 이미 존재하는 AWS 리소스를 가져오기 (0) | 2023.06.03 |