현대의 클라우드 인프라 관리에서 Infrastructure as Code(IaC)는 더 이상 선택이 아닌 필수가 되었습니다.
특히 AWS 환경에서 Terraform을 활용한 인프라 자동화는 DevOps 엔지니어들에게 가장 중요한 스킬 중 하나로 자리잡았습니다.
이 글에서는 Terraform 모듈 작성의 실전 노하우부터 AWS 리소스 관리 베스트 프랙티스까지 상세히 다루어보겠습니다.
Terraform과 Infrastructure as Code의 중요성
Infrastructure as Code는 인프라 구성을 코드로 정의하고 관리하는 방법론입니다.
전통적인 수동 인프라 관리 방식과 달리, IaC는 버전 관리, 자동화, 재현성을 보장합니다.
Terraform은 HashiCorp에서 개발한 오픈소스 IaC 도구로, 선언적 언어인 HCL(HashiCorp Configuration Language)을 사용하여 인프라를 정의합니다.
AWS와 같은 클라우드 프로바이더뿐만 아니라 다양한 서비스와의 통합을 지원하는 것이 Terraform의 가장 큰 장점입니다.
주요 장점:
- 일관성: 동일한 환경을 여러 번 배포할 수 있습니다
- 추적성: 인프라 변경사항을 Git으로 관리 가능합니다
- 자동화: CI/CD 파이프라인과 통합하여 완전 자동화가 가능합니다
- 비용 최적화: 불필요한 리소스를 쉽게 식별하고 제거할 수 있습니다
Terraform 기본 개념과 핵심 컴포넌트
Terraform을 효과적으로 활용하기 위해서는 핵심 개념들을 정확히 이해해야 합니다.
Provider와 Resource
Provider는 Terraform이 특정 플랫폼과 통신하기 위한 플러그인입니다.
AWS Provider를 사용할 때는 다음과 같이 설정합니다:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "terraform-aws-infrastructure"
Environment = var.environment
ManagedBy = "terraform"
}
}
}
Resource는 실제 인프라 구성요소를 정의하는 블록입니다.
예를 들어 EC2 인스턴스를 생성하는 경우:
resource "aws_instance" "web_server" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.web_sg.id]
subnet_id = aws_subnet.public_subnet.id
user_data = file("${path.module}/user_data.sh")
tags = {
Name = "${var.project_name}-web-server"
}
}
State 파일 관리 전략
Terraform State는 인프라의 현재 상태를 추적하는 핵심 요소입니다.
프로덕션 환경에서는 반드시 원격 백엔드를 사용해야 합니다:
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "infrastructure/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
실전 Terraform 모듈 설계 원칙
효율적인 Terraform 모듈을 작성하기 위해서는 몇 가지 핵심 원칙을 따라야 합니다.
단일 책임 원칙
각 모듈은 하나의 명확한 목적을 가져야 합니다.
예를 들어, VPC 모듈은 네트워킹 관련 리소스만 관리하고, EC2 모듈은 컴퓨팅 리소스만 담당해야 합니다.
modules/
├── vpc/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── versions.tf
├── ec2/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── versions.tf
└── rds/
├── main.tf
├── variables.tf
├── outputs.tf
└── versions.tf
입력과 출력의 명확한 정의
모듈의 variables.tf 파일에서 입력값을 명확히 정의합니다:
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "VPC CIDR must be a valid IPv4 CIDR block."
}
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
default = ["ap-northeast-2a", "ap-northeast-2c"]
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
재사용 가능한 출력값 설계
outputs.tf에서 다른 모듈이나 루트 모듈에서 사용할 수 있는 값들을 노출합니다:
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "IDs of the public subnets"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs of the private subnets"
value = aws_subnet.private[*].id
}
output "vpc_cidr_block" {
description = "CIDR block of the VPC"
value = aws_vpc.main.cidr_block
}
AWS VPC 모듈 실전 구현
네트워킹의 기반이 되는 VPC 모듈을 실제로 구현해보겠습니다.
메인 VPC 리소스 정의
# modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.common_tags, {
Name = "${var.project_name}-vpc"
})
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(var.common_tags, {
Name = "${var.project_name}-igw"
})
}
서브넷 자동 생성 로직
# 퍼블릭 서브넷 생성
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.common_tags, {
Name = "${var.project_name}-public-subnet-${count.index + 1}"
Type = "public"
})
}
# 프라이빗 서브넷 생성
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = var.availability_zones[count.index]
tags = merge(var.common_tags, {
Name = "${var.project_name}-private-subnet-${count.index + 1}"
Type = "private"
})
}
NAT Gateway와 라우팅 테이블
# Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(var.availability_zones) : 0
domain = "vpc"
depends_on = [aws_internet_gateway.main]
tags = merge(var.common_tags, {
Name = "${var.project_name}-nat-eip-${count.index + 1}"
})
}
# NAT Gateway
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? length(var.availability_zones) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(var.common_tags, {
Name = "${var.project_name}-nat-gw-${count.index + 1}"
})
depends_on = [aws_internet_gateway.main]
}
# 퍼블릭 라우팅 테이블
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(var.common_tags, {
Name = "${var.project_name}-public-rt"
})
}
# 프라이빗 라우팅 테이블
resource "aws_route_table" "private" {
count = var.enable_nat_gateway ? length(var.availability_zones) : 1
vpc_id = aws_vpc.main.id
dynamic "route" {
for_each = var.enable_nat_gateway ? [1] : []
content {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
}
tags = merge(var.common_tags, {
Name = "${var.project_name}-private-rt-${count.index + 1}"
})
}
EC2 인스턴스 모듈과 Auto Scaling 구현
확장 가능한 EC2 인프라를 위한 모듈을 구현해보겠습니다.
Launch Template 정의
# modules/ec2/main.tf
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_launch_template" "main" {
name_prefix = "${var.project_name}-lt-"
image_id = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
vpc_security_group_ids = var.security_group_ids
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
project_name = var.project_name
environment = var.environment
}))
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = var.root_volume_size
volume_type = "gp3"
encrypted = true
}
}
iam_instance_profile {
name = aws_iam_instance_profile.ec2_profile.name
}
monitoring {
enabled = true
}
tag_specifications {
resource_type = "instance"
tags = merge(var.common_tags, {
Name = "${var.project_name}-instance"
})
}
}
Auto Scaling Group 설정
resource "aws_autoscaling_group" "main" {
name = "${var.project_name}-asg"
vpc_zone_identifier = var.subnet_ids
target_group_arns = var.target_group_arns
health_check_type = "ELB"
health_check_grace_period = 300
min_size = var.min_size
max_size = var.max_size
desired_capacity = var.desired_capacity
launch_template {
id = aws_launch_template.main.id
version = "$Latest"
}
# 인스턴스 새로고침 설정
instance_refresh {
strategy = "Rolling"
preferences {
min_healthy_percentage = 50
}
}
tag {
key = "Name"
value = "${var.project_name}-asg-instance"
propagate_at_launch = true
}
dynamic "tag" {
for_each = var.common_tags
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}
자동 스케일링 정책
# CPU 기반 스케일 아웃 정책
resource "aws_autoscaling_policy" "scale_out" {
name = "${var.project_name}-scale-out"
scaling_adjustment = 2
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = aws_autoscaling_group.main.name
}
# CPU 기반 스케일 인 정책
resource "aws_autoscaling_policy" "scale_in" {
name = "${var.project_name}-scale-in"
scaling_adjustment = -1
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = aws_autoscaling_group.main.name
}
# CloudWatch 알람
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
alarm_name = "${var.project_name}-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitors ec2 cpu utilization"
alarm_actions = [aws_autoscaling_policy.scale_out.arn]
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.main.name
}
}
RDS 데이터베이스 모듈 구현
안전하고 확장 가능한 데이터베이스 인프라를 구성해보겠습니다.
DB 서브넷 그룹과 파라미터 그룹
# modules/rds/main.tf
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-db-subnet-group"
subnet_ids = var.subnet_ids
tags = merge(var.common_tags, {
Name = "${var.project_name}-db-subnet-group"
})
}
resource "aws_db_parameter_group" "main" {
family = var.parameter_group_family
name = "${var.project_name}-db-params"
dynamic "parameter" {
for_each = var.db_parameters
content {
name = parameter.value.name
value = parameter.value.value
}
}
tags = var.common_tags
}
RDS 인스턴스 정의
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-${var.environment}-db"
# 엔진 설정
engine = var.engine
engine_version = var.engine_version
instance_class = var.instance_class
# 스토리지 설정
allocated_storage = var.allocated_storage
max_allocated_storage = var.max_allocated_storage
storage_type = "gp2"
storage_encrypted = true
# 데이터베이스 설정
db_name = var.database_name
username = var.username
password = var.password
# 네트워크 설정
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = var.security_group_ids
# 백업 설정
backup_retention_period = var.backup_retention_period
backup_window = var.backup_window
maintenance_window = var.maintenance_window
# 모니터링 설정
monitoring_interval = var.monitoring_interval
monitoring_role_arn = var.monitoring_interval > 0 ? aws_iam_role.rds_enhanced_monitoring[0].arn : null
# 기타 설정
parameter_group_name = aws_db_parameter_group.main.name
skip_final_snapshot = var.skip_final_snapshot
deletion_protection = var.deletion_protection
tags = merge(var.common_tags, {
Name = "${var.project_name}-${var.environment}-db"
})
}
읽기 전용 복제본 구성
resource "aws_db_instance" "read_replica" {
count = var.create_read_replica ? 1 : 0
identifier = "${var.project_name}-${var.environment}-db-replica"
replicate_source_db = aws_db_instance.main.id
instance_class = var.replica_instance_class
# 읽기 전용 복제본은 다른 AZ에 배치
availability_zone = var.replica_availability_zone
# 모니터링 설정
monitoring_interval = var.monitoring_interval
monitoring_role_arn = var.monitoring_interval > 0 ? aws_iam_role.rds_enhanced_monitoring[0].arn : null
tags = merge(var.common_tags, {
Name = "${var.project_name}-${var.environment}-db-replica"
Type = "read-replica"
})
}
로드 밸런서와 보안 그룹 설정
고가용성을 위한 Application Load Balancer와 보안 그룹을 구성합니다.
Application Load Balancer
# modules/alb/main.tf
resource "aws_lb" "main" {
name = "${var.project_name}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
enable_deletion_protection = var.enable_deletion_protection
access_logs {
bucket = var.access_logs_bucket
prefix = "alb"
enabled = var.enable_access_logs
}
tags = var.common_tags
}
resource "aws_lb_target_group" "main" {
name = "${var.project_name}-tg"
port = var.target_port
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = var.health_check_path
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 2
}
tags = var.common_tags
}
resource "aws_lb_listener" "main" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.main.arn
}
}
보안 그룹 규칙
# ALB 보안 그룹
resource "aws_security_group" "alb" {
name_prefix = "${var.project_name}-alb-"
vpc_id = var.vpc_id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
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"]
}
tags = merge(var.common_tags, {
Name = "${var.project_name}-alb-sg"
})
}
# EC2 인스턴스 보안 그룹
resource "aws_security_group" "ec2" {
name_prefix = "${var.project_name}-ec2-"
vpc_id = var.vpc_id
ingress {
description = "HTTP from ALB"
from_port = var.target_port
to_port = var.target_port
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.ssh_cidr_blocks
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(var.common_tags, {
Name = "${var.project_name}-ec2-sg"
})
}
모듈 통합과 환경별 구성 관리
개발한 모듈들을 실제 환경에서 통합하여 사용하는 방법을 알아보겠습니다.
환경별 디렉토리 구조
environments/
├── dev/
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── outputs.tf
├── staging/
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── outputs.tf
└── prod/
├── main.tf
├── variables.tf
├── terraform.tfvars
└── outputs.tf
개발 환경 구성
# environments/dev/main.tf
module "vpc" {
source = "../../modules/vpc"
project_name = var.project_name
environment = var.environment
vpc_cidr = var.vpc_cidr
availability_zones = var.availability_zones
enable_nat_gateway = false # 개발환경에서는 비용 절약
common_tags = local.common_tags
}
module "ec2" {
source = "../../modules/ec2"
project_name = var.project_name
environment = var.environment
instance_type = "t3.micro"
min_size = 1
max_size = 2
desired_capacity = 1
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.security_groups.ec2_security_group_id]
common_tags = local.common_tags
}
module "rds" {
source = "../../modules/rds"
project_name = var.project_name
environment = var.environment
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.security_groups.rds_security_group_id]
# 개발환경 설정
allocated_storage = 20
backup_retention_period = 1
deletion_protection = false
skip_final_snapshot = true
common_tags = local.common_tags
}
프로덕션 환경 구성
# environments/prod/main.tf
module "vpc" {
source = "../../modules/vpc"
project_name = var.project_name
environment = var.environment
vpc_cidr = var.vpc_cidr
availability_zones = var.availability_zones
enable_nat_gateway = true # 프로덕션에서는 NAT Gateway 사용
common_tags = local.common_tags
}
module "ec2" {
source = "../../modules/ec2"
project_name = var.project_name
environment = var.environment
instance_type = "t3.large"
min_size = 2
max_size = 10
desired_capacity = 3
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.security_groups.ec2_security_group_id]
target_group_arns = [module.alb.target_group_arn]
common_tags = local.common_tags
}
module "rds" {
source = "../../modules/rds"
project_name = var.project_name
environment = var.environment
engine = "mysql"
engine_version = "8.0"
instance_class = "db.r5.xlarge"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.security_groups.rds_security_group_id]
# 프로덕션 설정
allocated_storage = 100
max_allocated_storage = 1000
backup_retention_period = 7
deletion_protection = true
skip_final_snapshot = false
create_read_replica = true
monitoring_interval = 60
common_tags = local.common_tags
}
CI/CD 파이프라인과 Terraform 자동화
GitLab CI/CD를 활용한 Terraform 자동화 파이프라인을 구성해보겠습니다.
GitLab CI/CD 파이프라인
# .gitlab-ci.yml
stages:
- validate
- plan
- apply
- destroy
variables:
TF_ROOT: ${CI_PROJECT_DIR}/environments/${ENVIRONMENT}
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${ENVIRONMENT}
before_script:
- cd $TF_ROOT
- terraform --version
- terraform init -backend-config="address=${TF_ADDRESS}" -backend-config="lock_address=${TF_ADDRESS}/lock" -backend-config="unlock_address=${TF_ADDRESS}/lock" -backend-config="username=gitlab-ci-token" -backend-config="password=${CI_JOB_TOKEN}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5"
validate:
stage: validate
script:
- terraform validate
- terraform fmt -check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
plan:
stage: plan
script:
- terraform plan -out="planfile"
artifacts:
name: plan
paths:
- ${TF_ROOT}/planfile
expire_in: 1 week
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
apply:
stage: apply
script:
- terraform apply -input=false "planfile"
dependencies:
- plan
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
- if: '$ENVIRONMENT == "prod"'
when: manual
destroy:
stage: destroy
script:
- terraform destroy -auto-approve
rules:
- when: manual
allow_failure: false
Terraform 백엔드 설정 자동화
# backend.tf (각 환경별)
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "environments/${var.environment}/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-state-lock"
# 역할 기반 액세스
role_arn = "arn:aws:iam::123456789012:role/TerraformRole"
}
}
모니터링과 로깅 구현
CloudWatch와 AWS X-Ray를 활용한 종합적인 모니터링 시스템을 구축합니다.
CloudWatch 대시보드 자동 생성
# modules/monitoring/main.tf
resource "aws_cloudwatch_dashboard" "main" {
dashboard_name = "${var.project_name}-${var.environment}"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 6
properties = {
metrics = [
["AWS/ApplicationELB", "RequestCount", "LoadBalancer", aws_lb.main.arn_suffix],
[".", "TargetResponseTime", ".", "."],
[".", "HTTPCode_Target_2XX_Count", ".", "."],
[".", "HTTPCode_Target_4XX_Count", ".", "."],
[".", "HTTPCode_Target_5XX_Count", ".", "."]
]
view = "timeSeries"
stacked = false
region = var.aws_region
title = "ALB Metrics"
period = 300
}
},
{
type = "metric"
x = 0
y = 6
width = 12
height = 6
properties = {
metrics = [
["AWS/EC2", "CPUUtilization", "AutoScalingGroupName", var.asg_name],
[".", "NetworkIn", ".", "."],
[".", "NetworkOut", ".", "."]
]
view = "timeSeries"
stacked = false
region = var.aws_region
title = "EC2 Metrics"
period = 300
}
}
]
})
}
로그 그룹과 메트릭 필터
resource "aws_cloudwatch_log_group" "application" {
name = "/aws/ec2/${var.project_name}"
retention_in_days = var.log_retention_days
tags = var.common_tags
}
resource "aws_cloudwatch_metric_filter" "error_count" {
name = "${var.project_name}-error-count"
log_group_name = aws_cloudwatch_log_group.application.name
pattern = "[timestamp, request_id, ERROR, ...]"
metric_transformation {
name = "ErrorCount"
namespace = "${var.project_name}/Application"
value = "1"
}
}
resource "aws_cloudwatch_alarm" "high_error_rate" {
alarm_name = "${var.project_name}-high-error-rate"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "ErrorCount"
namespace = "${var.project_name}/Application"
period = "300"
statistic = "Sum"
threshold = "10"
alarm_description = "This metric monitors application error rate"
alarm_actions = [aws_sns_topic.alerts.arn]
tags = var.common_tags
}
비용 최적화와 리소스 관리
AWS 비용을 효율적으로 관리하기 위한 Terraform 구성을 살펴보겠습니다.
Spot 인스턴스 활용
# modules/ec2/spot_instances.tf
resource "aws_launch_template" "spot" {
count = var.enable_spot_instances ? 1 : 0
name_prefix = "${var.project_name}-spot-lt-"
image_id = data.aws_ami.amazon_linux.id
instance_type = var.spot_instance_type
vpc_security_group_ids = var.security_group_ids
instance_market_options {
market_type = "spot"
spot_options {
max_price = var.spot_max_price
}
}
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
project_name = var.project_name
environment = var.environment
}))
tag_specifications {
resource_type = "instance"
tags = merge(var.common_tags, {
Name = "${var.project_name}-spot-instance"
Type = "spot"
})
}
}
resource "aws_autoscaling_group" "spot" {
count = var.enable_spot_instances ? 1 : 0
name = "${var.project_name}-spot-asg"
vpc_zone_identifier = var.subnet_ids
target_group_arns = var.target_group_arns
min_size = var.spot_min_size
max_size = var.spot_max_size
desired_capacity = var.spot_desired_capacity
mixed_instances_policy {
launch_template {
launch_template_specification {
launch_template_id = aws_launch_template.spot[0].id
version = "$Latest"
}
override {
instance_type = "t3.medium"
}
override {
instance_type = "t3.large"
}
}
instances_distribution {
on_demand_base_capacity = 1
on_demand_percentage_above_base_capacity = 25
spot_allocation_strategy = "diversified"
}
}
}
자동 리소스 정리
# modules/cleanup/main.tf
resource "aws_lambda_function" "resource_cleanup" {
filename = "cleanup.zip"
function_name = "${var.project_name}-resource-cleanup"
role = aws_iam_role.lambda_role.arn
handler = "index.handler"
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
runtime = "python3.9"
timeout = 300
environment {
variables = {
PROJECT_NAME = var.project_name
ENVIRONMENT = var.environment
}
}
tags = var.common_tags
}
resource "aws_cloudwatch_event_rule" "cleanup_schedule" {
name = "${var.project_name}-cleanup-schedule"
description = "Trigger cleanup lambda"
schedule_expression = "cron(0 2 * * ? *)" # 매일 오전 2시
tags = var.common_tags
}
resource "aws_cloudwatch_event_target" "lambda_target" {
rule = aws_cloudwatch_event_rule.cleanup_schedule.name
target_id = "CleanupLambdaTarget"
arn = aws_lambda_function.resource_cleanup.arn
}
보안과 규정 준수
엔터프라이즈급 보안 요구사항을 만족하는 Terraform 구성을 구현합니다.
AWS Config 규칙 자동화
# modules/compliance/main.tf
resource "aws_config_configuration_recorder" "main" {
name = "${var.project_name}-config-recorder"
role_arn = aws_iam_role.config_role.arn
recording_group {
all_supported = true
include_global_resource_types = true
}
depends_on = [aws_config_delivery_channel.main]
}
resource "aws_config_delivery_channel" "main" {
name = "${var.project_name}-config-delivery-channel"
s3_bucket_name = aws_s3_bucket.config_bucket.bucket
snapshot_delivery_properties {
delivery_frequency = "Daily"
}
}
# 보안 그룹 규칙 검사
resource "aws_config_config_rule" "sg_ssh_restricted" {
name = "${var.project_name}-sg-ssh-restricted"
source {
owner = "AWS"
source_identifier = "INCOMING_SSH_DISABLED"
}
depends_on = [aws_config_configuration_recorder.main]
}
# 암호화 확인 규칙
resource "aws_config_config_rule" "encrypted_volumes" {
name = "${var.project_name}-encrypted-volumes"
source {
owner = "AWS"
source_identifier = "ENCRYPTED_VOLUMES"
}
depends_on = [aws_config_configuration_recorder.main]
}
Secrets Manager 통합
# modules/secrets/main.tf
resource "aws_secretsmanager_secret" "db_password" {
name = "${var.project_name}/${var.environment}/db-password"
tags = var.common_tags
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = jsonencode({
username = var.db_username
password = var.db_password
})
}
# RDS에서 Secrets Manager 사용
resource "aws_db_instance" "main" {
# ... 기타 설정 ...
manage_master_user_password = true
master_user_secret_kms_key_id = aws_kms_key.rds.arn
tags = var.common_tags
}
성능 최적화와 모범 사례
Terraform 코드의 성능과 유지보수성을 향상시키는 방법들을 알아보겠습니다.
Data Source 최적화
# 효율적인 데이터 소스 사용
data "aws_availability_zones" "available" {
state = "available"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "state"
values = ["available"]
}
}
# 캐시된 데이터 활용
locals {
availability_zones = data.aws_availability_zones.available.names
ami_id = data.aws_ami.amazon_linux.id
}
조건부 리소스 생성
# 환경에 따른 조건부 리소스
resource "aws_nat_gateway" "main" {
count = var.environment == "prod" ? length(local.availability_zones) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(var.common_tags, {
Name = "${var.project_name}-nat-gw-${count.index + 1}"
})
}
# 동적 블록 활용
resource "aws_security_group" "main" {
name_prefix = "${var.project_name}-"
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
tags = var.common_tags
}
모듈 버전 관리
# terraform.tf에서 모듈 버전 고정
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# 모듈 소스에서 버전 태그 사용
module "vpc" {
source = "git::https://github.com/company/terraform-aws-vpc.git?ref=v1.2.0"
# 모듈 변수들...
}
트러블슈팅과 디버깅
실제 운영 환경에서 발생할 수 있는 문제들과 해결 방법을 살펴보겠습니다.
상태 파일 복구
# 상태 파일 백업
terraform state pull > terraform.tfstate.backup
# 리소스 상태 확인
terraform state list
# 특정 리소스 상태 보기
terraform state show aws_instance.web_server
# 리소스 import
terraform import aws_instance.web_server i-1234567890abcdef0
# 상태에서 리소스 제거
terraform state rm aws_instance.old_server
디버깅 환경 변수
# Terraform 로그 레벨 설정
export TF_LOG=DEBUG
export TF_LOG_PATH=./terraform.log
# AWS API 디버깅
export TF_LOG_PROVIDER=DEBUG
# 병렬 실행 제한
terraform apply -parallelism=1
일반적인 오류 해결
# 순환 종속성 해결
resource "aws_security_group" "app" {
name_prefix = "${var.project_name}-app-"
vpc_id = var.vpc_id
# 별도 리소스로 규칙 정의
tags = var.common_tags
}
resource "aws_security_group_rule" "app_ingress" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
source_security_group_id = aws_security_group.alb.id
security_group_id = aws_security_group.app.id
}
결론
Terraform을 활용한 AWS 인프라 코드화는 현대 클라우드 환경에서 필수적인 기술입니다.
이 글에서 다룬 모듈 설계 원칙과 실전 구현 방법들을 통해 확장 가능하고 유지보수가 용이한 인프라를 구축할 수 있습니다.
핵심 포인트 요약
모듈 설계 시 중요한 점들:
- 단일 책임 원칙을 준수하여 각 모듈의 목적을 명확히 합니다
- 입력과 출력을 명확히 정의하여 재사용성을 높입니다
- 환경별 변수 관리를 통해 일관된 배포를 보장합니다
운영 관리 측면에서:
- 상태 파일을 원격 백엔드로 관리하여 팀 협업을 원활하게 합니다
- CI/CD 파이프라인 통합으로 자동화된 배포를 구현합니다
- 모니터링과 알림 설정으로 안정적인 운영을 보장합니다
다음 단계
이제 여러분의 프로젝트에 이러한 패턴들을 적용해보시기 바랍니다.
작은 프로젝트부터 시작하여 점진적으로 복잡한 인프라로 확장해나가면서 Terraform의 진정한 가치를 경험하실 수 있을 것입니다.
더 자세한 정보는 Terraform 공식 문서와 AWS Provider 문서를 참조하시기 바랍니다.
참고 자료:
'DevOps' 카테고리의 다른 글
LocalStack으로 AWS 로컬 개발환경 구축하기: 완벽한 클라우드 개발 가이드 (0) | 2025.06.23 |
---|---|
Podman vs Docker - 컨테이너 런타임 실전 비교: 2025년 완벽 가이드 (0) | 2025.06.20 |
Apache Kafka vs Apache Pulsar vs RabbitMQ - 메시지 큐 선택 가이드 2025 (0) | 2025.06.09 |
WebAssembly WASI로 서버사이드 개발하기: Docker 없이 컨테이너 대체하는 혁신적 접근법 (0) | 2025.06.05 |
Nginx 리버스 프록시 완벽 가이드 - 로드밸런싱부터 마이크로서비스 아키텍처까지 (0) | 2025.06.01 |