Skip to content

필사 모드: Terraform IaC 완전 가이드 2025: AWS/GCP 인프라를 코드로 관리하는 모든 것

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

인프라를 수동으로 관리하는 시대는 끝났습니다. 클라우드 리소스가 수십, 수백 개로 늘어나면 콘솔 클릭으로는 재현성, 감사 추적, 협업이 불가능합니다. Terraform은 HashiCorp가 만든 오픈소스 IaC(Infrastructure as Code) 도구로, AWS, GCP, Azure를 비롯한 3,000개 이상의 프로바이더를 지원합니다.

이 가이드에서는 Terraform의 기초부터 실전까지 모든 것을 다룹니다. HCL 문법, State 관리, 모듈 설계, AWS/GCP 실전 인프라, Terragrunt, CI/CD 자동화, 보안 스캐닝, 비용 추정까지 — 프로덕션 레벨의 IaC를 구축하는 데 필요한 모든 지식을 체계적으로 정리합니다.

1. 왜 IaC가 중요한가

1.1 수동 관리 vs IaC

| 관점 | 수동 (콘솔) | IaC (Terraform) |

|------|------------|-----------------|

| 재현성 | 불가능 | 코드로 100% 재현 |

| 감사 추적 | 제한적 | Git 히스토리 |

| 협업 | 불가능 | PR 리뷰 |

| 환경 복제 | 수 시간 | 수 분 |

| 롤백 | 수동 복구 | `terraform apply` |

| 문서화 | 별도 관리 | 코드가 문서 |

1.2 Terraform vs 다른 IaC 도구

| 도구 | 언어 | 클라우드 | 상태 관리 | 학습 곡선 |

|------|------|---------|----------|----------|

| Terraform | HCL | 멀티 클라우드 | State 파일 | 중간 |

| CloudFormation | JSON/YAML | AWS 전용 | 스택 | 낮음 |

| Pulumi | Python/TS/Go | 멀티 클라우드 | State 파일 | 높음 |

| AWS CDK | Python/TS/Go | AWS 전용 | CloudFormation | 높음 |

| OpenTofu | HCL | 멀티 클라우드 | State 파일 | 중간 |

Terraform을 선택하는 이유:

- 멀티 클라우드 지원 (AWS + GCP + Azure)

- 가장 큰 커뮤니티와 모듈 레지스트리

- 선언적 구문으로 의도 명확

- Plan -> Apply 워크플로로 안전한 변경

2. Terraform 기초

2.1 HCL 기본 문법

프로바이더 설정

terraform {

required_version = ">= 1.7.0"

required_providers {

aws = {

source = "hashicorp/aws"

version = "~> 5.40"

}

}

backend "s3" {

bucket = "my-terraform-state"

key = "prod/terraform.tfstate"

region = "ap-northeast-2"

dynamodb_table = "terraform-locks"

encrypt = true

}

}

provider "aws" {

region = var.aws_region

default_tags {

tags = {

Environment = var.environment

ManagedBy = "terraform"

Project = var.project_name

}

}

}

2.2 변수 (Variables)

variables.tf

variable "aws_region" {

description = "AWS region to deploy resources"

type = string

default = "ap-northeast-2"

}

variable "environment" {

description = "Environment name (dev, staging, prod)"

type = string

validation {

condition = contains(["dev", "staging", "prod"], var.environment)

error_message = "Environment must be dev, staging, or prod."

}

}

variable "instance_config" {

description = "EC2 instance configuration"

type = object({

instance_type = string

volume_size = number

enable_monitoring = bool

})

default = {

instance_type = "t3.medium"

volume_size = 50

enable_monitoring = true

}

}

variable "allowed_cidrs" {

description = "List of CIDR blocks allowed for ingress"

type = list(string)

default = ["10.0.0.0/8"]

}

variable "tags" {

description = "Additional tags"

type = map(string)

default = {}

}

2.3 리소스 (Resources)

main.tf

resource "aws_vpc" "main" {

cidr_block = "10.0.0.0/16"

enable_dns_support = true

enable_dns_hostnames = true

tags = {

Name = "main-vpc-${var.environment}"

}

}

resource "aws_subnet" "public" {

count = length(var.availability_zones)

vpc_id = aws_vpc.main.id

cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)

availability_zone = var.availability_zones[count.index]

map_public_ip_on_launch = true

tags = {

Name = "public-subnet-${count.index + 1}"

Type = "public"

}

}

2.4 데이터 소스 (Data Sources)

최신 Amazon Linux 2023 AMI 조회

data "aws_ami" "amazon_linux" {

most_recent = true

owners = ["amazon"]

filter {

name = "name"

values = ["al2023-ami-*-x86_64"]

}

filter {

name = "virtualization-type"

values = ["hvm"]

}

}

