DevOps

Terraform으로 AWS 인프라 코드화하기: 실전 모듈 작성법

devcomet 2025. 6. 12. 10:12
728x90
반응형

terraform-aws-infrastructure-thumbnail
terraform-aws-infrastructure-thumbnail

 

현대의 클라우드 인프라 관리에서 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 워크플로우와 AWS 통합 아키텍처 다이어그램
Terraform 워크플로우와 AWS 통합 아키텍처 다이어그램


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
}

Terraform 모듈 구조와 데이터 플로우 다이어그램
Terraform 모듈 구조와 데이터 플로우 다이어그램


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

 

AWS Auto Scaling과 CloudWatch 통합 모니터링 대시보드
AWS Auto Scaling과 CloudWatch 통합 모니터링 대시보드


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 문서를 참조하시기 바랍니다.


참고 자료:

728x90
반응형