Terraform 입문클릭으로 만들던 인프라를 코드로 관리하기

 

 

☁️ Infrastructure as Code

AWS 콘솔 없이도 동일한 인프라를 반복 재현하는 법 
개념부터 실전 패턴, 실수 방지까지

📅 2025년 5월 🏷️ Terraform · IaC · DevOps 

 

왜 Terraform을 써야 하는가

어느 스타트업의 월요일 아침, 팀장님이 이렇게 메시지를 보냈다고 상상해 보세요.

💬 실제 상황
"스테이징 환경 그대로 프로덕션에 한 벌 더 만들어주세요. 이번 주 안에 가능하시죠?"
— 스테이징은 6개월 전 누군가가 AWS 콘솔에서 클릭클릭 만든 환경. 문서는 없음.

VPC, Subnet, Security Group, RDS, EC2, ALB, IAM Role … 하나씩 콘솔 열어서 비교하고 다시 클릭으로 만드는 것, 가능하긴 합니다. 그런데 그 과정에서 누락이나 실수가 생기면? 다음에 또 같은 요청이 들어오면?

콘솔 관리 vs IaC — 무엇이 다른가

구분 콘솔 관리 IaC (Terraform)
핵심 질문 지금 무엇을 만들 것인가? 어떤 상태를 유지할 것인가?
변경 이력 ❌ 남지 않음 ✅ Git에 기록됨
환경 복제 수작업 반복 동일 코드로 반복 가능
코드 리뷰 ❌ 불가 ✅ PR 리뷰 가능
실수 회복 어려움 코드 되돌리기로 복구
환경 동기화 dev/prod 설정이 서서히 어긋남 같은 코드로 동일 보장
✅ 한 줄 정의
Terraform은 HashiCorp가 만든 IaC 도구로, 인프라를 HCL(HashiCorp Configuration Language) 코드로 선언하고, 현재 상태와 비교한 뒤, 변경을 안전하게 반영하는 도구입니다.

좋은 IaC의 5가지 조건

조건 왜 필요한가 어떻게
선언적 "어떤 상태가 되어야 하는지"만 표현 resource 블록 사용
재현 가능 같은 코드 = 같은 결과 Terraform 자체 동작
검토 가능 변경 전 영향 파악 terraform plan
협업 가능 팀원과 동시 작업 Remote Backend + Locking
안전 사고 시 복구 가능 Git 이력 + 백업

핵심 개념 4가지

Provider
Terraform과 외부 서비스(AWS, GCP 등)를 연결하는 플러그인
Resource
만들거나 관리할 실제 대상 (EC2, S3, RDS 등)
State
코드와 실제 인프라를 매핑한 기록 파일 (.tfstate)
Plan / Apply
변경 계획 미리보기(plan) → 실제 반영(apply)

Provider — Terraform과 AWS를 연결하는 다리

Terraform 자체는 AWS EC2가 뭔지 모릅니다. Provider가 각 클라우드 서비스의 리소스 타입과 API 호출 방법을 알고 있습니다.

# Provider 설정 — 항상 버전을 고정하세요
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # 5.x 범위 내 최신 버전 사용
    }
  }
}

provider "aws" {
  region = "ap-northeast-2"  # 서울 리전
}main.tf
⚠️ 주의
version = "~> 5.0"5.x 버전만 허용한다는 뜻입니다. 버전을 고정하지 않으면 팀원마다 다른 버전으로 실행해서 예기치 않은 동작이 발생할 수 있습니다.

Resource — 관리할 대상 선언

# 형식: resource "리소스_타입" "로컬_이름" { ... }
resource "aws_s3_bucket" "logs" {
  bucket = "my-app-logs-2026"

  tags = {
    Environment = "prod"
    ManagedBy   = "terraform"
  }
}main.tf

aws_s3_bucket은 리소스 타입, logs는 이 코드 내에서 참조할 때 쓰는 이름입니다. 실제 AWS에는 bucket 값인 my-app-logs-2026로 만들어집니다.

State — Terraform의 기억

State는 Terraform이 "내가 어떤 리소스를 만들었는가"를 기록한 파일입니다. terraform.tfstate라는 JSON 파일로 저장됩니다.