현재 AWS 계정 정보

data "aws_caller_identity" "current" {}

현재 리전

data "aws_region" "current" {}

2.5 출력값 (Outputs)

outputs.tf

output "vpc_id" {

description = "VPC ID"

value = aws_vpc.main.id

}

output "public_subnet_ids" {

description = "Public subnet IDs"

value = aws_subnet.public[*].id

}

output "account_id" {

description = "AWS Account ID"

value = data.aws_caller_identity.current.account_id

sensitive = false

}

output "db_password" {

description = "Database password"

value = random_password.db.result

sensitive = true

}

2.6 Locals

locals {

name_prefix = "${var.project_name}-${var.environment}"

common_tags = merge(var.tags, {

Environment = var.environment

ManagedBy = "terraform"

Project = var.project_name

})

env_config = {

dev = {

instance_type = "t3.small"

min_size = 1

max_size = 2

}

staging = {

instance_type = "t3.medium"

min_size = 2

max_size = 4

}

prod = {

instance_type = "t3.large"

min_size = 3

max_size = 10

}

}

config = local.env_config[var.environment]

}

3. State 관리

3.1 State란?

Terraform State는 실제 인프라와 코드 간의 매핑 정보를 저장합니다. `terraform.tfstate` 파일에 모든 리소스의 현재 상태가 JSON으로 기록됩니다.

State가 없으면:

- Terraform이 어떤 리소스를 관리하는지 알 수 없음

- Plan에서 모든 리소스를 새로 생성하려고 함

- 리소스 간 의존성 추적 불가

3.2 원격 State (S3 + DynamoDB)

backend.tf

terraform {

backend "s3" {

bucket = "mycompany-terraform-state"

key = "environments/prod/terraform.tfstate"

region = "ap-northeast-2"

dynamodb_table = "terraform-locks"

encrypt = true

}

}

S3 버킷과 DynamoDB 테이블 생성 (부트스트랩):

bootstrap/main.tf

resource "aws_s3_bucket" "terraform_state" {

bucket = "mycompany-terraform-state"

lifecycle {

prevent_destroy = true

}

}

resource "aws_s3_bucket_versioning" "terraform_state" {

bucket = aws_s3_bucket.terraform_state.id

versioning_configuration {

status = "Enabled"

}

}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {

bucket = aws_s3_bucket.terraform_state.id

rule {

apply_server_side_encryption_by_default {

sse_algorithm = "aws:kms"

}

}

}

resource "aws_dynamodb_table" "terraform_locks" {

name = "terraform-locks"

billing_mode = "PAY_PER_REQUEST"

hash_key = "LockID"

attribute {

name = "LockID"

type = "S"

}

}

3.3 State 명령어

State 목록 확인

terraform state list

특정 리소스 상세 정보

terraform state show aws_vpc.main

리소스 이름 변경 (코드 리팩터링 시)

terraform state mv aws_instance.old aws_instance.new

State에서 리소스 제거 (실제 인프라는 유지)

terraform state rm aws_instance.legacy

State 가져오기

terraform state pull > backup.tfstate

4. 모듈 (Modules)

4.1 모듈 생성

modules/

vpc/

main.tf

variables.tf

outputs.tf

ec2/

main.tf

variables.tf

outputs.tf

rds/

main.tf

variables.tf

outputs.tf

modules/vpc/main.tf

resource "aws_vpc" "this" {

cidr_block = var.cidr_block

enable_dns_support = true

enable_dns_hostnames = true

tags = merge(var.tags, {

Name = "${var.name_prefix}-vpc"

})

}

resource "aws_subnet" "public" {

count = length(var.public_subnet_cidrs)

vpc_id = aws_vpc.this.id

cidr_block = var.public_subnet_cidrs[count.index]

availability_zone = var.availability_zones[count.index]

map_public_ip_on_launch = true

tags = merge(var.tags, {

Name = "${var.name_prefix}-public-${count.index + 1}"

Type = "public"

})

}

resource "aws_subnet" "private" {

count = length(var.private_subnet_cidrs)

vpc_id = aws_vpc.this.id

cidr_block = var.private_subnet_cidrs[count.index]

availability_zone = var.availability_zones[count.index]

tags = merge(var.tags, {

Name = "${var.name_prefix}-private-${count.index + 1}"

Type = "private"

})

}

resource "aws_internet_gateway" "this" {

vpc_id = aws_vpc.this.id

tags = merge(var.tags, {

Name = "${var.name_prefix}-igw"

})

}

