안녕하세요
이번 시간에는 코드 재사용성과 관리 용이성을 위한 테라폼 모듈화 실습을 진행하겠습니다.
1. 테라폼 소개
테라폼은 클라우드 인프라스트럭처를 코드로 정의하고 관리하는 강력한 도구입니다. 이 도구의 장점을 최대한 활용하기 위해 코드를 모듈화하는 것은 중요한 요소입니다. 이 블로그에서는 테라폼 모듈화의 개념과 모듈을 구성하기 위한 최적의 접근 방법을 자세히 알아보겠습니다.
2. 모듈(Module)이란?
여러 테라폼 리소스를 하나의 논리적 그룹으로 관리하기 위해 사용, 하나의 디렉토리 내의 .tf 혹은 .tf.json 파일로 구성된 콜렉션입니다.
1. 모듈의 종류
(1) 루트 모듈(Root Module)
테라폼 CLI가 plan / apply를 실제로 수행하게 되는 작업 디렉토리의 테라폼 코드 모음
- 루트 모듈은 인프라 코드의 진입점 역할을 합니다.
- 보통 프로젝트 또는 애플리케이션의 최상위 디렉토리에 위치합니다.
- 루트 모듈은 여러 차일드 모듈을 조합하여 인프라를 정의하고 관리합니다.
- Terraform 실행 시 루트 모듈이 시작점이 되어 하위 모듈들을 호출하고 관리합니다.
- 루트 모듈은 변수, 출력, 데이터 소스, 로컬 값 등을 정의할 수 있습니다.
- 주로 전체 인프라 스택의 구성을 담당하며, 하위 모듈들을 통해 인프라를 세부적으로 구성합니다.
(2) 차일드 모듈(Child Module)
다른 모듈의 테라폼 코드 내에서 호출(참조)하기 위한 목적으로 작성된 테라폼 코드 모음
- 차일드 모듈은 단일 기능이나 리소스 그룹에 대한 구성 요소를 정의합니다.
- 루트 모듈 내에서 호출되는 모듈입니다.
- 보통 디렉토리 단위로 구분되며, 해당 디렉토리에는 해당 모듈의 Terraform 파일과 리소스 정의가 포함됩니다.
- 차일드 모듈은 인프라의 특정 구성을 담당합니다. 예를 들어, VPC 모듈, 서브넷 모듈, RDS 모듈 등이 있을 수 있습니다.
- 차일드 모듈은 입력 변수를 받아서 리소스를 생성하고 구성합니다.
- 차일드 모듈은 독립적으로 테스트하고 재사용할 수 있습니다.
루트 모듈과 차일드 모듈은 서로 협력하여 인프라를 구성하는데 사용됩니다. 루트 모듈은 인프라의 전체 구성과 차일드 모듈 간의 상호작용을 관리하면서 차일드 모듈은 구체적인 리소스 또는 기능을 정의하고 재사용할 수 있도록 만듭니다. 이러한 모듈화 접근 방식은 코드의 가독성, 유지 보수성, 재사용성을 향상시키는 데 도움이 됩니다.
3. 모듈화의 필요성
테라폼 코드를 모듈화하는 이유는 여러 가지가 있습니다.
1. 코드 재사용성을 향상
모듈화(캡슐화)를 통해 비슷한 기능을 가진 리소스 그룹을 쉽게 생성하고 구성할 수 있습니다.
2. 코드의 가독성과 유지 보수성을 향상
모듈은 코드를 논리적으로 그룹화하고 추상화하여 코드베이스를 단순화시킵니다.
3. 중첩 루프 (Nested Loop)
리소스와 리소스가 1:N의 관계를 가지는 리소스가 있다면 리소스 선언으로 처리하기 쉽지 않지만 모듈을 사용하게 되면 복잡한 객체(리소스 집합)을 단순하게 관리할 수 있습니다.
4. 테라폼 레지스트리
HashiCorp에서 공식적으로 운영하는 테라폼 프로바이더 및 모듈 저장소.
모듈을 형상관리할 수 있으며 공개된 테라폼 모듈을 쉽게 찾아 활용 가능합니다.
# 외부에 공개된 테라폼 모듈 GitHub: terraform-aws-modules
https://github.com/terraform-aws-modules/
가장 많은 인기를 끌고 있는 AWS 테라폼 모듈을 관리하는 GitHub 조직이며 모듈 가이드 제공
5. 테라폼 구성 요소
provider | 테라폼으로 생성할 인프라의 종류를 의미 |
resource | 테라폼으로 실제로 생성할 인프라 자원을 의미 |
state | 테라폼을 통해 생성한 자원의 상태를 의미 |
output | 테라폼으로 만든 자원을 변수 형태로 state에 저장하는 것을 의미 |
module | 공통적으로 활용할 수 있는 코드를 문자 그대로 모듈 형태로 정의하는 것을 의미 |
remote | 다른 경로의 state를 참조하는 것을 의미하며 output 변수를 불러올 때 주로 사용 |
1. 모듈(Module) vs 스택(Stack)
테라폼을 통해 인프라를 구축하기로 마음 먹었다면 조직에서는 다음과 같은 고민을 하게 되고 의견 충돌이 있을 수 있습니다.
(1) 모듈(Module)
기존 리소스들을 유의미한 객체 단위로 다시 추상화한 리소스 모음이며 조직의 정보를 담고 있지 않도록 합니다.
얻떠한 조직에서든 용도에 무관하게 재사용하기 쉽도록 하는 것이 목적입니다.
(2) 스택(Stack)
조직의 기술 표준과 컨벤션이 적용되어 제한된 인터페이스만 제공하는 테라폼 모듈. 스택은 리소스와 모듈의 모음으로 구성되며, 사용 용도와 옵션을 제한하여 사용성을 향상시키고 기술 표준을 강제 적용하는 것이 목적입니다.
6. 테라폼 기본적인 Blocks 구성요소
테라폼에서 사용하는 블럭에 대해 알아보고 실습을 진행하겠습니다.
1. Blocks
테라폼 구성 파일(.tf파일)에서 정의되는 섹션들을 말합니다. 이러한 Blocks는 테라폼이 인프라스트럭처를 관리하는 데 필요한 정보를 제공합니다.
(1) Provider
Provider는 테라폼이 특정 인프라스트럭처 공급자와 상호작용하도록 설정하는 데 사용됩니다. 이 블럭은 특정 클라우드 플랫폼(AWS, Azure, GCP, NCP)을 지정하고, 해당 플랫폼과의 인증 및 연결을 위한 자격 증명 정보를 포함합니다.
(2) Resource
Resource는 테라폼으로 생성하고 관리하려는 인프라스트럭처 자원을 정의하는 데 사용됩니다. 이 블럭은 특정 인프라 자원을 생성하고 구성하는 데 필요한 매개변수와 속성을 제공합니다.
(3) Variable
Variable은 테라폼 모듈에 전달되는 매개변수(변수)를 정의하는 데 사용됩니다. 이 블럭은 모듈의 재사용성을 높이기 위해 매개변수화된 입력을 받아들이며, 이를 통해 모듈을 다양한 환경에 대응할 수 있습니다.
(4) Output
Output은 테라폼 모듈의 결과 값을 정의하는 데 사용됩니다. 이 블럭은 모듈의 실행 후 특정 정보를 사용자에게 반환하거나 다른 모듈에서 사용할 수 있도록 제공합니다.
(5) .tfvars
'.tfvars'는 테라폼에서 변수 값을 설정하기 위해 사용되는 파일입니다. 이 파일은 테라폼 모듈 또는 구성 파일에서 사용되는 변수에 대한 값을 정의하는 데 사용됩니다. '.tfvars' 파일은 일반적으로 텍스트 파일이며, 변수 이름과 해당 값을 설정하는 Key-Value 쌍 형식으로 작성됩니다.
가독성: '.tfvars' 파일은 변수 값들을 명확하고 읽기 쉬운 형식으로 정의할 수 있습니다. 이를 통해 변수 값들의 관리와 유지보수가 간편해집니다.
재사용성: '.tfvars' 파일을 사용하면 여러 모듈 또는 구성 파일에서 동일한 변수 값을 공유할 수 있습니다. 이를 통해 중복을 피하고 일관된 설정을 유지할 수 있습니다.
7. 테라폼을 이용한 기본적인 AWS 인프라를 로컬 모듈로 생성하기
이번 실습에서는 로컬 모듈을 사용하여 실습을 진행하겠습니다. 로컬 모듈를 사용하면 편리하지만 모듈의 버전을 선택할 수 없다는 단점이 있습니다. 대규모 환경이나 엔터프라이즈급에서는 버전 관리를 위해 레포지토리를 통해 원격 모듈을 사용해야 유연하고 확장 가능한 모듈 환경을 구성할 수 있습니다.
대부분의 조직에서는 운영 환경과 개발 환경을 구분해서 생성하지만 이번 실습에서는 구분하지 않고 진행하겠습니다.
참조 관계를 표현하면 이렇게 표현할 수 있을 것 같습니다.
1. provider 작성하기 (root-provider.tf)
provider "aws" {
region = "your-region"
access_key = "your-access-key"
secret_key = "your-secret-key"
}
자격 증명을 위해 본인의 프로필을 넣어줍니다.
2. resource 작성하기 (child-main.tf)
resource "aws_vpc" "vpc" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
tags = {Name = "${var.env}-vpc"}
}
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.vpc.id
service_name = "com.amazonaws.ap-northeast-2.s3"
tags = {Name = "${var.env}-endpoint-s3"}
}
resource "aws_subnet" "public-subnet" {
count = length(var.public-subnet)
vpc_id = aws_vpc.vpc.id
cidr_block = var.public-subnet[count.index]
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = {Name = "${var.env}-public-${count.index+1}"}
}
resource "aws_subnet" "private-subnet" {
count = length(var.public-subnet)
vpc_id = aws_vpc.vpc.id
cidr_block = var.private-subnet[count.index]
availability_zone = var.azs[count.index]
tags = {Name = "${var.env}-private-${count.index+1}"}
}
resource "aws_internet_gateway" "internet-gateway" {
vpc_id = aws_vpc.vpc.id
tags = {Name = "${var.env}-igw"}
}
resource "aws_nat_gateway" "nat-gateway" {
subnet_id = aws_subnet.public-subnet[0].id # 첫 번째 서브넷에만 생성
tags = {Name = "${var.env}-nat-gw"}
}
resource "aws_route_table" "public-route-table" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.internet-gateway.id
}
tags = {Name = "${var.env}-public-table"}
}
resource "aws_route_table" "private-route-table" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat-gateway.id
}
tags = {Name = "${var.env}-private-table"}
}
resource "aws_route_table_association" "public-route-table-association" {
count = length(aws_subnet.public-subnet)
subnet_id = aws_subnet.public-subnet[count.index].id
route_table_id = aws_route_table.public-route-table.id
}
resource "aws_route_table_association" "private-route-table-association" {
count = length(aws_subnet.private-subnet)
subnet_id = aws_subnet.private-subnet[count.index].id
route_table_id = aws_route_table.private-route-table.id
}
리소스를 생성할 때 변수를 사용함으로써 유연하게 생성할 수 있습니다.
3. variables 작성하기 (child-variables.tf)
variable "env" {}
variable "cidr_block" {}
variable "azs" {}
variable "public-subnet" {}
variable "private-subnet" {}
변수 선언은 아무것도 지정하지 않으면 default 값으로 type을 string으로 받습니다.
4. output 작성하기 (child-output.tf)
output "vpc_id" {
value = aws_vpc.vpc.id
}
output "public_subnet_id" {
value = aws_subnet.public-subnet[*].id
}
output "private_subnet_id" {
value = aws_subnet.private-subnet[*].id
}
output "endpoint_id" {
value = aws_vpc_endpoint.s3.id
}
위에서 얘기했던 1:N이라던지 N:N의 경우 리소스를 특정해서 의존성을 처리해주어야 할 수도 있습니다.
이번 실습에서는 없어도 상관 없습니다.
5. main 작성하기 (root-main.tf)
module "network" {
source = "../../modules/network"
env = var.env
cidr_block = var.cidr_block
azs = var.azs
public-subnet = var.public_subnet_cidr
private-subnet = var.private_subnet_cidr
}
module "s3_bucket" {
source = "../../modules/s3"
env = var.env
bucket_name = var.bucket_name
}
output "vpc_id" {
value = module.network.vpc_id
}
output "public_subnet_id" {
value = module.network.public_subnet_id
}
output "private_subnet_id" {
value = module.network.private_subnet_id
}
output "endpoint_id" {
value = module.network.endpoint_id
}
6. variable 작성하기 (root-variables.tf)
variable "env" {}
variable "azs" {}
variable "cidr_block" {}
variable "public_subnet_cidr" {}
variable "private_subnet_cidr" {}
variable "bucket_name" {}
7. '.tfvars' 작성하기 (root-terraform.tfvars)
env = "hyuk"
cidr_block = "10.185.0.0/16"
azs = ["ap-northeast-2a"]
public_subnet_cidr = ["10.185.0.0/24"]
private_subnet_cidr = ["10.185.100.0/24"]
bucket_name = "hyuk-s3-bucket"
'.tfvars'에서는 variables의 들어갈 값을 이렇게 정의할 수 있습니다.
만약 변수를 넘기지 않는다면 테라폼은 프롬프트에서 입력받을 수 있도록 해줍니다.
8. terraform init
terraform init
테라폼 프로젝트를 초기화하는 명령어입니다. 이 명령어를 실행하면 테라폼은 프로젝트에 필요한 구성 파일과 플러그인을 준비하고, 백엔드(Backend) 구성을 설정합니다. 일반적으로 테라폼 프로젝트를 처음 설정할 때 한 번만 실행하면 되나 팀원과 협업하거나 인프라스트럭처를 관리하는 환경이 변경될 때 마다 실행하여 테라폼의 설정을 최신 상태로 유지하는 것이 좋습니다.
9. terraform plan
terraform paln
테라폼 구성 파일을 인프라스트럭처 변경 사항을 예측하는 명령어입니다. 현재 구성 상태와 목표 상태 간의 차이점을 분석하고, 변경 사항에 대한 실행 계획을 생성합니다. terraform plan을 실행하여 변경 사항을 사전에 확인함으로써, 예상되는 인프라스트럭처의 상태를 미리 파악하고 이해할 수 있습니다. 이를 통해 실수나 예상치 못한 변동을 방지하고, 변경 사항에 대한 신뢰성과 안정성을 확보할 수 있습니다.
10. terraform apply
terraform apply
테라폼에서 인프라스터럭처를 프로비저닝하고 변경 사항을 적용하는 명령어입니다. 다음 명령어를 실행하면 테라폼은 구성 파일을 기반으로 목표 상태로 인프라스트럭처를 생성, 수정 또는 삭제합니다.
* apply 명령어는 실제로 인프라스트럭처를 변경하므로 주의해야 하며 실수로 인한 잘못된 변경으로 인해 리소스가 생성 또는 삭제될 수 있습니다. apply를 하기 전에 plan으로 변경 사항을 신중하게 검토하고 예상 결과를 미리 확인해야 합니다. 또한 진행 중인 작업을 중지할 수 없기 때문에 확실한 검토가 필요하고 프로비저닝하면 요금이 발생할 수 있습니다.
정상적으로 테라폼을 통해 리소스들이 생성된 모습입니다.
8. 모듈 구성하기
앞서 말씀드린 것처럼 모듈은 재사용 가능한 코드 단위로 작성되어야 합니다. 일반적으로 모듈은 특정 기능, 리소스 유형 또는 환경에 따라 구성 요소를 그룹화합니다. 모듈을 구성하는 단계는 다음과 같습니다.
1. 필요한 리소스 정의하기
모듈에 필요한 리소스와 데이터 소스를 정의합니다. 리소스의 구성과 속성을 설정합니다.
2. 입력 변수 정의하기
모듈에서 사용되는 입력 변수를 정의합니다. 필요한 입력 값을 모듈 사용자에게 전달받을 수 있도록 합니다.
3. 출력 값 정의하기
모듈의 출력 값을 정의합니다. 다른 모듈이나 외부에서 해당 값을 사용할 수 있도록 합니다.
루트 모듈에서 차일드 모듈을 호출하여 사용할 수 있습니다. 루트 모듈에서 차일드 모듈의 변수 값을 설정하고, 차일드 모듈에서 정의된 리소스를 생성하고 구성합니다. 이를 통해 모듈화된 코드를 효율적으로 관리하고 활용할 수 있습니다.
▶ 참고 문서
Documentation | Terraform | HashiCorp Developer
Docs overview | hashicorp/aws | Terraform Registry
[Korean] 확장 가능한 테라폼 코드 관리 - YouTube
▶ 요약
테라폼 모듈화는 코드의 재사용성과 유지 보수성을 향상시키는 핵심적인 기법입니다. 이를 통해 조직은 효과적으로 인프라스트럭처 환경을 관리하고, 코드 기반의 인프라스트럭처 관리를 통해 조직과 팀 간의 협업 능력을 쉽게 향상시킬 수 있습니다. 이 블로그는 모듈화의 필요성을 이해하고, 효율적인 모듈 디렉토리 구조와 구성 방법을 상세히 설명했습니다. 이제 모듈화를 통해 테라폼 코드를 보다 효율적으로 관리하고 최대한 활용할 수 있을 것입니다.
'IaC > Terraform' 카테고리의 다른 글
[Terraform] Windows/Linux에서 테라폼 설치하기 실습 (0) | 2023.04.07 |
---|---|
[Terraform] 코드형 인프라, 장점, 프로비저닝 도구, 선언적 언어, 테라폼 알아보기 (0) | 2022.11.13 |
클라우드, 개발, 자격증, 취업 정보 등 IT 정보 공간
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!