Terraform 標準化技能 (Terraform Standardization Skill)
此技能引導代理將原始、棕地、匯入(例如來自 Terraformer 或 terraform import)、或舊版的 Terraform 程式碼重構為模組化、符合生產等級的 HashiCorp 標準架構。
使用時機 (When to Use This Skill)
當以下情況發生時,請啟用此技能:
- 使用者要求「標準化」、「重構」、「清理」或「模組化」Terraform 配置。
- 原始 Terraform 程式碼包含硬編碼字串、魔術數字或靜態雲端 ID(
ami-*、vpc-*、subnet-*、sg-*、arn:*)。 - 單一檔案的配置(所有東西都在
main.tf)需要分離成標準的多檔案佈局(provider.tf、variables.tf、locals.tf、outputs.tf、backend.tf)。 - 雲端資源需要標準化的標籤分類法(例如
Environment、Project、ManagedBy = "terraform")。 - 程式碼庫缺少變數定義、型別約束或描述區塊。
標準化工作流程 (Standardization Workflow)
標準化任何 Terraform 程式碼庫時,請遵循以下 5 個階段程序:
┌────────────────────────────────────────────────────────┐
│ 第1階段:稽核與識別程式碼異味 (Audit & Identify │
│ Code Smells) │
│ • 偵測硬編碼的文字值、ID、區域和 CIDR │
│ • 檢查現有檔案結構與標準佈局之間的差異 │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 第2階段:建立標準檔案架構 (Establish Standard File │
│ Architecture) │
│ • 抽取 provider.tf、versions.tf 和 backend.tf │
│ • 建立 variables.tf、locals.tf 和 outputs.tf │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 第3階段:將文字參數化為變數與區域變數 (Parameterize │
│ Literals into Variables & Locals) │
│ • 提取帶有型別的環境/區域變數 │
│ • 為統一的資源標籤建立 local.common_tags │
│ • 建立 terraform.tfvars.example │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 第4階段:動態參考連結與重新命名 (Dynamic Reference │
│ Linking & Renaming) │
│ • 以動態參考取代靜態資源 ID │
│ • 以資料來源取代硬編碼的 AMI/子網路 │
│ • 標準化資源標籤(snake_case,不包含型別前綴) │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 第5階段:格式化、驗證與確認 (Format, Validate, and │
│ Verify) │
│ • 執行 `terraform fmt -recursive` │
│ • 執行 `terraform validate` │
│ • 使用 `terraform plan` 進行驗證(零漂移) │
└────────────────────────────────────────────────────────┘
逐步操作說明 (Step-by-Step Instructions)
1. 第1階段:稽核與探索 (Phase 1: Audit & Discovery)
在進行編輯前,掃描目標目錄以識別:
- CLI 可用性:執行
terraform version(對 OpenTofu 則執行tofu version)來判斷是否能在第5階段執行 CLI 格式化、驗證與計劃檢查。 - 缺少標準檔案:檢查
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階段:建立標準核心檔案 (Phase 2: Create Standard Core Files)
確保目標配置已拆分為多個單一職責的檔案:
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(若使用遠端狀態)
分離狀態儲存配置(AWS 用 s3,GCP 用 gcs,Azure 用 azurerm):
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階段:將純文字字串轉換為變數與區域變數 (Phase 3: Convert Plain Strings to Variables and Locals)
將所有硬編碼參數抽取到 variables.tf 與 locals.tf 中:
variables.tf 規則:
- 始終宣告
type:(string、number、bool、list(string)、map(string))。 - 始終包含
description:說明用途與預期格式。 - 僅在合理時提供
default:像environment或aws_region這類設定選項可以有預設值,但敏感值不應有硬編碼的預設值。 - 將機密標記為
sensitive = true:密碼、Token 與機密變數必須標記為敏感。
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 = "環境必須是以下之一:dev、staging、production。"
}
}
variable "instance_type" {
type = string
description = "應用程式叢集的 EC2 執行個體大小。"
default = "t3.medium"
}
variable "vpc_cidr" {
type = string
description = "虛擬私有雲端的基礎 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階段:以動態參考取代靜態值(零漂移防護)(Phase 4: Replace Static Values with Dynamic References (Zero-Drift Guardrails))
將靜態雲端 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** 操作,會銷毀並重建現有基礎設施。應在variables.tf/terraform.tfvars中將現有的雲端名稱參數化,而不是將它們改寫為"${local.name_prefix}-..."。 - 在棕地匯入時鎖定已匯入的映像/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)折疊為單一for_each資源,並搭配moved區塊(to = google_compute_instance.api["01"])。 - 將內嵌的
\n跳脫字元user_data/startup-script字串提取到scripts/檔案中,並透過file()或templatefile()載入。 - 在變更 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階段:格式化、漂移稽核與驗證 (Phase 5: Formatting, Drift Audit, and Validation)
-
計劃前漂移稽核檢查清單(始終執行,即使本機未安裝
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. 若在具有狀態的活躍環境中工作,請確保計劃不會產生非預期的差異
terraform plan
- 摘要
terraform plan變更(偵測到任何差異時): 如果terraform plan回報的內容不是No changes,請在建議apply之前停止,並提出結構化摘要:- 計劃總計:說明確切的資源計數(
X 個新增,Y 個變更,Z 個銷毀)以及任何moved狀態轉換。 - 先列出高風險變更(
-/+取代,-銷毀):突出顯示每個標記為取代或刪除的資源,指出導致該操作的ForceNew屬性(例如name、image、boot_disk),並說明如何修復(例如新增缺少的moved {}區塊,或在tfvars中還原原始值)。 - 就地更新(
~)與建立(+):列出每個受影響的資源以及正在變更的確切屬性(例如labels、tags、scopes、public_access_prevention)。 - 有意的差異 vs. 非預期的漂移:清楚標記哪些差異是有意的(選擇加入的安全性強化或標籤標準化),哪些是非預期的棕地漂移,以便使用者批准或還原。
- 計劃總計:說明確切的資源計數(