// terraform.tfstate (자동 생성 — 직접 수정 금지!)
{
  "version": 4,
  "resources": [
    {
      "type": "aws_s3_bucket",
      "name": "logs",
      "instances": [
        {
          "attributes": {
            "bucket": "my-app-logs-2026",
            "arn":    "arn:aws:s3:::my-app-logs-2026"
          }
        }
      ]
    }
  ]
}terraform.tfstate
🚨 절대 금지
terraform.tfstate절대 Git에 커밋하지 마세요. DB 비밀번호, IAM Secret Key 등 민감 정보가 평문(plain text)으로 담겨 있습니다.

Plan / Apply — 변경의 양면

plan은 변경 예고편, apply는 실제 반영입니다. 실무에서 plan 결과를 정확히 읽는 것이 apply보다 더 중요합니다.

+ aws_instance.web       # ✅ 신규 생성 → 비용 발생
~ aws_s3_bucket.logs    # 🔄 속성 수정 (in-place)
- aws_eip.legacy        # ❌ 삭제 → 데이터 손실 위험!
-/+ aws_db_instance.main # 💥 삭제 후 재생성 (다운타임!)plan 기호 해석
💥 Force Replacement (-/+)
-/+는 기존 리소스를 삭제 후 새로 만든다는 뜻입니다. RDS의 db_name 변경이 대표적인 예시 — DB가 삭제되면 데이터도 함께 사라집니다. 반드시 스냅샷 백업 후 진행하세요.

기본 명령어와 동작 흐름

명령어 역할 언제 실행?
terraform init 작업 디렉터리 초기화, Provider 다운로드 프로젝트 시작 시, Provider 변경 시
terraform fmt 코드 포맷 자동 정리 커밋 전
terraform validate 문법 검증 커밋 전, CI에서 자동 실행
terraform plan 변경 계획 미리 보기 apply 전 항상
terraform apply 실제 인프라 반영 plan 검토 후
terraform destroy 관리 중인 리소스 전체 삭제 테스트 환경 정리 시
terraform state list state에 등록된 리소스 목록 state 확인 시
terraform state mv 리소스 이름 변경 시 state 동기화 리소스 식별자 변경 시
terraform import 기존 리소스를 state로 가져옴 콘솔 리소스를 IaC로 전환 시
terraform output output 값 조회 apply 후 값 확인 시

전체 동작 흐름

.tf 파일 작성
HCL로 원하는 인프라 상태를 선언
terraform init
Provider 다운로드, .terraform 폴더 생성
terraform fmt & validate
코드 스타일 정리 + 문법 검증
terraform plan
현재 state와 코드를 비교 → 변경 예고
🔍 plan 결과 검토 
삭제 항목, Force Replacement 여부 확인
terraform apply
실제 클라우드 API 호출 → 인프라 변경 + state 업데이트
다시 plan → "No changes"
코드와 실제가 일치함을 확인

실습: Random Provider로 흐름 체험하기

AWS 계정 없이도 Terraform 명령어 흐름을 연습할 수 있습니다.

terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

resource "random_id" "server" {
  byte_length = 8
}

output "server_id" {
  value = random_id.server.hex
}main.tf
# 순서대로 실행
$ mkdir terraform-practice && cd terraform-practice
$ terraform init
$ terraform plan
$ terraform apply
$ cat terraform.tfstate   # state 파일 확인
$ terraform destroyterminal

State와 Remote Backend

State가 꼬이면 벌어지는 일

상황 결과
state 파일 분실 Terraform이 기존 리소스를 "없는 것"으로 인식 → 중복 생성
state와 실제 인프라 불일치 plan 결과가 예상과 전혀 다르게 나옴
두 명이 동시에 apply state 파일 손상 (race condition)
state를 Git에 커밋 DB 비밀번호 등이 GitHub에 평문 노출

Remote Backend — 팀 협업의 필수 조건

혼자 쓸 때는 로컬 state 파일로도 되지만, 팀이 함께 작업한다면 Remote Backend는 필수입니다. 가장 흔한 조합은 AWS S3 + DynamoDB입니다.

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true                       # S3 저장 시 암호화
    dynamodb_table = "terraform-state-lock"     # 동시 실행 방지 (Locking)
  }
}backend.tf

State Locking — 동시 실행 방지

[10:00] 개발자 A: terraform apply 시작
        → DynamoDB에 lock 획득

[10:01] 개발자 B: terraform apply 시도
        → Error: Error acquiring the state lock
        → 대기 또는 중단

[10:05] 개발자 A: apply 완료 → lock 해제
[10:06] 개발자 B: 재시도 가능State Locking 흐름

Drift — 코드와 실제 인프라가 어긋날 때