resource "aws_nat_gateway" "this" {

count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0

allocation_id = aws_eip.nat[count.index].id

subnet_id = aws_subnet.public[count.index].id

tags = merge(var.tags, {

Name = "${var.name_prefix}-nat-${count.index + 1}"

})

}

resource "aws_eip" "nat" {

count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0

domain = "vpc"

tags = merge(var.tags, {

Name = "${var.name_prefix}-eip-${count.index + 1}"

})

}

modules/vpc/variables.tf

variable "name_prefix" {

description = "Name prefix for resources"

type = string

}

variable "cidr_block" {

description = "VPC CIDR block"

type = string

default = "10.0.0.0/16"

}

variable "public_subnet_cidrs" {

description = "Public subnet CIDR blocks"

type = list(string)

}

variable "private_subnet_cidrs" {

description = "Private subnet CIDR blocks"

type = list(string)

}

variable "availability_zones" {

description = "Availability zones"

type = list(string)

}

variable "enable_nat_gateway" {

description = "Enable NAT Gateway"

type = bool

default = true

}

variable "tags" {

description = "Additional tags"

type = map(string)

default = {}

}

modules/vpc/outputs.tf

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

}

4.2 모듈 사용

environments/prod/main.tf

module "vpc" {

source = "../../modules/vpc"

name_prefix = "prod"

cidr_block = "10.0.0.0/16"

public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]

private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]

availability_zones = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]

enable_nat_gateway = true

tags = local.common_tags

}

module "rds" {

source = "../../modules/rds"

name_prefix = "prod"

vpc_id = module.vpc.vpc_id

subnet_ids = module.vpc.private_subnet_ids

instance_class = "db.r6g.large"

engine_version = "15.5"

allocated_storage = 100

tags = local.common_tags

}

4.3 Terraform Registry 모듈 사용

공식 AWS VPC 모듈 사용

module "vpc" {

source = "terraform-aws-modules/vpc/aws"

version = "5.5.0"

name = "my-vpc"

cidr = "10.0.0.0/16"

azs = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]

private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]

public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

enable_nat_gateway = true

single_nat_gateway = false

tags = local.common_tags

}

5. AWS 실전 인프라

5.1 VPC + EC2 + ALB + RDS

EC2 인스턴스 + Auto Scaling Group

resource "aws_launch_template" "app" {

name_prefix = "${local.name_prefix}-app-"

image_id = data.aws_ami.amazon_linux.id

instance_type = local.config.instance_type

vpc_security_group_ids = [aws_security_group.app.id]

user_data = base64encode(templatefile("userdata.sh", {

environment = var.environment

db_host = aws_db_instance.main.endpoint

}))

iam_instance_profile {

name = aws_iam_instance_profile.app.name

}

block_device_mappings {

device_name = "/dev/xvda"

ebs {

volume_size = var.instance_config.volume_size

volume_type = "gp3"

encrypted = true

}

}

tag_specifications {

resource_type = "instance"

tags = merge(local.common_tags, {

Name = "${local.name_prefix}-app"

})

}

}

resource "aws_autoscaling_group" "app" {

name = "${local.name_prefix}-app-asg"

desired_capacity = local.config.min_size

min_size = local.config.min_size

max_size = local.config.max_size

vpc_zone_identifier = module.vpc.private_subnet_ids

target_group_arns = [aws_lb_target_group.app.arn]

launch_template {

id = aws_launch_template.app.id

version = "$Latest"

}

health_check_type = "ELB"

health_check_grace_period = 300

tag {

key = "Name"

value = "${local.name_prefix}-app"

propagate_at_launch = true

}

}

5.2 ALB 설정

resource "aws_lb" "app" {

name = "${local.name_prefix}-alb"

internal = false

load_balancer_type = "application"

security_groups = [aws_security_group.alb.id]

subnets = module.vpc.public_subnet_ids

enable_deletion_protection = var.environment == "prod"

tags = local.common_tags

}

resource "aws_lb_target_group" "app" {

name = "${local.name_prefix}-app-tg"

port = 8080

protocol = "HTTP"

vpc_id = module.vpc.vpc_id

health_check {

path = "/actuator/health"

port = "traffic-port"

healthy_threshold = 3

unhealthy_threshold = 3

timeout = 5

interval = 30

matcher = "200"

}

deregistration_delay = 30

tags = local.common_tags

}

resource "aws_lb_listener" "https" {

load_balancer_arn = aws_lb.app.arn

port = 443

protocol = "HTTPS"

ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"

certificate_arn = aws_acm_certificate.main.arn

default_action {

type = "forward"

target_group_arn = aws_lb_target_group.app.arn

}

}

5.3 RDS (PostgreSQL)

