Community코딩 & 개발github.com

daily-dreamer/terraform-standardize

원시, 브라운필드 또는 Terraformer로 가져온 Terraform 코드(AWS, GCP, Azure)를 프로덕션 등급 HashiCorp 표준으로 정규화합니다: provider.tf, versions.tf, backend.tf를 분리하고, 하드코딩된 문자열을 타입화된 variables.tf와 locals.tf로 매개변수화하며, 정적 클라우드 ID를 동적 참조로 대체하고, ForceNew 및 제로 드리프트 가드레일을 moved 블록으로 적용하며, 정규 HCL 포맷팅, 검증, plan diff 요약을 실행합니다.

terraform-standardize란 무엇인가요?

이 스킬은 에이전트가 원시, 브라운필드, 임포트된(Terraformer 또는 'terraform import' 등), 또는 레거시 Terraform 코드를 모듈화된 프로덕션 등급 HashiCorp 표준 아키텍처로 리팩토링하도록 안내합니다. AWS, GCP, Azure를 포함한 주요 클라우드 제공자를 지원하며, 5단계 워크플로우(감사 및 코드 냄새 식별, 표준 파일 아키텍처 구축, 리터럴을 변수와 로컬로 매개변수화, 동적 참조 연결 및 이름 변경, 포맷팅 및 검증)를 따릅니다. 하드코딩된 문자열, 정적 ID, 누락된 변수 정의 및 단일 파일 구성을 식별하고 해결합니다. moved 블록을 적용하여 기존 리소스의 안전한 이름 변경을 보장하고, ForceNew 속성 변경으로 인한 의도치 않은 리소스 교체를 방지합니다. Terraformer로 가져온 코드의 태그 정리, 충돌하는 하위 블록 제거, 중복 리소스 통합을 위한 특정 정리 단계를 포함합니다. terraform plan을 실행할 때 의도적인 변경과 의도하지 않은 드리프트를 명확히 구분하여 구조화된 변경 요약을 제공합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/daily-dreamer/terraform-standardize/tree/HEAD/skills/terraform-standardize

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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단계: 감사 및 발견

편집을 시작하기 전에 대상 디렉터리를 스캔하여 다음을 식별하십시오:

  1. CLI 가용성: terraform version (또는 OpenTofu의 경우 tofu version)을 실행하여 5단계에서 CLI 포맷팅, 검증 및 plan 검사를 실행할 수 있는지 확인합니다.
  2. 누락된 표준 파일: versions.tf, provider.tf, variables.tf, locals.tf, outputs.tf가 존재하는지 확인합니다.
  3. 하드코딩된 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/...), 정적 project ID
    • CIDR 블록: "10.0.0.0/16", "0.0.0.0/0"
    • 인스턴스 유형: "t3.micro", "m5.large", "e2-standard-4"
    • 정적 리전 및 계정 번호: "us-east-1", "us-central1", "123456789012"
    • 원시 자격 증명 또는 암호화되지 않은 비밀
  4. 포맷팅 기준선: 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 / family data 조회로 전환하면 최신 이미지가 존재하는 경우 기존 VM이 교체됩니다. 제로 드리프트가 필요한 경우 변수/data 필터를 통해 정확한 임포트된 이미지/AMI 이름 또는 ID를 고정하고, 사용자가 이미지 업그레이드를 선택한 경우에만 most_recent = true를 활성화하십시오.
  • 보안 강화를 제로 드리프트 리팩토링과 분리하십시오: 제로 드리프트 마이그레이션 중에 동작/보안 속성(예: http_tokens "optional" → "required", public_access_prevention "inherited" → "enforced", 또는 VM OAuth scopes)을 자동으로 변경하지 마십시오. 강화는 명시적 옵트인으로 취급하거나 모든 의도적인 차이를 명확히 나열하십시오.
  • 마이그레이션 시 레거시 태그/레이블 키 유지: 기존 리소스가 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단계: 포맷팅, 드리프트 감사 및 검증

  1. 사전 계획 드리프트 감사 체크리스트 (특히 terraform CLI가 로컬에 설치되지 않은 경우 항상 수행):

    • 모든 리소스의 클라우드 name, image/ami, location/zone, tags/labels, 보안/네트워크 설정을 임포트된 소스와 비교하십시오.
    • ForceNew 필드가 실수로 변경되지 않았는지, 모든 tfer--* 리소스에 일치하는 moved {} 블록이 있는지 확인하십시오.
    • 의도적인 차이(예: 보안 강화)를 제로 드리프트 리팩토링과 분리하여 보고하십시오.
  2. Terraform CLI 검사 실행 (설치된 경우):

# 1. 표준 정규 HCL 들여쓰기로 코드 포맷팅
terraform fmt -recursive

# 2. 제공자 플러그인 초기화 (오프라인/상태 없이 검증 시 -backend=false 사용) 및 구문 검증
terraform init -backend=false
terraform validate

# 3. 활성 환경에서 작업 중이고 상태가 있다면 plan이 의도하지 않은 차이를 생성하지 않는지 확인
terraform plan
  1. 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 의도하지 않은 드리프트: 사용자가 승인하거나 되돌릴 수 있도록 어떤 차이가 의도적인 것(옵트인 보안 강화 또는 태그 표준화)이고 어떤 것이 의도하지 않은 브라운필드 드리프트인지 명확히 표시하십시오.

상세 지침 및 참조

관련 스킬