⚠️ Drift란?
Terraform으로 만든 리소스를 콘솔에서 직접 수정하면 코드와 실제가 달라집니다. 다음 plan 때 의도치 않은 변경이 나타납니다.
# 시나리오
1. Terraform으로 EC2 생성 (코드: t3.medium)
2. 콘솔에서 t3.large로 수동 변경  ← Drift 발생!
3. 다음 terraform plan 결과:
   ~ aws_instance.web
     ~ instance_type: "t3.large" → "t3.medium"
4. 모르고 apply 하면 t3.medium으로 되돌려짐Drift 예시

원칙: Terraform으로 만든 리소스는 콘솔에서 수정하지 않는다.
긴급 상황으로 콘솔 수정이 불가피했다면, 반드시 코드에도 동일하게 반영

추가: terraform import

기존에 콘솔로 만든 리소스를 Terraform 관리로 가져오고 싶을 때 씁니다.

# 콘솔에서 만든 S3 버킷을 state로 가져오기
$ terraform import aws_s3_bucket.logs my-app-logs-2026

# 가져온 뒤에는 코드를 state에 맞게 작성해야 합니다
$ terraform plan  # "No changes"가 나올 때까지 코드를 맞춰가세요terminal
ℹ️ Terraform 1.5+
Terraform 1.5부터 import 블록을 코드 안에 선언할 수 있습니다. 1.6부터는 terraform plan이 자동으로 import 코드를 생성해 주는 기능이 추가됐습니다.

변수 · 출력 · 모듈

variable — 환경별 값 분리

# variables.tf
variable "environment" {
  type        = string
  description = "배포 환경 (dev, staging, prod)"
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment는 dev, staging, prod 중 하나여야 합니다."
  }
}

variable "db_instance_class" {
  type    = string
  default = "db.t3.micro"
}

variable "db_password" {
  type      = string
  sensitive = true   # plan/apply 출력에서 마스킹
}variables.tf
💡 validation 블록
Terraform 0.13부터 validation 블록으로 변수 값을 검증할 수 있습니다. 잘못된 환경 이름이 들어오면 apply 전에 에러를 발생시켜 사고를 예방합니다.

tfvars — 환경별 값 파일

# dev.tfvars
environment       = "dev"
db_instance_class = "db.t3.micro"

# prod.tfvars
environment       = "prod"
db_instance_class = "db.r6g.large"*.tfvars
$ terraform apply -var-file="dev.tfvars"
$ terraform apply -var-file="prod.tfvars"terminal
🚨 .gitignore 필수 등록
*.tfvars 파일에 비밀번호 같은 값이 들어갈 수 있으므로, 반드시 .gitignore에 추가하세요.

output — 생성된 리소스 정보 노출

output "db_endpoint" {
  value       = aws_db_instance.main.endpoint
  description = "애플리케이션에서 사용할 DB 엔드포인트"
}

output "db_password" {
  value     = aws_db_instance.main.password
  sensitive = true   # 화면 출력 시 "sensitive" 표시
}outputs.tf

sensitive = true로 설정해도 state 파일에는 평문으로 저장됩니다. state 파일 자체를 암호화(S3 encrypt)하고 접근 권한을 제한하는 것이 중요합니다.

module — 재사용 단위

project/
├── main.tf
├── variables.tf
└── modules/
    └── web-server/
        ├── main.tf        # 실제 리소스 정의
        ├── variables.tf   # 입력 변수
        └── outputs.tf     # 출력 값디렉터리 구조
# project/main.tf
module "web_dev" {
  source        = "./modules/web-server"
  environment   = "dev"
  instance_type = "t3.micro"
}

module "web_prod" {
  source        = "./modules/web-server"
  environment   = "prod"
  instance_type = "t3.large"
}main.tf
⚠️ 모듈화 함정
처음부터 모든 것을 모듈로 쪼개면 안 됩니다. 같은 패턴이 2~3번 반복될 때 모듈로 분리하는 것이 안전합니다. 너무 이른 모듈화는 변수가 수십 개로 늘어나고 디버깅이 어려워집니다.

locals — 중간 계산 값 정의

반복 사용되는 표현식을 locals로 정의하면 코드가 훨씬 깔끔해집니다.

locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
    Project     = "my-app"
  }
  name_prefix = "${var.environment}-myapp"
}

resource "aws_instance" "web" {
  ami           = "ami-0abc12345"
  instance_type = "t3.medium"
  tags          = local.common_tags  # 재사용
}locals 활용