resource "aws_db_instance" "main" {

identifier = "${local.name_prefix}-db"

engine = "postgres"

engine_version = "15.5"

instance_class = "db.r6g.large"

allocated_storage = 100

max_allocated_storage = 500

storage_type = "gp3"

storage_encrypted = true

db_name = "myapp"

username = "admin"

password = random_password.db.result

multi_az = var.environment == "prod"

db_subnet_group_name = aws_db_subnet_group.main.name

vpc_security_group_ids = [aws_security_group.db.id]

backup_retention_period = 7

backup_window = "03:00-04:00"

maintenance_window = "Mon:04:00-Mon:05:00"

skip_final_snapshot = var.environment != "prod"

final_snapshot_identifier = "${local.name_prefix}-db-final"

performance_insights_enabled = true

tags = local.common_tags

}

resource "random_password" "db" {

length = 32

special = false

}

5.4 S3 + IAM

resource "aws_s3_bucket" "assets" {

bucket = "${local.name_prefix}-assets"

tags = local.common_tags

}

resource "aws_s3_bucket_versioning" "assets" {

bucket = aws_s3_bucket.assets.id

versioning_configuration {

status = "Enabled"

}

}

resource "aws_s3_bucket_public_access_block" "assets" {

bucket = aws_s3_bucket.assets.id

block_public_acls = true

block_public_policy = true

ignore_public_acls = true

restrict_public_buckets = true

}

IAM Role for EC2

resource "aws_iam_role" "app" {

name = "${local.name_prefix}-app-role"

assume_role_policy = jsonencode({

Version = "2012-10-17"

Statement = [

{

Action = "sts:AssumeRole"

Effect = "Allow"

Principal = {

Service = "ec2.amazonaws.com"

}

}

]

})

tags = local.common_tags

}

resource "aws_iam_role_policy" "app_s3" {

name = "${local.name_prefix}-app-s3-policy"

role = aws_iam_role.app.id

policy = jsonencode({

Version = "2012-10-17"

Statement = [

{

Effect = "Allow"

Action = [

"s3:GetObject",

"s3:PutObject",

"s3:ListBucket"

]

Resource = [

aws_s3_bucket.assets.arn,

"${aws_s3_bucket.assets.arn}/*"

]

}

]

})

}

6. GCP 실전 인프라

6.1 VPC + GKE

provider "google" {

project = var.gcp_project_id

region = var.gcp_region

}

resource "google_compute_network" "main" {

name = "${local.name_prefix}-vpc"

auto_create_subnetworks = false

}

resource "google_compute_subnetwork" "main" {

name = "${local.name_prefix}-subnet"

ip_cidr_range = "10.0.0.0/20"

region = var.gcp_region

network = google_compute_network.main.id

secondary_ip_range {

range_name = "pods"

ip_cidr_range = "10.1.0.0/16"

}

secondary_ip_range {

range_name = "services"

ip_cidr_range = "10.2.0.0/20"

}

}

resource "google_container_cluster" "primary" {

name = "${local.name_prefix}-gke"

location = var.gcp_region

remove_default_node_pool = true

initial_node_count = 1

network = google_compute_network.main.name

subnetwork = google_compute_subnetwork.main.name

ip_allocation_policy {

cluster_secondary_range_name = "pods"

services_secondary_range_name = "services"

}

workload_identity_config {

workload_pool = "${var.gcp_project_id}.svc.id.goog"

}

release_channel {

channel = "REGULAR"

}

}

resource "google_container_node_pool" "primary" {

name = "${local.name_prefix}-node-pool"

location = var.gcp_region

cluster = google_container_cluster.primary.name

node_count = 3

node_config {

machine_type = "e2-standard-4"

disk_size_gb = 100

disk_type = "pd-ssd"

oauth_scopes = [

"https://www.googleapis.com/auth/cloud-platform"

]

labels = {

environment = var.environment

}

}

autoscaling {

min_node_count = 3

max_node_count = 10

}

}

6.2 Cloud SQL + Cloud Run

resource "google_sql_database_instance" "main" {

name = "${local.name_prefix}-db"

database_version = "POSTGRES_15"

region = var.gcp_region

settings {

tier = "db-custom-4-16384"

ip_configuration {

ipv4_enabled = false

private_network = google_compute_network.main.id

}

backup_configuration {

enabled = true

point_in_time_recovery_enabled = true

start_time = "03:00"

}

availability_type = var.environment == "prod" ? "REGIONAL" : "ZONAL"

}

deletion_protection = var.environment == "prod"

}

