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/サブネットをデータソースに置き換え │
│ • リソースラベルを標準化(スネークケース、タイプなし) │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ フェーズ5: フォーマット、検証、確認 │
│ • `terraform fmt -recursive` を実行 │
│ • `terraform validate` を実行 │
│ • `terraform plan` で確認(ゼロドリフト) │
└────────────────────────────────────────────────────────┘
ステップバイステップの手順
1. フェーズ1: 監査と発見
編集を開始する前に、ターゲットディレクトリをスキャンして以下を特定します:
- 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: 標準コアファイルの作成
ターゲット設定が、明確で単一責任のファイルに分割されていることを確認します:
provider.tf / versions.tf
Terraform 設定、最小 CLI バージョン、必要なプロバイダーを一元管理します(AWS、GCP、Azure に合わせて調整):
# versions.tf erraform {
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):
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 = "The AWS region where resources will be provisioned."
default = "us-east-1"
}
variable "environment" {
type = string
description = "Deployment environment name (e.g., dev, staging, prod)."
default = "production"
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be one of: dev, staging, production."
}
}
variable "instance_type" {
type = string
description = "EC2 instance size for the application cluster."
default = "t3.medium"
}
variable "vpc_cidr" {
type = string
description = "Base CIDR block for the Virtual Private Cloud."
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が設定されていることを確認します。
アンチパターン(生 / ハードコード):
# BAD: ハードコードされた 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)をリストします。 - 意図的なドリフトと意図しないドリフト: どの差分が意図的(オプトインされたセキュリティ強化やタグ標準化)であり、どの差分が意図しないブラウンフィールドドリフトであるかを明確にマークし、ユーザーが承認または差し戻しできるようにします。
- Plan の集計: 正確なリソース数(
詳細なガイドラインと参考資料
- ドメイングループ化パターンについては、標準ファイルレイアウトを参照してください。
- 命名、
ForceNewガードレール、タグ/ラベル、アンチパターンチェックリストについては、リファクタリングルールを参照してください。