for_each / count — 여러 리소스를 한 번에

# count: 숫자로 반복
resource "aws_instance" "web" {
  count         = 3
  ami           = "ami-0abc12345"
  instance_type = "t3.micro"
  tags = { Name = "web-${count.index}" }
}

# for_each: 맵/셋으로 반복 (더 권장됨)
resource "aws_s3_bucket" "buckets" {
  for_each = toset(["logs", "assets", "backups"])
  bucket   = "my-app-${each.key}"
}for_each vs count
💡 for_each를 권장하는 이유
count는 중간 항목을 삭제하면 인덱스가 바뀌어 이후 리소스가 줄줄이 재생성됩니다. for_each는 키 기반이라 다른 항목에 영향을 주지 않습니다.

절대로 코드에 넣으면 안 되는 것들

🚨 코드에 절대 넣지 말 것
AWS Access Key / Secret Key · RDS 비밀번호 · API Token · Private Key (.pem, .key) · JWT Secret · OAuth Client Secret · DB 접속 문자열 (계정 포함) · 개인정보

가장 흔한 실수 — Provider에 키를 직접 입력

# ❌ 절대 이렇게 하면 안 됨
provider "aws" {
  region     = "ap-northeast-2"
  access_key = "AKIAIOSFODNN7EXAMPLE"   # 💀
  secret_key = "wJalrXUtnFEMI/K7MDENGbPxRfiCYEXAMPLEKEY"  # 💀
}절대 금지

GitHub public 저장소에 push하면 자동 스캐너가 수초 내에 키를 감지합니다. AWS 계정 탈취로 수백만 원의 요금 청구가 발생한 실제 사례가 많습니다.

안전한 자격 증명 방법

방식 설명 추천 상황
환경 변수 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 로컬 개발
자격 증명 파일 ~/.aws/credentials (aws configure) 로컬 개발
IAM Role EC2/EKS에 Role 부여 서버에서 실행 시
AWS SSO 임시 자격 증명 사용 기업 환경
OIDC (GitHub Actions) 키 없이 임시 토큰 발급 CI/CD 파이프라인
# ✅ 올바른 방법 — Provider에 키를 적지 않음
provider "aws" {
  region = "ap-northeast-2"
  # 자격 증명은 환경 변수나 ~/.aws/credentials에서 자동으로 읽힘
}main.tf

민감 값 주입 방법

# 방법 1: 환경 변수로 주입 (로컬 작업 시)
$ export TF_VAR_db_password="MySecurePassword!"
$ terraform apply

# 방법 2: AWS Secrets Manager에서 동적으로 읽기 (운영 환경 권장)
data "aws_secretsmanager_secret_version" "db_pass" {
  secret_id = "prod/myapp/db_password"
}

resource "aws_db_instance" "main" {
  username = "admin"
  password = data.aws_secretsmanager_secret_version.db_pass.secret_string
}민감 값 주입

.gitignore 필수 항목

# .gitignore
.terraform/
*.tfstate
*.tfstate.*
*.tfvars
*.tfvars.json
crash.log
override.tf
override.tf.json
.terraform.lock.hcl    # 팀에 따라 커밋 여부 결정.gitignore

변경 요청 6단계 워크플로우

1
요구사항을 코드로 표현 가능한 형태인지 확인

"이번에만 임시로 콘솔에서 열어주세요"도 Terraform 코드로 처리해야 합니다. 임시라도 콘솔 변경은 Drift를 만듭니다.

2
적용 환경 확정 (dev → staging → prod 순서)

prod에 바로 적용하지 않습니다. dev에서 먼저 검증하세요.

3
terraform plan 결과 검토

Add / Change / Destroy 수치가 의도와 일치하는지, Force Replacement가 있는지 확인합니다.

4
삭제 항목이 의도된 것인지 재확인

resource 이름 변경, 블록 삭제 등으로 의도치 않은 destroy가 발생할 수 있습니다. 이름 변경이 필요하면 terraform state mv를 먼저 실행하세요.

5
동료 리뷰 (코드 + plan 결과 함께)

plan 결과를 PR에 붙여넣고, 보안 / 가독성 / 영향 범위를 함께 확인합니다.

6
apply 후 실제 리소스 확인

apply 직후 콘솔 또는 CLI로 리소스가 정상 생성됐는지, 서비스가 정상 응답하는지, 다시 plan했을 때 "No changes"가 뜨는지 확인합니다.