resource "google_cloud_run_v2_service" "app" {

name = "${local.name_prefix}-app"

location = var.gcp_region

template {

containers {

image = "gcr.io/${var.gcp_project_id}/myapp:latest"

ports {

container_port = 8080

}

resources {

limits = {

cpu = "2"

memory = "1Gi"

}

}

env {

name = "SPRING_PROFILES_ACTIVE"

value = var.environment

}

startup_probe {

http_get {

path = "/actuator/health"

}

initial_delay_seconds = 10

period_seconds = 3

}

}

scaling {

min_instance_count = var.environment == "prod" ? 2 : 0

max_instance_count = 10

}

}

}

7. 고급 패턴

7.1 Workspaces

워크스페이스 생성

terraform workspace new dev

terraform workspace new staging

terraform workspace new prod

워크스페이스 전환

terraform workspace select prod

현재 워크스페이스 확인

terraform workspace show

워크스페이스 기반 환경 분리

locals {

env_config = {

dev = {

instance_type = "t3.small"

db_instance = "db.t3.medium"

}

staging = {

instance_type = "t3.medium"

db_instance = "db.r6g.large"

}

prod = {

instance_type = "t3.large"

db_instance = "db.r6g.xlarge"

}

}

config = local.env_config[terraform.workspace]

}

7.2 Dynamic Blocks

resource "aws_security_group" "app" {

name = "${local.name_prefix}-app-sg"

description = "Application security group"

vpc_id = module.vpc.vpc_id

dynamic "ingress" {

for_each = var.ingress_rules

content {

from_port = ingress.value.port

to_port = ingress.value.port

protocol = "tcp"

cidr_blocks = ingress.value.cidr_blocks

description = ingress.value.description

}

}

egress {

from_port = 0

to_port = 0

protocol = "-1"

cidr_blocks = ["0.0.0.0/0"]

}

tags = local.common_tags

}

variable "ingress_rules" {

type = list(object({

port = number

cidr_blocks = list(string)

description = string

}))

default = [

{

port = 8080

cidr_blocks = ["10.0.0.0/8"]

description = "Application port"

},

{

port = 443

cidr_blocks = ["0.0.0.0/0"]

description = "HTTPS"

}

]

}

7.3 for_each vs count

count -- 인덱스 기반 (삭제 시 재생성 문제)

resource "aws_subnet" "example" {

count = 3

cidr_block = "10.0.${count.index}.0/24"

vpc_id = aws_vpc.main.id

}

for_each -- 키 기반 (안전한 삭제/추가)

resource "aws_subnet" "example" {

for_each = {

"public-a" = { cidr = "10.0.1.0/24", az = "ap-northeast-2a" }

"public-b" = { cidr = "10.0.2.0/24", az = "ap-northeast-2b" }

"private-a" = { cidr = "10.0.11.0/24", az = "ap-northeast-2a" }

}

cidr_block = each.value.cidr

availability_zone = each.value.az

vpc_id = aws_vpc.main.id

tags = {

Name = "${local.name_prefix}-${each.key}"

}

}

7.4 Lifecycle 규칙

resource "aws_instance" "app" {

ami = data.aws_ami.amazon_linux.id

instance_type = local.config.instance_type

lifecycle {

create_before_destroy = true

prevent_destroy = false

ignore_changes = [ami]

}

}

moved 블록 (리소스 이름 변경 시)

moved {

from = aws_instance.old_name

to = aws_instance.new_name

}

8. Import & 마이그레이션

8.1 terraform import 명령어

기존 리소스 임포트

terraform import aws_vpc.main vpc-0123456789abcdef0

terraform import aws_instance.web i-0123456789abcdef0

terraform import 'aws_subnet.public[0]' subnet-0123456789abcdef0

8.2 Import Block (Terraform 1.5+)

import.tf -- 선언적 임포트

to = aws_vpc.main

id = "vpc-0123456789abcdef0"

}

to = aws_instance.web

id = "i-0123456789abcdef0"

}

코드 자동 생성

terraform plan -generate-config-out=generated.tf

생성된 코드 검토 후 적용

terraform apply

8.3 기존 인프라 마이그레이션 전략

단계별 접근:

1. **인벤토리 작성** — 현재 리소스 목록화

2. **코드 작성** — 리소스별 HCL 코드 작성

3. **Import** — 기존 리소스를 State에 가져오기

4. **Plan 검증** — `terraform plan`으로 변경사항 없음 확인

5. **점진적 관리** — 새 리소스부터 Terraform으로 생성

9. Terragrunt

9.1 DRY 환경 관리

infrastructure/

terragrunt.hcl

environments/

dev/

terragrunt.hcl

vpc/

terragrunt.hcl

rds/

terragrunt.hcl

prod/

terragrunt.hcl

vpc/

terragrunt.hcl

rds/

terragrunt.hcl

modules/

vpc/

rds/

