Terraform 표준화 스킬
이 스킬은 에이전트가 원시, 브라운필드, 임포트된(예: Terraformer 또는 terraform import), 또는 레거시 Terraform 코드를 모듈화된 프로덕션 등급 HashiCorp 표준 아키텍처로 리팩토링하도록 안내합니다.
이 스킬을 사용해야 할 때
다음과 같은 경우 이 스킬을 활성화하세요:
- 사용자가 Terraform 구성을 "표준화", "리팩토링", "정리", 또는 "모듈화"하도록 요청할 때.
- 원시 Terraform 코드에 하드코딩된 문자열, 매직 넘버, 또는 정적 클라우드 ID(
ami-*,vpc-*,subnet-*,sg-*,arn:*)가 포함될 때. - 단일 파일 구성(
main.tf하나에 모든 것이 들어있는 경우)을 표준 다중 파일 레이아웃(provider.tf,variables.tf,locals.tf,outputs.tf,backend.tf)으로 분리해야 할 때. - 클라우드 리소스에 표준화된 태깅 분류 체계(예:
Environment,Project,ManagedBy = "terraform")가 필요할 때. - 코드베이스에 변수 정의, 타입 제약 조건, 또는 설명 블록이 없는 경우.
표준화 워크플로우
Terraform 코드베이스를 표준화할 때 다음 5단계 절차를 따르세요:
┌────────────────────────────────────────────────────────┐
│ 1단계: 감사 및 코드 냄새 식별 │
│ • 하드코딩된 리터럴, ID, 리전, CIDR 탐지 │
│ • 기존 파일 구조와 표준 레이아웃 비교 검사 │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 2단계: 표준 파일 아키텍처 구축 │
│ • provider.tf, versions.tf, backend.tf 추출 │
│ • variables.tf, locals.tf, outputs.tf 생성 │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 3단계: 리터럴을 변수 및 로컬로 매개변수화 │
│ • 타입과 함께 환경/리전 변수 추출 │
│ • 통합 리소스 태깅을 위한 local.common_tags 구축 │
│ • terraform.tfvars.example 생성 │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 4단계: 동적 참조 연결 및 이름 변경 │
│ • 정적 리소스 ID를 동적 참조로 대체 │
│ • 하드코딩된 AMI/서브넷을 데이터 소스로 대체 │
│ • 리소스 레이블 표준화 (snake_case, 타입 접미사 없음) │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 5단계: 포맷팅, 검증 및 확인 │
│ • `terraform fmt -recursive` 실행 │
│ • `terraform validate` 실행 │
│ • `terraform plan`으로 확인 (제로 드리프트) │
└────────────────────────────────────────────────────────┘
단계별 지침
1. 1단계: 감사 및 발견
편집을 시작하기 전에 대상 디렉터리를 스캔하여 다음을 식별하십시오:
- CLI 가용성:
terraform version(또는 OpenTofu의 경우tofu version)을 실행하여 5단계에서 CLI 포맷팅, 검증 및 plan 검사를 실행할 수 있는지 확인합니다. - 누락된 표준 파일:
versions.tf,provider.tf,variables.tf,locals.tf,outputs.tf가 존재하는지 확인합니다. - 하드코딩된 ID 및 리터럴:
- 클라우드 식별자:
ami-[a-f0-9]+,vpc-[a-f0-9]+,subnet-[a-f0-9]+,sg-[a-f0-9]+,igw-[a-f0-9]+, 전체 GCP 자체 링크 (https://www.googleapis.com/compute/...), 정적projectID - CIDR 블록:
"10.0.0.0/16","0.0.0.0/0" - 인스턴스 유형:
"t3.micro","m5.large","e2-standard-4" - 정적 리전 및 계정 번호:
"us-east-1","us-central1","123456789012" - 원시 자격 증명 또는 암호화되지 않은 비밀
- 클라우드 식별자:
- 포맷팅 기준선: Terraform이 설치된 경우, 파일이
terraform fmt -check -recursive를 준수하는지 확인합니다.
2. 2단계: 표준 핵심 파일 생성
대상 구성을 개별적이고 단일 책임을 가진 파일로 분할했는지 확인하십시오:
provider.tf / versions.tf
Terraform 설정, 최소 CLI 버전, 필수 제공자(AWS, GCP 또는 Azure에 맞게 조정)를 중앙 집중화합니다:
# versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
# GCP 예시:
# google = {
# source = "hashicorp/google"
# version = "~> 5.5"
# }
}
}
# provider.tf (AWS)
provider "aws" {
region = var.aws_region
default_tags {
tags = local.common_tags
}
}
# provider.tf (GCP 대안)
# provider "google" {
# project = var.project_id
# region = var.region
# default_labels = local.common_labels
# }
단일 태깅/레이블링 전략: 제공자 수준의
default_tags/default_labels(개별 리소스에는 리소스별 키만 입력) 또는 각 리소스에merge(local.common_tags, ...)/merge(local.common_labels, ...)중 하나만 사용하십시오 — 둘을 중복해서 사용하지 마십시오.
backend.tf (원격 상태를 사용하는 경우)
별도의 상태 저장소 구성 (s3 for AWS, gcs for GCP, azurerm for Azure):
terraform {
backend "s3" {
bucket = "myorg-terraform-state"
key = "services/app/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# GCP 예시:
# terraform {
# backend "gcs" {
# bucket = "myorg-terraform-state"
# prefix = "services/app"
# }
# }
3. 3단계: 일반 문자열을 변수 및 로컬로 변환
모든 하드코딩된 매개변수를 variables.tf 및 locals.tf로 추출하십시오:
variables.tf 규칙:
- 항상
type선언: (string,number,bool,list(string),map(string)). - 항상
description포함: 목적과 예상 형식을 설명합니다. - 합당한 경우에만
default제공:environment또는aws_region과 같은 구성 옵션은 기본값을 가질 수 있지만, 민감한 값은 하드코딩된 기본값을 가져서는 안 됩니다. - 비밀을
sensitive = true로 표시: 비밀번호, 토큰 및 비밀 변수는 반드시 민감함으로 표시해야 합니다.
variables.tf 예시:
variable "aws_region" {
type = string
description = "리소스가 프로비저닝될 AWS 리전입니다."
default = "us-east-1"
}
variable "environment" {
type = string
description = "배포 환경 이름 (예: dev, staging, prod)."
default = "production"
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment는 dev, staging, production 중 하나여야 합니다."
}
}
variable "instance_type" {
type = string
description = "애플리케이션 클러스터의 EC2 인스턴스 크기입니다."
default = "t3.medium"
}
variable "vpc_cidr" {
type = string
description = "Virtual Private Cloud의 기본 CIDR 블록입니다."
default = "10.0.0.0/16"
}
locals.tf 규칙:
내부적으로 파생된 값, 환경 접두사 및 공통 태그에는 locals를 사용하십시오:
locals {
name_prefix = "${var.project_name}-${var.environment}"
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "terraform"
}
}
terraform.tfvars.example
동료가 값을 제공하는 방법을 알 수 있도록 항상 예시 변수 값 파일을 생성하십시오:
aws_region = "us-east-1"
environment = "production"
project_name = "checkout-service"
4. 4단계: 정적 값을 동적 참조로 대체 (제로 드리프트 가드레일)
정적 클라우드 ID와 분리된 리소스 참조를 기본 HCL 표현식으로 변환하면서 라이브 인프라를 우발적인 교체로부터 보호하십시오:
불변성 및 ForceNew 가드레일 (브라운필드 / Terraformer에 중요)
- Terraform 주소와 클라우드
name속성 구분:moved {}블록으로 Terraform 리소스 레이블(tfer--prod-main-vpc→main)의 이름을 바꾸는 것은 안전하고 무중단입니다. 클라우드name속성(예:google_compute_network.name,google_compute_subnetwork.name,google_compute_firewall.name,google_compute_instance.name또는aws_s3_bucket.bucket)을 변경하는 것은 **ForceNew**이며 라이브 인프라를 파괴하고 재생성합니다. 기존 클라우드 이름을"${local.name_prefix}-..."로 다시 작성하는 대신variables.tf/terraform.tfvars에서 매개변수화하십시오. - 브라운필드 임포트 시 임포트된 이미지/AMI 고정: 하드코딩된
ami-*또는debian-12-bookworm-v20240515를most_recent = true/ familydata조회로 전환하면 최신 이미지가 존재하는 경우 기존 VM이 교체됩니다. 제로 드리프트가 필요한 경우 변수/data필터를 통해 정확한 임포트된 이미지/AMI 이름 또는 ID를 고정하고, 사용자가 이미지 업그레이드를 선택한 경우에만most_recent = true를 활성화하십시오. - 보안 강화를 제로 드리프트 리팩토링과 분리하십시오: 제로 드리프트 마이그레이션 중에 동작/보안 속성(예:
http_tokens"optional"→"required",public_access_prevention"inherited"→"enforced", 또는 VM OAuthscopes)을 자동으로 변경하지 마십시오. 강화는 명시적 옵트인으로 취급하거나 모든 의도적인 차이를 명확히 나열하십시오. - 마이그레이션 시 레거시 태그/레이블 키 유지: 기존 리소스가
env = "prod"를 사용하는 경우, 키 교체가 승인되지 않는 한 기존 키를 유지(또는 표준화된 키를 함께 추가)하십시오.
Terraformer 특화 정리 체크리스트
- 계산된 읽기 전용 상태 속성(AWS의
tags_all; GCP의effective_labels및terraform_labels)과 빈/기본값 속성("",[],{},false,0)을 제거하십시오. - 충돌하는 상태 덤프 하위 블록(예:
subnet_id가 설정된 경우 EC2 기본network_interface)을 제거하십시오. - 레거시
${data.terraform_remote_state.*.outputs.*}보간 및 전체 자체 링크 URL을 직접 리소스 참조(예:google_compute_network.main.id) 또는data조회로 대체하십시오. - 번호가 매겨진 중복 리소스(
vm-01,vm-02)를moved블록(to = google_compute_instance.api["01"])과 쌍을 이루는 단일for_each리소스로 축소하십시오. - 인라인
\n이스케이프된user_data/startup-script문자열을file()또는templatefile()을 통해 로드되는scripts/파일로 추출하십시오. - GCP VM
scopes또는machine_type을 변경할 때, 인플레이스 업데이트가 의도된 경우allow_stopping_for_update = true가 설정되었는지 확인하십시오.
안티 패턴 (원시 / 하드코딩):
# 나쁨: 하드코딩된 ID 및 리터럴 문자열
resource "aws_instance" "tfer--i-0123456789abcdef0" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = "subnet-01234567"
vpc_security_group_ids = [
"sg-0abcdef1234567890"
]
}
표준 패턴 (리팩토링):
# 1. 상태 보존: 이 리소스의 이름이 변경되었음을 Terraform에 알림 (파괴 및 재생성 방지)
moved {
from = aws_instance.tfer--i-0123456789abcdef0
to = aws_instance.web
}
# 2. 기본 이미지에 대한 동적 조회 (제로 드리프트 브라운필드의 경우 정확한 이름 고정, 그린필드의 경우 most_recent 사용)
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = [var.ami_name_pattern]
}
}
# 3. 동적 참조를 사용한 표준화된 리소스 정의
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = aws_subnet.private[0].id
vpc_security_group_ids = [aws_security_group.web.id]
# provider.tf에서 이미 default_tags를 설정했다면 여기서 common_tags는 생략
tags = {
Name = var.instance_name
}
}
5. 5단계: 포맷팅, 드리프트 감사 및 검증
-
사전 계획 드리프트 감사 체크리스트 (특히
terraformCLI가 로컬에 설치되지 않은 경우 항상 수행):- 모든 리소스의 클라우드
name,image/ami,location/zone,tags/labels, 보안/네트워크 설정을 임포트된 소스와 비교하십시오. ForceNew필드가 실수로 변경되지 않았는지, 모든tfer--*리소스에 일치하는moved {}블록이 있는지 확인하십시오.- 의도적인 차이(예: 보안 강화)를 제로 드리프트 리팩토링과 분리하여 보고하십시오.
- 모든 리소스의 클라우드
-
Terraform CLI 검사 실행 (설치된 경우):
# 1. 표준 정규 HCL 들여쓰기로 코드 포맷팅
terraform fmt -recursive
# 2. 제공자 플러그인 초기화 (오프라인/상태 없이 검증 시 -backend=false 사용) 및 구문 검증
terraform init -backend=false
terraform validate
# 3. 활성 환경에서 작업 중이고 상태가 있다면 plan이 의도하지 않은 차이를 생성하지 않는지 확인
terraform plan
terraform plan변경 사항 요약 (차이가 감지된 경우):terraform plan이No changes이외의 결과를 보고하면,apply를 권장하기 전에 중단하고 구조화된 요약을 제시하십시오:- Plan 총계: 정확한 리소스 수(
X to add, Y to change, Z to destroy)와 모든moved상태 전환을 명시하십시오. - 고위험 변경 사항 먼저 (
-/+교체,-삭제): 교체 또는 삭제로 표시된 모든 리소스를 강조하고, 이를 유발하는ForceNew속성(예:name,image,boot_disk)을 명명하고, 수정 방법(예: 누락된moved {}블록 추가 또는tfvars에서 원래 값 복원)을 설명하십시오. - 인플레이스 업데이트 (
~) 및 생성 (+): 영향을 받는 각 리소스와 정확히 변경되는 속성(예:labels,tags,scopes,public_access_prevention)을 나열하십시오. - 의도적 vs 의도하지 않은 드리프트: 사용자가 승인하거나 되돌릴 수 있도록 어떤 차이가 의도적인 것(옵트인 보안 강화 또는 태그 표준화)이고 어떤 것이 의도하지 않은 브라운필드 드리프트인지 명확히 표시하십시오.
- Plan 총계: 정확한 리소스 수(
상세 지침 및 참조
- 도메인 그룹화 패턴은 표준 파일 레이아웃을 참조하십시오.
- 명명,
ForceNew가드레일, 태그/레이블 및 안티 패턴 체크리스트는 리팩토링 규칙을 참조하십시오.