# plan 결과를 파일로 저장해서 동일한 내용을 apply
$ terraform plan -out=tfplan
$ terraform apply tfplan   # 검토한 그대로만 반영됨terminal

Plan 결과 직접 분석해보기

$ terraform plan

Terraform will perform the following actions:

  # aws_security_group.web will be updated in-place
  ~ resource "aws_security_group" "web" {
      ~ ingress = [
          - {
              - cidr_blocks = ["10.0.0.0/16"]   # 내부 IP만 허용
              - from_port   = 22
              - protocol    = "tcp"
              - to_port     = 22
            },
          + {
              + cidr_blocks = ["0.0.0.0/0"]     # 전체 인터넷 허용!
              + from_port   = 22
              + protocol    = "tcp"
              + to_port     = 22
            },
        ]
    }

  # aws_db_instance.main must be replaced
  -/+ resource "aws_db_instance" "main" {
      ~ db_name = "appdb" → "app_db"  # forces replacement
      ~ id      = "db-abc123" → (known after apply)
    }

Plan: 1 to add, 1 to change, 1 to destroy.plan 결과

📋 분석 결과

질문 답변
변경되는 리소스는? security group(수정) + db_instance(삭제 후 재생성) = 총 2개
SG 변경 내용 SSH 허용 IP가 내부 VPC(10.0.0.0/16)에서 전체 인터넷(0.0.0.0/0)으로 변경
SG 변경이 안전한가? 위험! SSH 포트를 전 세계에 여는 건 심각한 보안 취약점
RDS 처리 db_name 변경은 삭제 후 재생성을 유발 (Force Replacement)
데이터 영향 데이터 손실! 기존 DB 데이터가 함께 삭제됨
안전한 진행 방법 ① RDS 스냅샷 백업 먼저 ② SG 변경 의도 재확인 ③ db_name 변경 방법 재검토 (마이그레이션 방안 필요)
🚨 이 plan은 apply하면 안 됩니다
SSH를 전 세계에 열고 + DB 데이터가 삭제됩니다. 즉시 코드 작성자에게 의도를 확인하고, RDS는 스냅샷 + 데이터 마이그레이션 계획을 먼저 수립해야 합니다.

추가로 알아야 할 개념들

data source — 기존 리소스 참조

Terraform이 만들지 않은 기존 리소스(다른 팀이 만든 VPC 등)를 코드에서 참조할 때 씁니다.

# 기존에 존재하는 VPC를 참조 (만드는 게 아님)
data "aws_vpc" "existing" {
  filter {
    name   = "tag:Name"
    values = ["prod-vpc"]
  }
}

resource "aws_subnet" "new" {
  vpc_id     = data.aws_vpc.existing.id  # 참조!
  cidr_block = "10.0.100.0/24"
}data source

depends_on — 명시적 의존성

Terraform은 리소스 간 참조를 분석해서 자동으로 순서를 결정합니다. 하지만 참조 관계가 없는데도 순서가 필요할 때는 depends_on을 명시합니다.

resource "aws_iam_role_policy_attachment" "attach" {
  role       = aws_iam_role.example.name
  policy_arn = aws_iam_policy.example.arn

  depends_on = [aws_iam_role.example]  # 명시적 의존성
}depends_on

lifecycle — 리소스 생명주기 제어

resource "aws_instance" "web" {
  ami           = "ami-0abc12345"
  instance_type = "t3.medium"

  lifecycle {
    create_before_destroy = true   # 삭제 전 새 것을 먼저 만듦 (무중단)
    prevent_destroy       = true   # 실수로 destroy 막기 (prod DB에 유용)
    ignore_changes        = [ami]  # AMI 변경은 Terraform이 무시
  }
}lifecycle 블록
✅ 실무 팁
프로덕션 RDS나 중요 S3 버킷에는 prevent_destroy = true를 반드시 붙이세요. 실수로 terraform destroy를 실행해도 에러가 나면서 막아줍니다.

dynamic 블록 — 반복 속성

variable "ingress_rules" {
  default = [
    { port = 80,  cidr = "0.0.0.0/0" },
    { port = 443, cidr = "0.0.0.0/0" },
  ]
}

resource "aws_security_group" "web" {
  name = "web-sg"

  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]
    }
  }
}dynamic 블록

terraform.lock.hcl — Provider 버전 고정

terraform init 후 생성되는 파일입니다. Provider의 정확한 버전과 체크섬을 기록해서 팀원 모두가 동일한 버전을 사용하도록 보장합니다. 반드시 Git에 커밋하세요.