infrastructure/terragrunt.hcl (루트)

remote_state {

backend = "s3"

generate = {

path = "backend.tf"

if_exists = "overwrite"

}

config = {

bucket = "mycompany-terraform-state"

key = "${path_relative_to_include()}/terraform.tfstate"

region = "ap-northeast-2"

dynamodb_table = "terraform-locks"

encrypt = true

}

}

generate "provider" {

path = "provider.tf"

if_exists = "overwrite"

contents = <<EOF

provider "aws" {

region = "ap-northeast-2"

}

EOF

}

infrastructure/environments/prod/vpc/terragrunt.hcl

include "root" {

path = find_in_parent_folders()

}

terraform {

source = "../../../modules/vpc"

}

inputs = {

name_prefix = "prod"

cidr_block = "10.0.0.0/16"

public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]

private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]

availability_zones = ["ap-northeast-2a", "ap-northeast-2b"]

enable_nat_gateway = true

}

9.2 의존성 관리

infrastructure/environments/prod/rds/terragrunt.hcl

include "root" {

path = find_in_parent_folders()

}

terraform {

source = "../../../modules/rds"

}

dependency "vpc" {

config_path = "../vpc"

}

inputs = {

name_prefix = "prod"

vpc_id = dependency.vpc.outputs.vpc_id

subnet_ids = dependency.vpc.outputs.private_subnet_ids

}

10. CI/CD 자동화

10.1 GitHub Actions + Terraform

.github/workflows/terraform.yml

name: Terraform CI/CD

on:

pull_request:

paths:

- 'infrastructure/**'

push:

branches:

- main

paths:

- 'infrastructure/**'

permissions:

id-token: write

contents: read

pull-requests: write

jobs:

plan:

if: github.event_name == 'pull_request'

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- uses: aws-actions/configure-aws-credentials@v4

with:

role-to-assume: arn:aws:iam::123456789012:role/terraform-ci

aws-region: ap-northeast-2

- uses: hashicorp/setup-terraform@v3

with:

terraform_version: 1.7.0

- name: Terraform Init

working-directory: infrastructure/environments/prod

run: terraform init

- name: Terraform Plan

working-directory: infrastructure/environments/prod

run: terraform plan -no-color -out=tfplan

apply:

if: github.ref == 'refs/heads/main' && github.event_name == 'push'

runs-on: ubuntu-latest

environment: production

steps:

- uses: actions/checkout@v4

- uses: aws-actions/configure-aws-credentials@v4

with:

role-to-assume: arn:aws:iam::123456789012:role/terraform-ci

aws-region: ap-northeast-2

- uses: hashicorp/setup-terraform@v3

with:

terraform_version: 1.7.0

- name: Terraform Init

working-directory: infrastructure/environments/prod

run: terraform init

- name: Terraform Apply

working-directory: infrastructure/environments/prod

run: terraform apply -auto-approve

10.2 Atlantis

atlantis.yaml

version: 3

projects:

- name: prod-infra

dir: infrastructure/environments/prod

workspace: default

terraform_version: v1.7.0

autoplan:

when_modified:

- "*.tf"

- "../../modules/**/*.tf"

enabled: true

apply_requirements:

- approved

- mergeable

11. 비용 관리 (Infracost)

11.1 Infracost 통합

Infracost 실행

infracost breakdown --path=infrastructure/environments/prod

JSON 출력

infracost breakdown \

--path=infrastructure/environments/prod \

--format=json \

--out-file=infracost.json

PR에 비용 코멘트

infracost comment github \

--path=infracost.json \

--repo=myorg/myrepo \

--pull-request=42 \

--github-token=GH_TOKEN_VALUE

11.2 태깅 전략

비용 추적을 위한 필수 태그

locals {

required_tags = {

Environment = var.environment

Project = var.project_name

Team = var.team_name

CostCenter = var.cost_center

ManagedBy = "terraform"

}

}

12. 보안 (tfsec, Checkov)

12.1 tfsec 정적 분석

tfsec 실행

tfsec ./infrastructure/

특정 규칙 무시

tfsec ./infrastructure/ --exclude-downloaded-modules

tfsec 규칙 무시 (정당한 사유가 있을 때만)

resource "aws_s3_bucket" "logs" {

bucket = "my-access-logs"

#tfsec:ignore:aws-s3-enable-versioning -- 로그 버킷은 버저닝 불필요

}

12.2 Checkov

Checkov 실행

checkov -d ./infrastructure/ --framework terraform

CI/CD에서 실행

checkov -d ./infrastructure/ \

--output junitxml \

--output-file-path ./results

12.3 Secrets 관리 (Vault)

HashiCorp Vault에서 시크릿 조회

