目次
- Core Terraform Workflow
- 📦 Code Examples
- The Terraform Lifecycle
- Step 1: Write Configuration
- Step 2: Initialize the Project
- Step 3: Format Your Code
- Step 4: Validate Configuration
- Step 5: Preview Changes (Plan)
- Step 6: Apply Changes
- Step 7: Inspect Your Infrastructure
- Step 8: Destroy Infrastructure
- Debugging Terraform
- Importing Existing Infrastructure
- State Operations
- Advanced CLI Operations
- The Complete Workflow Script
- Checkpoint Questions
- What's Next: Part 7 - Modules for Organization
- Series Navigation
- References
Core Terraform Workflow
HCL構文、変数、そしてstate管理について学びました。では次に、実際のworkflowについて説明します。これはインフラに触れるたびに使う日常的なサイクルです。
これはPart 6 of 12です。この記事を読み終えるころには、必要なコマンド、使うタイミング、そして問題が起きたときのデバッグ方法を理解できます。
📦 Code Examples
Repository: terraform-hcl-tutorial-series This Part: Part 6 - Workflow Examples
動作するサンプルを取得:
git clone https://github.com/khuongdo/terraform-hcl-tutorial-series.git
cd terraform-hcl-tutorial-series
git checkout part-06
cd examples/part-06-workflow/
# workflowを練習
terraform init
terraform fmt
terraform validate
terraform plan
The Terraform Lifecycle
すべてのインフラ変更は、このパターンに従います:
Write → Init → Format → Validate → Plan → Apply → Destroy
↑______________________________________________|
(Repeat for changes)
各ステップには役割があります。一つでもスキップすると、なぜそれが存在するのか困難な方法で理解することになります。
| Step | What It Does | What Breaks If You Skip It |
|---|---|---|
| Write | 欲しいものを定義 | デプロイするものがない |
| Init | Providerをダウンロード、backendをセットアップ | "not initialized"でコマンドが失敗 |
| Format | スペースとインデントをクリーンアップ | 乱雑なdiff、面倒なコードレビュー |
| Validate | 実行前に構文チェック | plan/apply中のエラー |
| Plan | 変更をプレビュー | 予期しない削除、事故 |
| Apply | 実際にリソースを作成 | インフラがない |
| Destroy | すべて削除 | 孤立したリソース、予期しない請求 |
それでは、各ステップを見ていきましょう。
Step 1: Write Configuration
まず、欲しいものを書きます。基本的なEC2インスタンスのセットアップ例です:
# main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = "${var.project_name}-web-server"
Environment = var.environment
}
}
# variables.tf
variable "aws_region" {
description = "AWS region for resources"
type = string
default = "us-east-1"
}
variable "ami_id" {
description = "AMI ID for EC2 instance"
type = string
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "project_name" {
description = "Project name for resource tagging"
type = string
}
variable "environment" {
description = "Environment (dev/staging/prod)"
type = string
}
# outputs.tf
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.web.id
}
output "public_ip" {
description = "Public IP address of the instance"
value = aws_instance.web.public_ip
}
Step 2: Initialize the Project
Command: terraform init
これは常に最初に実行するコマンドです。以下のことを行います:
- Providerプラグインをダウンロード(AWS、GCPなど)
- Backendをセットアップ(stateの保存場所)
.terraformディレクトリを作成
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.31.0...
- Installed hashicorp/aws v5.31.0 (signed by HashiCorp)
Terraform has been successfully initialized!
initを再実行するタイミング:
- 新しいproviderを追加した
- Backend設定を変更した
- Gitを新しくcloneした
.terraform/ディレクトリを削除した
terraform initは何度実行しても安全です。必要かどうか分からない場合は、実行してください。
Step 3: Format Your Code
Command: terraform fmt
.tfファイルをTerraformの標準スタイルに自動フォーマットします:
$ terraform fmt
main.tf
variables.tf
修正される内容:
- インデント(2スペース)
=周りのスペース- アライメント
- 末尾の空白
フォーマット前:
resource "aws_instance" "web"{
ami="ami-12345"
instance_type = "t3.micro"
tags={
Name="web-server"
}
}
terraform fmt実行後:
resource "aws_instance" "web" {
ami = "ami-12345"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}
これをGitのpre-commitフックに追加すれば、誰も乱雑なコードをpushできなくなります:
#!/bin/bash
# .git/hooks/pre-commit
terraform fmt -check -recursive
if [ $? -ne 0 ]; then
echo "Error: Terraform files not formatted. Run 'terraform fmt'"
exit 1
fi
Step 4: Validate Configuration
Command: terraform validate
Cloud APIに触れることなく、構文エラーや必須フィールドの欠落をチェックします:
$ terraform validate
Success! The configuration is valid.
検出される内容:
- 不正なHCL構文
- 必須引数の欠落
- 無効なリソース参照
- 型の不一致
Validation失敗の例:
resource "aws_instance" "web" {
ami = var.ami_id
# おっと、instance_typeを忘れた
}
$ terraform validate
Error: Missing required argument
on main.tf line 10, in resource "aws_instance" "web":
10: resource "aws_instance" "web" {
The argument "instance_type" is required, but no definition was found.
terraform validateは完全にローカルで実行されます。API呼び出しは一切行いません。早めに頻繁に使用してください。
Step 5: Preview Changes (Plan)
Command: terraform plan
これが最も重要なステップです。何かを変更する前に、正確に何が起こるかを表示します:
$ terraform plan -var-file="dev.tfvars" -out=tfplan
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0c55b159cbfafe1f0"
+ instance_type = "t3.micro"
+ id = (known after apply)
+ public_ip = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "myapp-web-server"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ instance_id = (known after apply)
+ public_ip = (known after apply)
────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
Planシンボルの理解:
| Symbol | Meaning | Risk Level |
|---|---|---|
+ | リソースが作成される | Low |
~ | リソースがその場で変更される | Medium |
-/+ | リソースが破棄され再作成される | High |
- | リソースが破棄される | Critical |
より安全なapplyのためにplanを保存:
# planファイルを生成
terraform plan -out=tfplan
# planをレビュー
terraform show tfplan
# 正確なplanを適用
terraform apply tfplan
planを保存する理由:
-outなしでterraform applyを実行すると、apply時に新しいplanが生成されます。planとapplyの間に何かが変更された場合(drift、他の人の変更)、レビューしたものとは異なるものを適用する可能性があります。
本番環境では常にterraform plan -out=tfplanの後にterraform apply tfplanを使用してください。保存されたplanをレビューせずにterraform applyを実行しないでください。
Step 6: Apply Changes
Command: terraform apply
これはplanを実行し、インフラを作成/変更/破棄します:
# Interactive apply(確認を求める)
$ terraform apply -var-file="dev.tfvars"
# Auto-approve(CI/CDでのみ使用)
$ terraform apply -var-file="dev.tfvars" -auto-approve
# 保存されたplanを適用(推奨)
$ terraform apply tfplan
Output:
aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Still creating... [20s elapsed]
aws_instance.web: Creation complete after 25s [id=i-0abcd1234efgh5678]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
instance_id = "i-0abcd1234efgh5678"
public_ip = "54.123.45.67"
Apply中に起こること:
- Terraformがstateファイルをロック(他の実行を防止)
- Cloud providerにAPI呼び出し
- 新しいリソースIDで
terraform.tfstateを更新 - Stateファイルをアンロック
- Outputsを表示
Apply実行中に失敗した場合:
クラッシュした場合(ネットワークエラー、APIリミット、タイムアウト)、stateファイルは実際に作成されたものを反映します。問題を修正してterraform applyを再実行すれば、Terraformは中断したところから再開します。
Step 7: Inspect Your Infrastructure
Apply後、作成したものを確認します:
# state内のすべてのリソースをリスト
$ terraform state list
aws_instance.web
# 詳細なリソース属性を表示
$ terraform state show aws_instance.web
# aws_instance.web:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
id = "i-0abcd1234efgh5678"
public_ip = "54.123.45.67"
tags = {
"Environment" = "dev"
"Name" = "myapp-web-server"
}
}
# Outputsを再度表示
$ terraform output
instance_id = "i-0abcd1234efgh5678"
public_ip = "54.123.45.67"
# 特定のoutput値を取得(スクリプト用)
$ terraform output -raw public_ip
54.123.45.67
Step 8: Destroy Infrastructure
Command: terraform destroy
終了したら(dev環境、テストなど)、削除します:
$ terraform destroy -var-file="dev.tfvars"
Terraform will perform the following actions:
# aws_instance.web will be destroyed
- resource "aws_instance" "web" {
- ami = "ami-0c55b159cbfafe1f0" -> null
- instance_type = "t3.micro" -> null
- id = "i-0abcd1234efgh5678" -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_instance.web: Destroying... [id=i-0abcd1234efgh5678]
aws_instance.web: Destruction complete after 35s
Destroy complete! Resources: 1 destroyed.
ターゲット指定での削除(特定のリソース):
# Webインスタンスのみ削除
terraform destroy -target=aws_instance.web
terraform destroyは元に戻せません。常にterraform plan -destroyを最初に実行して、何が削除されるかをプレビューしてください。本番環境では、lifecycle { prevent_destroy = true }で重要なリソースを保護してください。
Debugging Terraform
問題が発生したとき、Terraformは詳細なログを提供します:
Enable Debug Logging
# ログレベルを設定(TRACE、DEBUG、INFO、WARN、ERROR)
export TF_LOG=DEBUG
# ログをファイルに保存
export TF_LOG_PATH=terraform-debug.log
# Terraformコマンドを実行
terraform apply
# ロギングを無効化
unset TF_LOG TF_LOG_PATH
Log levels:
| Level | Use Case |
|---|---|
TRACE | 最大の詳細度 - すべてのAPI呼び出しを表示 |
DEBUG | 詳細なデバッグ情報 |
INFO | 標準的な操作メッセージ |
WARN | 警告メッセージのみ |
ERROR | エラーのみ |
Common Debugging Scenarios
Problem: "Error: Provider configuration not present"
# Solution: initを実行
terraform init
Problem: "Error: Inconsistent dependency lock file"
# Solution: lockファイルを再生成
terraform init -upgrade
Problem: "Error: Backend initialization required"
# Solution: backendを移行
terraform init -migrate-state
Problem: "Error: Resource already exists"
# Solution: 既存のリソースをimport
terraform import aws_instance.web i-1234567890abcdef0
Importing Existing Infrastructure
手動で作成したリソースがありますか?Terraformにimportしましょう:
Step 1: リソース設定を書く
# main.tf
resource "aws_instance" "legacy" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "legacy-server"
}
}
Step 2: リソースをimport
# 構文: terraform import <resource_type>.<name> <cloud_resource_id>
$ terraform import aws_instance.legacy i-1234567890abcdef0
aws_instance.legacy: Importing from ID "i-1234567890abcdef0"...
aws_instance.legacy: Import prepared!
Prepared aws_instance for import
aws_instance.legacy: Refreshing state... [id=i-1234567890abcdef0]
Import successful!
Step 3: importを確認
$ terraform plan
No changes. Your infrastructure matches the configuration.
Planが変更を表示する場合は、実際のリソースに合わせて設定を更新してください。
Import後にterraform showを使用して完全なリソース設定を確認し、.tfファイルにコピーしてください。
State Operations
Terraform stateコマンドを使用すると、cloudリソースに触れることなくstateを検査および操作できます:
List Resources
$ terraform state list
aws_instance.web
aws_security_group.web_sg
aws_vpc.main
Show Resource Details
$ terraform state show aws_instance.web
# aws_instance.web:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
id = "i-0abcd1234efgh5678"
instance_type = "t3.micro"
}
Move Resources (Refactoring)
コード内でリソースの名前を変更したら、stateを更新して一致させます:
# コード内で名前変更: aws_instance.web → aws_instance.app_server
$ terraform state mv aws_instance.web aws_instance.app_server
Move "aws_instance.web" to "aws_instance.app_server"
Successfully moved 1 object(s).
Remove Resources from State
リソースを破棄せずにTerraform管理から削除:
$ terraform state rm aws_instance.web
Removed aws_instance.web
Successfully removed 1 resource instance(s).
インスタンスはAWSに残っていますが、Terraformはもう管理しません。
Advanced CLI Operations
Refresh State
Terraform stateを実際のcloudリソースと同期:
$ terraform refresh
使用するタイミング:
- 誰かがcloudコンソールで手動変更を行った
- stateと実際の状態の差分をチェック
Note: Terraformはplanとapply中に自動的にrefreshするため、明示的なrefreshはほとんど必要ありません。
Replace Resources
リソースの強制置換(破棄して再作成):
# 特定のリソースを置換
$ terraform apply -replace=aws_instance.web
# 以下の場合に有用:
# - リソースが破損している
# - 新しいインスタンスIDが必要
# - 災害復旧のテスト
Targeted Apply
特定のリソースのみに変更を適用:
# Webインスタンスのみ作成/更新
$ terraform apply -target=aws_instance.web
# 複数のターゲット
$ terraform apply -target=aws_instance.web -target=aws_security_group.web_sg
-targetの使用は不整合なstateを作成する可能性があります。部分的な失敗からの回復以外では、本番環境での使用を避けてください。
The Complete Workflow Script
本番環境対応のworkflowスクリプトです:
#!/bin/bash
# deploy.sh - Safe Terraform deployment script
set -e # エラー時に終了
ENVIRONMENT=${1:-dev}
VAR_FILE="${ENVIRONMENT}.tfvars"
echo "========================================="
echo "Deploying to: $ENVIRONMENT"
echo "========================================="
# Step 1: Initialize
echo "→ Initializing Terraform..."
terraform init
# Step 2: Format check
echo "→ Checking code formatting..."
terraform fmt -check -recursive
if [ $? -ne 0 ]; then
echo "❌ Code not formatted. Run: terraform fmt -recursive"
exit 1
fi
# Step 3: Validate
echo "→ Validating configuration..."
terraform validate
# Step 4: Generate plan
echo "→ Generating plan..."
terraform plan -var-file="$VAR_FILE" -out=tfplan
# Step 5: Show plan
echo "→ Plan generated. Review changes above."
read -p "Apply this plan? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo "❌ Deployment cancelled."
rm tfplan
exit 0
fi
# Step 6: Apply plan
echo "→ Applying changes..."
terraform apply tfplan
# Cleanup
rm tfplan
echo "✅ Deployment complete!"
terraform output
使用方法:
chmod +x deploy.sh
./deploy.sh dev # devにデプロイ
./deploy.sh staging # stagingにデプロイ
./deploy.sh prod # productionにデプロイ
Checkpoint Questions
理解度をテストしましょう:
terraform initは何をしますか?planやapplyの前に実行しなければならないのはなぜですか?terraform planとterraform applyの違いは? Planせずにapplyできますか?コード変更なしで
terraform applyを2回実行するとどうなりますか?すべてを削除せずに特定のリソースのみを削除するには?
なぜ
-out=tfplanでplanを保存するのですか? どんな問題を解決しますか?terraform importを使用するのはいつですか? Terraformが作成したリソースをimportできますか?terraform state rmとterraform destroyの違いは何ですか?
クリックして回答を表示
terraform initはproviderプラグインをダウンロードし、backendを初期化します。すべてのTerraform操作の前に必要です。planは変更を加えずにプレビューし、applyは変更を実行します。明示的なplanなしでapplyできますが、リスクがあります(planがインラインで実行される)。何も変更されません。Terraformはインフラが望ましい状態と一致していることを検出し、操作を実行しません。
terraform destroy -target=resource_type.name保存されたplanは、レビューした変更が正確に適用されることを保証します(planとapplyの間にdriftがない)。
手動で作成されたリソースをTerraform管理下に置くために
importを使用します。はい、stateを失った場合、Terraformが作成したリソースをimportできます。state rmはTerraformの追跡から削除しますが、リソースはそのまま残ります。destroyは実際のcloudリソースを削除します。
What's Next: Part 7 - Modules for Organization
Terraform workflowをマスターしましたが、コードベースが数百のリソースに成長したらどうなりますか?同じVPC設定を10のプロジェクトにコピーしますか?関連リソース間の依存関係を管理しますか?
Part 7では、これらの問題をTerraformモジュールで解決します - 再利用可能なコンポーネントで:
- コードの重複を排除
- 組織の標準を強制
- チームコラボレーションを可能に
- 複雑なインフラを簡素化
学ぶこと:
- ゼロからカスタムモジュールを作成
- Terraform Registryのパブリックモジュールを使用
- モジュール間で変数とoutputsを渡す
- 内部モジュールのバージョン管理と公開
- 大規模なTerraformプロジェクトの構造化
プレビュースニペット(Part 7):
# VPCコードを繰り返す代わりに...
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
name = "${var.project}-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
}
# モジュールのoutputsを参照
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnets[0]
}
Part 7でお会いしましょう。
Series Navigation
- Part 1: Why Infrastructure as Code?
- Part 2: Setting Up Terraform (Coming soon)
- Part 3: Your First Cloud Resource (Coming soon)
- Part 4: HCL Fundamentals (Coming soon)
- Part 5: Variables, Outputs & State (Coming soon)
- Part 6: Core Terraform Workflow (You are here)
- Part 7: Modules for Organization (Coming soon)
- Part 8: Multi-Cloud Patterns (Coming soon)
- Part 9: State Management & Team Workflows (Coming soon)
- Part 10: Testing & Validation (Coming soon)
- Part 11: Security & Secrets Management (Coming soon)
- Part 12: Production Patterns & DevSecOps (Coming soon)
References
- Terraform CLI Documentation
- Terraform State Command Reference
- Debugging Terraform
- Import Command Documentation
連載ナビゲーション:
- 第1回: Infrastructure as Codeとは?
- 第2回: Terraformのセットアップ
- 第3回: 初めてのクラウドリソース
- 第4回: HCL基礎
- 第5回: Variables, Outputs & State
- 第6回: Terraformの基本ワークフロー (今ここ)
- 第7回: モジュールで整理する (近日公開)
- 第8回: マルチクラウドパターン (近日公開)
- 第9回: State管理 & チームワークフロー (近日公開)
- 第10回: テスト & 検証 (近日公開)
- 第11回: セキュリティ & シークレット管理 (近日公開)
- 第12回: 本番環境パターン & DevSecOps (近日公開)
質問やフィードバックがありますか? 下にコメントを残すか、LinkedInでつながってください。