moved 블록 — 리소스 이름 변경 시

# Terraform 1.1+ — state mv 없이 코드로 이름 변경
moved {
  from = aws_instance.old_name
  to   = aws_instance.new_name
}moved 블록

이전에는 terraform state mv CLI를 써야 했지만, moved 블록으로 코드에서 선언적으로 처리할 수 있습니다.

workspaces — 하나의 코드로 여러 환경

$ terraform workspace new dev
$ terraform workspace new prod
$ terraform workspace select prod
$ terraform apply    # prod workspace에 반영workspaces
⚠️ workspace 주의사항
workspace는 state만 분리합니다. 코드는 공유되므로 환경별로 큰 차이가 있는 경우에는 디렉터리를 분리하는 방식(envs/dev, envs/prod)을 더 권장합니다.

실무 도구 생태계

역할 도구 특징
코드 작성 Terraform / OpenTofu OpenTofu는 완전 오픈소스 포크 (Linux Foundation)
코드 검사 tflint 문법 오류, 잘못된 인스턴스 타입 탐지
보안 검사 tfsec / Checkov 공개 S3, 평문 시크릿 등 보안 취약점 탐지
비용 예측 Infracost plan 결과로 월 예상 비용 계산
실행 자동화 Atlantis 오픈소스, GitHub/GitLab PR에서 plan/apply 자동화
실행 자동화 Terraform Cloud (HCP) HashiCorp SaaS, 팀 플랜 이상에서 유용
모듈 반복 제거 Terragrunt backend 설정, variable 주입 반복 감소
State 저장 S3 + DynamoDB AWS 환경에서 가장 흔한 조합

실무 자동화 파이프라인

1
.tf 코드 수정 후 PR 생성

개발자가 로컬에서 작업 후 GitHub/GitLab에 PR을 올립니다.

2
CI에서 자동으로 terraform plan 실행

tflint, tfsec 검사도 함께 실행합니다.

3
plan 결과를 PR 댓글로 게시

Atlantis 또는 GitHub Actions이 자동으로 붙입니다.

4
동료가 plan 결과 리뷰 후 승인
5
PR 머지 시 자동으로 terraform apply

개발자가 로컬에서 직접 apply를 실행하지 않습니다.

6
결과를 Slack 등 알림 채널에 게시

정리 & 체크리스트

핵심 키워드 한 눈에

Provider
외부 서비스와 연결하는 플러그인
Resource
만들고 관리할 대상 (EC2, RDS, S3 …)
State
코드↔실제 인프라 매핑 기록
Plan
변경 계획 미리 보기
Apply
실제 반영
Backend
state 저장 방식 (S3 등)
Module
재사용 단위
Variable
환경별 값을 주입하는 입력
Output
생성된 리소스 정보 노출
Drift
코드↔실제 인프라 불일치 상태
locals
중간 계산 값 재사용
data
기존 리소스 참조 (읽기 전용)

실무 적용 체크리스트

  • terraform.tfstate를 Git에 커밋하지 않는다
  • Secret을 코드에 직접 적지 않는다
  • 변경 전 항상 terraform plan을 확인한다
  • Destroy 항목이 의도된 것인지 점검한다
  • Remote Backend를 사용한다 (협업 시 필수)
  • 콘솔에서 수동 변경을 하지 않는다 (Drift 방지)
  • 동료 리뷰 후 apply한다
  • Provider 버전을 고정하고 .terraform.lock.hcl을 커밋한다
  • 중요 리소스에 prevent_destroy = true를 붙인다
  • 모든 리소스에 ManagedBy = "terraform" 태그를 붙인다

다음 학습 방향

영역 학습 주제
Terraform 심화 dynamic 블록, for_each, count, locals, moved 블록
State 운영 terraform import, state mv, state rm, state pull/push
모듈 설계 Terraform Registry 공개 모듈 분석, 모듈 버전 관리
보안 강화 AWS Secrets Manager 연동, IRSA, OIDC (키 없는 CI/CD)
자동화 Atlantis, GitHub Actions, Terragrunt
분석 도구 tflint, tfsec, Checkov, Infracost
멀티 클라우드 GCP, Azure Provider
Kubernetes 연동 Helm Provider, Kubernetes Provider
🎯 마무리 한 줄 요약
코드는 Git에, 실행은 자동화에, State는 안전한 Backend에.
이 세 가지를 지키면 인프라 관리의 90%는 해결됩니다.