data "vault_generic_secret" "db" {

path = "secret/data/myapp/database"

}

resource "aws_db_instance" "main" {

username = data.vault_generic_secret.db.data["username"]

password = data.vault_generic_secret.db.data["password"]

}

13. Terraform 자격증 가이드

HashiCorp Certified: Terraform Associate (003)

시험 개요:

- 시간: 60분

- 문항: 57문항 (객관식 + 빈칸 채우기)

- 합격 기준: 70%

- 유효 기간: 2년

- 비용: 약 70 USD

핵심 도메인:

1. **IaC 개념 이해** (15%) — IaC 장점, Terraform 워크플로

2. **Terraform 기초** (20%) — 프로바이더, 리소스, 변수, 출력

3. **State 관리** (15%) — 원격 State, 잠금, 명령어

4. **모듈** (15%) — 모듈 생성, 사용, 레지스트리

5. **Terraform CLI** (15%) — init, plan, apply, destroy, import

6. **워크플로** (10%) — 팀 워크플로, Terraform Cloud

7. **구현 및 유지보수** (10%) — 디버깅, 업그레이드

학습 리소스:

- HashiCorp Learn 공식 튜토리얼

- Terraform Up and Running (O'Reilly)

- 공식 시험 리뷰 가이드

14. 인터뷰 질문 15선

기본 (1-5)

**Q1. Terraform의 핵심 워크플로를 설명하세요.**

Write(코드 작성) -> Plan(변경사항 미리보기) -> Apply(적용)의 3단계입니다. `terraform plan`은 실제 변경 없이 무엇이 바뀔지 보여주고, `terraform apply`가 실제 인프라를 변경합니다.

**Q2. Terraform State의 역할과 중요성은?**

State는 실제 인프라와 코드 간의 매핑을 저장합니다. State가 없으면 Terraform은 어떤 리소스를 관리하는지 알 수 없습니다. 원격 백엔드(S3+DynamoDB)를 사용하여 팀 공유와 잠금을 구현해야 합니다.

**Q3. Provider와 Resource의 차이는?**

Provider는 클라우드/서비스와의 API 연결을 담당하는 플러그인(AWS, GCP 등)이고, Resource는 Provider를 통해 관리하는 실제 인프라 객체(VPC, EC2 등)입니다.

**Q4. terraform plan과 terraform apply의 차이는?**

`plan`은 변경사항을 미리보기로 보여주며 실제 인프라를 변경하지 않습니다. `apply`는 실제로 인프라를 변경합니다. `apply`는 내부적으로 `plan`을 먼저 실행합니다.

**Q5. 변수의 우선순위(Variable Precedence)를 설명하세요.**

환경변수 TF_VAR_xxx, terraform.tfvars, terraform.tfvars.json, auto.tfvars, -var 또는 -var-file 명령줄 옵션 순으로 우선순위가 높아집니다.

심화 (6-10)

**Q6. count와 for_each의 차이와 언제 어떤 것을 사용해야 하나요?**

`count`는 인덱스 기반으로 중간 요소 삭제 시 뒤의 모든 리소스가 재생성됩니다. `for_each`는 키 기반으로 특정 요소만 안전하게 삭제/추가 가능합니다. 일반적으로 `for_each`를 권장합니다.

**Q7. State Locking이 필요한 이유는?**

두 명이 동시에 `terraform apply`를 실행하면 State가 충돌하여 인프라가 손상될 수 있습니다. DynamoDB 잠금 테이블로 동시 실행을 방지합니다.

**Q8. 모듈(Module)의 장점과 설계 원칙은?**

재사용성, DRY 원칙, 캡슐화가 장점입니다. 단일 책임 원칙으로 설계하고, 변수로 설정을 외부화하며, 출력값으로 다른 모듈과 연결합니다.

**Q9. terraform import와 Import Block의 차이는?**

`terraform import`는 명령줄에서 한 번에 하나의 리소스만 가져오며, 코드를 자동 생성하지 않습니다. Import Block(1.5+)은 선언적으로 여러 리소스를 한 번에 가져올 수 있고, -generate-config-out 옵션으로 코드 자동 생성이 가능합니다.

**Q10. Terraform에서 시크릿을 안전하게 관리하는 방법은?**

State 파일에 민감 정보가 저장되므로 원격 백엔드 암호화 필수, sensitive = true 변수 사용, HashiCorp Vault나 AWS Secrets Manager 연동, .tfvars 파일은 .gitignore에 추가, 환경변수로 주입합니다.

실전 (11-15)

**Q11. Drift Detection이란 무엇이고 어떻게 처리하나요?**

Drift는 실제 인프라가 Terraform 코드와 다른 상태입니다. `terraform plan`으로 감지하고, `terraform apply`로 코드 상태로 되돌리거나 코드를 수정합니다. 정기적인 plan 실행으로 드리프트를 모니터링해야 합니다.

**Q12. Terragrunt를 사용하는 이유는?**

Terraform의 DRY 원칙 부족을 보완합니다. 환경별 설정을 상속 구조로 관리하고, 원격 State 설정을 자동화하며, 모듈 간 의존성을 관리합니다.

**Q13. Terraform CI/CD 파이프라인의 모범 사례는?**

PR에서 plan 실행 후 결과를 코멘트로 게시, main 브랜치 merge 시 apply 실행, OIDC로 AWS 인증(시크릿 키 불필요), terraform fmt + tfsec으로 코드 품질 검증, 환경별 승인 프로세스(GitHub Environments) 적용합니다.

**Q14. Terraform과 CloudFormation을 함께 사용해야 하는 경우는?**

AWS 전용이고 CloudFormation StackSets, Service Catalog 등 AWS 네이티브 기능이 필요한 경우 CFN을, 멀티 클라우드나 SaaS 리소스 관리가 필요한 경우 Terraform을 사용합니다.

**Q15. Infracost로 비용 관리하는 방법은?**

PR에서 인프라 변경의 비용 영향을 자동으로 추정하여 코멘트로 보여줍니다. 월간 비용 증가/감소를 시각화하고, 팀이 비용을 인지한 상태에서 인프라 변경을 승인합니다.

15. 퀴즈

DynamoDB입니다. S3는 State 파일 저장, DynamoDB는 잠금(Lock) 테이블로 동시 실행을 방지합니다. dynamodb_table 속성으로 설정합니다.

count는 인덱스 기반이라 중간 요소를 제거하면 그 뒤의 모든 리소스가 재생성됩니다. for_each는 키 기반이라 특정 요소만 안전하게 추가/삭제할 수 있습니다.

Import Block으로 선언한 리소스의 HCL 코드를 자동 생성합니다. 기존 인프라를 Terraform으로 마이그레이션할 때 코드를 처음부터 작성할 필요 없이 자동 생성된 코드를 기반으로 정리하면 됩니다.

모듈 간의 실행 순서와 데이터 전달을 관리합니다. 예를 들어 RDS 모듈이 VPC 모듈의 output(subnet_ids)에 의존하면, Terragrunt가 VPC를 먼저 적용하고 그 output을 RDS 모듈에 전달합니다.

tfsec는 Terraform 전용 보안 스캐너로 HCL 코드의 보안 취약점을 검사합니다. Checkov는 Terraform뿐 아니라 CloudFormation, Kubernetes, Docker 등 다양한 IaC 도구를 지원하는 범용 정책 검사 도구입니다.

참고 자료

1. [Terraform 공식 문서](https://developer.hashicorp.com/terraform/docs)

2. [Terraform Registry](https://registry.terraform.io/)

3. [Terraform AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)

4. [Terraform GCP Provider](https://registry.terraform.io/providers/hashicorp/google/latest/docs)

5. [Terragrunt 공식 문서](https://terragrunt.gruntwork.io/docs/)

6. [Infracost 문서](https://www.infracost.io/docs/)

7. [tfsec 가이드](https://aquasecurity.github.io/tfsec/)

8. [Checkov 문서](https://www.checkov.io/1.Welcome/Quick%20Start.html)

9. [HashiCorp Learn Terraform](https://developer.hashicorp.com/terraform/tutorials)

10. [Terraform Up and Running (O'Reilly)](https://www.terraformupandrunning.com/)

11. [AWS VPC Terraform Module](https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest)

12. [Terraform Best Practices](https://www.terraform-best-practices.com/)

13. [Atlantis 문서](https://www.runatlantis.io/docs/)

14. [OpenTofu](https://opentofu.org/docs/)

15. [HashiCorp Vault Terraform Provider](https://registry.terraform.io/providers/hashicorp/vault/latest/docs)

16. [Terraform Associate 시험 가이드](https://developer.hashicorp.com/certifications/infrastructure-automation)

17. [AWS IAM Terraform 모범 사례](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)

18. [GKE Terraform 모듈](https://registry.terraform.io/modules/terraform-google-modules/kubernetes-engine/google/latest)

현재 단락 (1/974)

인프라를 수동으로 관리하는 시대는 끝났습니다. 클라우드 리소스가 수십, 수백 개로 늘어나면 콘솔 클릭으로는 재현성, 감사 추적, 협업이 불가능합니다. Terraform은 Hashi...

작성 글자: 0원문 글자: 24,690작성 단락: 0/974