Terraform 基礎 — IaC 入門ハンズオン:S3・EC2をコードで構築するゼロからの実践ガイド

目次

Terraform 基礎 — IaC 入門ハンズオン:S3・EC2をコードで構築するゼロからの実践ガイド

公開日: 2026-04-13 / 難易度: 初級〜中級 / 所要時間: 約120分

この記事で学ぶこと
– IaC(Infrastructure as Code)と Terraform の基本概念・位置づけ
– HCL(HashiCorp Configuration Language)の基本構文(resource / variable / output など)
– S3 バケット・EC2 インスタンスを Terraform でコード化する手順(Step A〜B)
– 変数化(variables.tf / terraform.tfvars)と出力値(outputs.tf)の活用(Step C〜D)
– S3 + DynamoDB を使ったリモート State 管理の実装(Step E)

前提条件
– AWSアカウント(無料利用枠可)
– IAMユーザー(AdministratorAccess)とアクセスキーの作成
– Gitの基礎知識(git clone / git commit 程度)


目次


Section 1: 概念編 — IaC・Terraform アーキテクチャ・HCL 基本構文


1-1. IaC(Infrastructure as Code)とは

IaC(Infrastructure as Code) とは、サーバー・ネットワーク・データベースといったインフラ構成を、手作業のコンソール操作ではなく コード(設定ファイル)として記述・管理 するアプローチです。

手動構築 vs コード管理

観点手動構築(コンソール操作)IaC(コード管理)
再現性担当者によって手順がブレる同じコードから何度でも同一環境を再現できる
バージョン管理変更履歴が残らないGit で差分・履歴を追跡できる
チーム開発「誰が何を変えたか」不明PR レビューで変更を可視化・承認できる
スケール環境が増えると作業が線形に増大コードを再利用して複数環境に一括適用
ドリフト検知手動比較が必要terraform plan で差分を自動検出
障害復旧手順書ベースで時間がかかるコードを apply するだけで環境を再構築
学習コスト操作に慣れれば低いHCL / DSL の学習が必要
デバッグGUIで直感的に確認できるエラーメッセージの読み方を覚える必要がある

IaC がもたらす主なメリット

バージョン管理との統合

インフラの変更を git commit として記録できます。「いつ・誰が・なぜ変更したか」が一目でわかるため、障害発生時の原因特定が格段に速くなります。

git log --oneline
a3b1c2d feat: RDS インスタンスタイプを db.t3.medium に変更
b4c2d3e fix: セキュリティグループのポート 22 を削除
c5d3e4f chore: 本番環境用 VPC CIDR を更新

再現性・一貫性

開発・ステージング・本番で同一のコードを使うため、「開発では動いたのに本番で動かない」という環境差異を防げます。

チーム開発の民主化

インフラ変更もアプリコードと同様に Pull Request でレビューできます。インフラエンジニア以外もコードを読んで理解・提案できるようになります。


1-2. Terraform の位置づけ

数あるIaCツールの中で Terraform はどのような立ち位置にあるのでしょうか。主要ツールを比較します。

主要 IaC ツール比較

ツール記述言語マルチクラウド状態管理特徴
TerraformHCL(独自DSL)◎ 対応tfstate ファイルプロバイダーエコシステムが豊富。宣言的記述でシンプル
AWS CloudFormationYAML / JSON✗ AWS専用AWS 側で管理AWS ネイティブ。無料。AWS サービスへの対応が最速
AWS CDKTypeScript / Python 等△ 主にAWSCloudFormation 経由プログラミング言語で記述。ロジックを組み込みやすい
PulumiTypeScript / Python / Go 等◎ 対応Pulumi Service / S3 等既存プログラミング言語を使えるため学習コストが低い

Terraform が選ばれる理由

1. マルチクラウド対応

AWS・Azure・GCP を同一ツールで管理できます。クラウドを横断したインフラ管理が必要な企業で特に重宝されます。

2. 豊富なプロバイダーエコシステム

2026年時点で Terraform Registry には 4,000以上のプロバイダー が公開されています。AWS・Azure・GCP はもちろん、Datadog・GitHub・Snowflake など SaaS サービスもコードで管理できます。

3. 宣言的記述によるシンプルさ

「どのような状態にしたいか」を記述するだけで、Terraform が現状との差分を計算して必要な操作を実行します。「何をすべきか」(手続き的)ではなく「どうあるべきか」(宣言的)を書くだけでよい点がシンプルです。

4. 実績と安定性

HashiCorp が開発し、2014年のリリース以降 10年以上の実績を持ちます。エンタープライズ採用実績も豊富で、コミュニティも大きく情報が充実しています。

補足: OpenTofu について
2023年に HashiCorp が Terraform のライセンスを BSL に変更したため、OSS フォークとして OpenTofu が誕生しました。基本的な HCL 構文は Terraform と互換性があります。本記事では Terraform(HashiCorp 版)を対象とします。


1-3. Terraform のアーキテクチャ

Terraform を理解する上で核心となる 5つのコア概念 を押さえましょう。

5つのコア概念

┌─────────────────────────────────────────────────────────────────┐
│Terraform Core│
│  │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐  │
│  │ Provider │ │ Resource │ │  State│ │  Plan /  │  │
│  │ │ │ │ │ (tfstate)│ │  Apply│  │
│  │ AWS・  │ │ S3 Bucket│ │ 現状記録  │ │ 差分計算 │  │
│  │ Azure等  │ │ EC2等 │ │ │ │ & 実行│  │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘  │
└─────────────────────────────────────────────────────────────────┘
 │  │
 ▼  ▼
┌─────────────┐  ┌──────────────┐
│ AWS API  │  │  terraform│
│ Azure API│  │  .tfstate │
│ GCP API 等  │  │  (S3等に保存) │
└─────────────┘  └──────────────┘

Provider(プロバイダー)

クラウドプロバイダーや SaaS との通信を担うプラグインです。hashicorp/aws プロバイダーは AWS API を呼び出すための認証・エンドポイント情報を管理します。

provider "aws" {
  region = "ap-northeast-1"
}

Resource(リソース)

Terraform で管理する実際のインフラリソースです。resource "リソースタイプ" "ローカル名" の形式で定義します。

resource "aws_s3_bucket" "handson" {
  bucket = "terraform-handson-bucket-YOURNAME"
}

State(ステート)

現在のインフラ状態を記録する JSON ファイル(terraform.tfstate)です。Terraform は State を参照して「現状と定義の差分」を計算します。

  • ローカル保存: デフォルトで terraform.tfstate ファイルに保存
  • リモート保存: S3 + DynamoDB(チーム開発推奨)

State ファイルの取り扱い注意点
tfstate ファイルにはリソースの詳細情報(場合によっては認証情報)が含まれます。.gitignore に追加して Git リポジトリに含めないようにしましょう。

Plan(プラン)

terraform plan を実行すると、現在の State と .tf ファイルの差分を計算し、「何を作成・変更・削除するか」をプレビュー表示します。実際のリソース変更は行わない dry-run です。

Plan: 1 to add, 0 to change, 0 to destroy.
  + aws_s3_bucket.handson
bucket: "terraform-handson-bucket-YOURNAME"

Apply(アプライ)

terraform apply を実行すると、Plan の内容を実際に AWS API 経由で適用します。リソースの作成・変更・削除が実行されます。

Terraform ワークフロー

[初回セットアップ]
  terraform init
│
▼(プロバイダーのダウンロード・初期化)
  terraform plan
│
▼(差分のプレビュー確認)
  terraform apply
│
▼(リソース作成・State 更新)

[変更時]
  .tf ファイルを編集
│
▼
  terraform plan  ← 差分確認(必須)
│
▼
  terraform apply ← 変更適用

[リソース削除時]
  terraform destroy  ← 全リソース削除(注意!)
コマンド役割実際の変更
terraform initプロバイダーDL・バックエンド初期化なし
terraform plan差分計算・プレビューなし(dry-run)
terraform applyプランを実行してリソース作成/変更/削除あり
terraform destroy管理下の全リソースを削除あり(破壊的)
terraform fmtHCL コードを自動整形なし
terraform validate構文・設定の検証なし
terraform outputoutput 変数の値を表示なし

1-4. HCL 基本構文

Terraform の設定ファイルは HCL(HashiCorp Configuration Language) で記述します。JSON に似た読みやすい構文です。

terraform ブロック

Terraform 本体のバージョン要件とプロバイダーの依存を宣言します。

terraform {
  required_version = ">= 1.7.0"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

2026年時点の最新バージョン
– Terraform: 1.14.8(2026年3月25日リリース)
– AWS Provider: 6.40.0(2026年4月時点)

本ハンズオンでは required_version = ">= 1.7.0" と指定し、Terraform 1.7以降で動作確認しています。本番環境では = 1.14.8 のように固定バージョンを推奨します。

provider ブロック

接続先クラウドの設定(リージョン・認証情報等)を記述します。

provider "aws" {
  region = "ap-northeast-1"
}

複数リージョンを使う場合は alias で区別できます。

provider "aws" {
  alias  = "tokyo"
  region = "ap-northeast-1"
}

provider "aws" {
  alias  = "virginia"
  region = "us-east-1"
}

resource ブロック

管理対象リソースを定義します。書式は resource "リソースタイプ" "ローカル名" です。

resource "aws_s3_bucket" "handson" {
  bucket = "terraform-handson-bucket-YOURNAME"
  tags = {
 Name  = "terraform-handson"
 Environment = "dev"
  }
}
  • aws_s3_bucket: リソースタイプ(プロバイダー名_リソース名)
  • handson: Terraform 内でこのリソースを参照するためのローカル名
  • 他のリソースから aws_s3_bucket.handson.id のように参照できます

variable ブロック

入力変数を定義します。環境ごとに値を変えたい場合に使います。

variable "bucket_name" {
  type  = string
  description = "S3バケット名"
  default  = "terraform-handson-bucket"
}

variable "environment" {
  type  = string
  description = "環境名(dev/stg/prod)"
  # default なし → 実行時に必須入力
}

variable "instance_count" {
  type = number
  default = 1
}

変数の参照には var.変数名 を使います。

resource "aws_s3_bucket" "handson" {
  bucket = var.bucket_name
}

変数への値渡し方法:
terraform.tfvars ファイルに記述
-var "bucket_name=my-bucket" オプション
– 環境変数 TF_VAR_bucket_name=my-bucket

output ブロック

terraform apply 後に出力したい値を定義します。他のモジュールへの値渡しにも使います。

output "bucket_id" {
  value = aws_s3_bucket.handson.id
  description = "作成したS3バケットのID"
}

output "bucket_arn" {
  value = aws_s3_bucket.handson.arn
  description = "作成したS3バケットのARN"
  sensitive= false
}

実行後に terraform output bucket_id で個別に参照できます。

data ブロック

既に存在するリソース(Terraform 管理外含む)の情報を参照します。

# 既存の VPC を名前で検索
data "aws_vpc" "existing" {
  filter {
 name= "tag:Name"
 values = ["production-vpc"]
  }
}

# 参照は data.リソースタイプ.ローカル名.属性
resource "aws_subnet" "app" {
  vpc_id  = data.aws_vpc.existing.id
  cidr_block = "10.0.1.0/24"
}

data ブロックは apply 時にリソースを作成しません。既存インフラの情報を読み込むだけです。

locals ブロック

モジュール内でのみ使う内部変数(ローカル変数)を定義します。繰り返し使う値や計算式を一か所にまとめられます。

locals {
  project  = "terraform-handson"
  environment = "dev"
  common_tags = {
 Project  = local.project
 Environment = local.environment
 ManagedBy= "Terraform"
  }
}

resource "aws_s3_bucket" "handson" {
  bucket = "${local.project}-bucket"
  tags= local.common_tags
}

local.変数名 で参照します(var. との違いに注意)。

HCL 構文まとめ

ブロック用途参照方法
terraformTerraform 本体・プロバイダーのバージョン宣言
providerクラウド接続設定
resourceリソース定義(作成・管理)リソースタイプ.ローカル名.属性
variable入力変数(外部から値を渡す)var.変数名
output出力変数(値を外部に公開)terraform output 変数名
data既存リソースの参照(読み取り専用)data.リソースタイプ.ローカル名.属性
locals内部変数(モジュール内のみ)local.変数名

1-5. Terraform のバージョン管理

チーム開発では Terraform のバージョンを揃えること が非常に重要です。バージョンによって構文や動作が変わる場合があるためです。

required_version の指定方法

terraform {
  # 完全一致(厳格)
  required_version = "= 1.14.8"

  # 最小バージョン(1.7.0以上)
  required_version = ">= 1.7.0"

  # 悲観的制約(1.xシリーズ内の最新を許容)
  required_version = "~> 1.7"

  # 範囲指定
  required_version = ">= 1.7.0, < 2.0.0"
}
演算子意味
=完全一致= 1.14.8 → 1.14.8 のみ
>=以上>= 1.7.0 → 1.7.0 以上すべて
~>悲観的制約(マイナーまで固定)~> 1.7 → 1.7.x のみ(2.0 は不可)
~>悲観的制約(パッチまで固定)~> 1.7.0 → 1.7.0〜1.7.x のみ

チーム開発では = 1.14.8 のように完全一致指定が最も安全です。CI/CD パイプラインでもバージョンを固定することを推奨します。

tfenv によるバージョン管理

tfenv は Terraform のバージョン管理ツールです。Node.js の nvm に相当します。

# インストール(macOS)
brew install tfenv

# 利用可能なバージョン一覧
tfenv list-remote

# 特定バージョンをインストール
tfenv install 1.14.8

# バージョンを切り替え
tfenv use 1.14.8

# 現在のバージョンを確認
terraform version

プロジェクトルートに .terraform-version ファイルを置くと、そのディレクトリでのバージョンが固定されます。

# .terraform-version
1.14.8

asdf でも Terraform を管理できます。

# asdf で Terraform プラグイン追加
asdf plugin add terraform

# バージョンインストール
asdf install terraform 1.14.8

# プロジェクト固定
asdf local terraform 1.14.8

terraform.lock.hcl の役割

terraform init を実行すると、プロジェクトルートに terraform.lock.hcl が生成されます。

# terraform.lock.hcl(自動生成)
provider "registry.terraform.io/hashicorp/aws" {
  version  = "5.94.0"
  constraints = "~> 5.0"
  hashes = [
 "h1:xxxx...",
 "zh:yyyy...",
  ]
}

lock ファイルの役割

  • プロバイダーの バージョンとハッシュを固定 する
  • チームメンバー全員が同一バージョンのプロバイダーを使えるよう保証する
  • セキュリティ面でもプロバイダーの改ざんを防止できる

重要: terraform.lock.hclGit にコミットしてくださいterraform.tfstate とは逆)。

# .gitignore(推奨設定)
*.tfstate
*.tfstate.backup
.terraform/ # プロバイダーバイナリ(再取得可能)

# コミットすべきファイル
# terraform.lock.hcl  ← コメントアウト不要(コミット対象)

バージョン管理のベストプラクティスまとめ

プロジェクト
├── .terraform-version← tfenv/asdf 用バージョン固定
├── terraform.lock.hcl← Git にコミット(プロバイダー固定)
├── .gitignore  ← .terraform/ と *.tfstate を除外
└── versions.tf ← required_version を専用ファイルに分離
# versions.tf(バージョン宣言を専用ファイルに分離する慣習)
terraform {
  required_version = ">= 1.7.0"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

Section 1 まとめ

テーマポイント
IaCインフラをコードで管理。再現性・バージョン管理・チーム開発が向上
Terraform の位置づけマルチクラウド対応・豊富なプロバイダー・宣言的HCL
5つのコア概念Provider / Resource / State / Plan / Apply
ワークフローinit → plan → apply → (変更時 plan→apply) → destroy
HCL ブロックterraform / provider / resource / variable / output / data / locals
バージョン管理required_version・tfenv・terraform.lock.hcl のセット管理

次の Section 2 では、Terraform の内部アーキテクチャとワークフローを図解で解説します。


Section 2: アーキテクチャ解説

Terraform を使った AWS インフラ構築を始める前に、その内部構造と動作原理を理解しておくことが重要です。このセクションでは、Terraform のワークフロー全体像・State 管理の仕組み・Provider と Resource の関係という3つの観点からアーキテクチャを解説します。


2-1. Terraform ワークフロー全体図

Terraform ワークフロー全体図

Terraform の基本的な作業フローは、init → plan → apply という3ステップで構成されています。destroy はリソース削除時にのみ実行する逆方向のオペレーションです。

init フェーズ

terraform init

terraform init は、作業ディレクトリを初期化するコマンドです。具体的には以下の処理を行います。

  • required_providers ブロックに記述されたプロバイダプラグインのダウンロード(.terraform/ ディレクトリに保存)
  • バックエンド(State 保存先)の初期化
  • モジュールの取得

.terraform/ ディレクトリにはプロバイダバイナリが保存されます。このディレクトリは .gitignore に追加し、バージョン管理から除外してください。

plan フェーズ

terraform plan

terraform plan は、現在の .tf ファイルと既存の State を比較し、実行計画(差分) を出力します。実際のリソース変更は行いません。

出力例:

Plan: 3 to add, 0 to change, 0 to destroy.

このフェーズでは +(追加)・~(変更)・-(削除)の記号で変更内容が表示されます。本番環境への適用前に必ず確認しましょう。

apply フェーズ

terraform apply

terraform apply は、plan の内容を実際に AWS に対して実行します。デフォルトでは確認プロンプトが表示されます(-auto-approve オプションで省略可能ですが、CI 以外では非推奨)。

apply 完了後、terraform.tfstate ファイルに現在のリソース状態が記録されます。

destroy フェーズ

terraform destroy

terraform destroy は、管理下のリソースをすべて削除します。apply の逆操作です。コスト削減や開発環境のクリーンアップに使用します。

注意: destroy は元に戻せません。必ず terraform plan -destroy で事前確認してください。


2-2. State 管理の仕組み

State 管理の仕組み

State とは

Terraform の State(状態ファイル) は、Terraform が管理するリソースの現在の状態を JSON 形式で記録したファイルです。デフォルトでは terraform.tfstate という名前でローカルに保存されます。

{
  "version": 4,
  "terraform_version": "1.7.0",
  "resources": [
 {
"type": "aws_s3_bucket",
"name": "my-bucket",
"instances": [...]
 }
  ]
}

ローカル State の問題点

チーム開発においてローカル State は以下の問題を引き起こします。

問題詳細
競合複数の開発者が同時に apply を実行すると State が上書きされる
不整合ローカルの State が最新でない場合、誤った差分が計算される
共有困難State ファイルを Git にコミットするとシークレット情報が漏洩するリスクがある

リモート State の仕組み(S3 + DynamoDB)

チーム開発では リモートバックエンド を使用して State を共有します。AWS では S3 + DynamoDB の組み合わせが標準的です。

terraform {
  backend "s3" {
 bucket= "my-terraform-state"
 key= "prod/terraform.tfstate"
 region= "ap-northeast-1"
 dynamodb_table = "terraform-lock"
 encrypt  = true
  }
}

S3 の役割: State ファイルの永続保存とバージョニング(誤操作からの復旧)

DynamoDB の役割: State ロック(同時実行の防止)

DynamoDB によるロック機構により、あるユーザーが terraform apply を実行中は他のユーザーの apply がブロックされます。これにより State の競合を防ぎます。

Lock Info:
  ID:  a1b2c3d4-...
  Path:my-terraform-state/prod/terraform.tfstate
  Operation: OperationTypeApply
  Who: user@machine
  Created:2026-04-13T10:00:00Z

State ファイルの取り扱い注意事項

  • .gitignore に必ず追加: パスワードや秘密鍵が平文で含まれることがある
  • terraform.tfstate.backup: apply 前の State が自動バックアップされる
  • 直接編集禁止: terraform state コマンドを使用すること

2-3. Provider と Resource の関係

Provider と Resource の関係

Provider とは

Provider は、Terraform と外部サービス(AWS・GCP・Azure など)の間を仲介するプラグインです。AWS Provider(hashicorp/aws)は、AWS の各種サービスに対する CRUD 操作を Terraform の宣言的構文で利用できるようにします。

terraform {
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

Provider は Terraform Registry で公開されており、terraform init 時に自動ダウンロードされます。

Resource とは

Resource は、Terraform で管理するインフラの最小単位です。resource ブロックで定義します。

resource "aws_s3_bucket" "my-bucket" {
  bucket = "my-unique-bucket-name"

  tags = {
 Environment = "production"
  }
}

resource "aws_instance" "web-server" {
  ami  = "ami-0c3fd0f5d33134a76"
  instance_type = "t3.micro"
}

resource "aws_security_group" "allow-http" {
  name  = "allow-http"
  description = "Allow HTTP inbound traffic"

  ingress {
 from_port= 80
 to_port  = 80
 protocol = "tcp"
 cidr_blocks = ["0.0.0.0/0"]
  }
}

Provider → Resource → AWS の関係フロー

Terraform 設定 (.tf)
 │
 ├── Provider (hashicorp/aws)  ← API認証・エンドポイント設定
 │ │
 │ └── AWS API を抽象化
 │
 └── Resource 定義
│
├── aws_s3_bucket →  S3 バケット (AWS)
├── aws_instance  →  EC2 インスタンス (AWS)
└── aws_security_group → Security Group (AWS)

Provider が AWS API の認証情報(リージョン・クレデンシャル)を管理し、各 Resource がそのコンテキストで AWS に対して API コールを行います。

Resource 間の依存関係

Resource 間の依存関係は depends_on で明示するか、属性参照(aws_instance.web-server.id など)で暗黙的に解決されます。

resource "aws_instance" "web-server" {
  ami = "ami-0c3fd0f5d33134a76"
  instance_type= "t3.micro"
  security_groups = [aws_security_group.allow-http.name]  # 暗黙的依存
}

Terraform は依存グラフを自動解析し、依存関係のないリソースは並列で作成します。これにより大規模インフラの構築時間を短縮できます。


Section 2 まとめ

概念役割
ワークフローinit → plan → apply → (destroy) の4フェーズ
Stateリソースの現在状態を記録。チーム開発では S3+DynamoDB でリモート管理
ProviderAWS API を抽象化するプラグイン。hashicorp/aws が AWS 操作の基盤
Resource管理する AWS リソースの宣言(S3・EC2・SG など)

次のセクションでは、実際に AWS マネジメントコンソールを使って Terraform の実行環境をセットアップし、最初のリソースをデプロイします。


Section 3: ハンズオン — ゼロからTerraformで AWS 環境を構築する

所要時間の目安: 約 90〜120 分
前提条件: AWSアカウント(無料利用枠可)、ターミナル操作の基礎知識

このセクションでは、Step A〜E の 5 段階で Terraform の基本操作を体験します。
最初はシンプルな S3 バケット作成から始まり、最後はリモート State 管理まで、実務で必要なスキルを一通り習得できます。


3-1. 環境準備

Terraform のインストール

Terraform は HashiCorp が提供する IaC ツールです。2026 年 4 月時点の最新安定版は v1.14.8(2026-03-25 リリース)です。

Mac(Homebrew)
# HashiCorp の tap を追加して Terraform をインストール
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# すでにインストール済みの場合はアップグレード
brew upgrade hashicorp/tap/terraform
Windows(Chocolatey)

管理者権限で PowerShell を起動し、以下を実行します。

choco install terraform

Chocolatey が未インストールの場合: https://chocolatey.org/install の手順に従ってインストールしてください。

Linux(Ubuntu / Debian)
# 必要パッケージのインストール
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common

# HashiCorp の GPG キーを追加
wget -O- https://apt.releases.hashicorp.com/gpg | \
  gpg --dearmor | \
  sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

# HashiCorp のリポジトリを追加
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list

# リポジトリを更新して Terraform をインストール
sudo apt-get update
sudo apt-get install terraform

バージョン確認

インストール後、以下のコマンドでバージョンを確認します。

terraform version

正常にインストールされていれば、以下のような出力が得られます。

Terraform v1.14.8
on darwin_arm64

注意: バージョンが >= 1.7.0 であれば、このハンズオンの全手順を実行できます。


AWS CLI の設定

Terraform が AWS リソースを操作するために、AWS CLI の設定が必要です。

IAM ユーザーの作成
  1. AWS マネジメントコンソール にログイン
  2. IAMユーザーユーザーを作成 をクリック
  3. ユーザー名を入力(例: terraform-handson
  4. 次のステップ: 許可の設定ポリシーを直接アタッチする を選択
  5. AdministratorAccess を選択して ユーザーの作成
  6. セキュリティ認証情報 タブ → アクセスキーを作成
  7. コマンドラインインターフェイス (CLI) を選択してアクセスキーを作成
  8. アクセスキー IDシークレットアクセスキー をメモ(後で使用)
[スクリーンショット: IAM ユーザー作成完了画面、アクセスキーの表示]

本番環境では: AdministratorAccess ではなく最小権限の IAM ポリシーを使用してください。ハンズオン目的で一時的に利用し、終了後はアクセスキーを削除することを推奨します。

aws configure の実行
aws configure

対話形式でアクセス情報を入力します。

AWS Access Key ID [None]: AKIA...(先ほどメモしたアクセスキー ID)
AWS Secret Access Key [None]: xxxxxxxx(シークレットアクセスキー)
Default region name [None]: ap-northeast-1
Default output format [None]: json

設定が正しいか確認します。

aws sts get-caller-identity

以下のように自分のアカウント情報が表示されれば成功です。

{
 "UserId": "AIDA...",
 "Account": "123456789012",
 "Arn": "arn:aws:iam::123456789012:user/terraform-handson"
}

作業ディレクトリの構成

このハンズオンでは、以下のディレクトリ構成で作業を進めます。

~/terraform-handson/
├── step-a/ # Step A: S3バケット作成
│└── main.tf
├── step-b/ # Step B: EC2インスタンス作成
│├── main.tf
│└── ec2.tf
├── step-c/ # Step C: 変数化
│├── main.tf
│├── variables.tf
│└── terraform.tfvars
├── step-d/ # Step D: Output
│├── main.tf
│├── variables.tf
│├── terraform.tfvars
│└── outputs.tf
├── step-e/ # Step E: リモートState設定
│├── main.tf
│├── variables.tf
│├── terraform.tfvars
│├── outputs.tf
│└── backend.tf
└── state-backend/# Step E: State管理用リソース
 └── main.tf

作業ディレクトリを作成します。

mkdir -p ~/terraform-handson/{step-a,step-b,step-c,step-d,step-e,state-backend}

3-2. Step A: Hello Terraform — S3 バケット作成

最初のステップとして、Terraform の基本的なワークフロー(initplanapplydestroy)を S3 バケットの作成を通じて体験します。

cd ~/terraform-handson/step-a

main.tf の作成

以下の内容で main.tf を作成します。

terraform {
  required_version = ">= 1.7.0"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_s3_bucket" "handson" {
  bucket = "terraform-handson-bucket-YOURNAME"
  tags = {
 Name  = "terraform-handson"
 Environment = "dev"
  }
}

重要: terraform-handson-bucket-YOURNAMEYOURNAME 部分を自分のユニークな文字列(例: 名前 + 日付)に変更してください。S3 バケット名はグローバルで一意である必要があります。


terraform init

プロバイダープラグインをダウンロードし、作業ディレクトリを初期化します。

terraform init

初期化が成功すると、以下のような出力が表示されます。

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.94.1...
- Installed hashicorp/aws v5.94.1 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your configuration. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

初期化後、ディレクトリには以下のファイルが追加されます。

step-a/
├── main.tf
├── .terraform/  # プロバイダーバイナリが格納される
│└── providers/
│ └── registry.terraform.io/
│  └── hashicorp/
│└── aws/
└── .terraform.lock.hcl  # プロバイダーバージョンのロックファイル

terraform plan

実際にリソースを作成する前に、変更計画を確認します。

terraform plan

以下のような出力が表示されます。

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_s3_bucket.handson will be created
  + resource "aws_s3_bucket" "handson" {
+ acceleration_status= (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "terraform-handson-bucket-YOURNAME"
+ bucket_domain_name = (known after apply)
+ bucket_prefix= (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy= false
+ hosted_zone_id  = (known after apply)
+ id  = (known after apply)
+ object_lock_enabled= (known after apply)
+ region = (known after apply)
+ request_payer= (known after apply)
+ tags= {
 + "Environment" = "dev"
 + "Name"  = "terraform-handson"
  }
+ tags_all  = {
 + "Environment" = "dev"
 + "Name"  = "terraform-handson"
  }
+ website_domain  = (known after apply)
+ website_endpoint= (known after apply)
 }

Plan: 1 to add, 0 to change, 0 to destroy.

──────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to perform exactly these actions if you run "terraform apply" now.

Plan: 1 to add と表示され、S3 バケット 1 つが追加されることが確認できます。


terraform apply

計画を実際に適用して、AWS リソースを作成します。

terraform apply

plan と同じ内容が表示された後、確認プロンプトが表示されます。

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 

yes と入力して Enter を押します。

aws_s3_bucket.handson: Creating...
aws_s3_bucket.handson: Creation complete after 2s [id=terraform-handson-bucket-YOURNAME]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

AWS コンソールで確認

  1. AWS S3 コンソール を開く
  2. 東京リージョン(ap-northeast-1)を選択
  3. terraform-handson-bucket-YOURNAME が作成されていることを確認
[スクリーンショット: S3 コンソールにバケットが表示されている画面]

作成されたバケットをクリックすると、プロパティ タブで terraform-handson タグが付与されていることも確認できます。


terraform destroy

ハンズオン終了後のリソース削除を体験します。Step B 以降でこのリソースは使用しないため、ここで削除します。

terraform destroy
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_s3_bucket.handson will be destroyed
  - resource "aws_s3_bucket" "handson" {
- arn = "arn:aws:s3:::terraform-handson-bucket-YOURNAME" -> null
- bucket = "terraform-handson-bucket-YOURNAME" -> 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, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: 

yes と入力します。

aws_s3_bucket.handson: Destroying... [id=terraform-handson-bucket-YOURNAME]
aws_s3_bucket.handson: Destruction complete after 1s

Destroy complete! Resources: 1 destroyed.

Step A のポイント:
terraform init でプロバイダーをダウンロード(プロジェクト初回のみ)
terraform plan で変更内容を事前確認(常に実行する習慣をつける)
terraform apply でリソースを作成(yes の確認が必要)
terraform destroy でリソースを削除(本番環境では慎重に)


3-3. Step B: EC2 インスタンス作成

Step B では、セキュリティグループ・キーペア・EC2 インスタンスの 3 つのリソースを作成し、リソース間の依存関係を学びます。

cd ~/terraform-handson/step-b

ファイル構成

Step B では 2 つのファイルを使用します。

step-b/
├── main.tf# terraform ブロック + provider ブロック(Step A と同じ)
└── ec2.tf # セキュリティグループ・キーペア・EC2リソース

main.tf の作成

Step A と同じ内容の main.tf を作成します。

terraform {
  required_version = ">= 1.7.0"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_s3_bucket" "handson" {
  bucket = "terraform-handson-bucket-YOURNAME"
  tags = {
 Name  = "terraform-handson"
 Environment = "dev"
  }
}

ec2.tf の作成

以下の内容で ec2.tf を作成します。

resource "aws_security_group" "handson_sg" {
  name  = "terraform-handson-sg"
  description = "Allow SSH"

  ingress {
 from_port= 22
 to_port  = 22
 protocol = "tcp"
 cidr_blocks = ["YOUR_IP/32"]
  }

  egress {
 from_port= 0
 to_port  = 0
 protocol = "-1"
 cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_key_pair" "handson" {
  key_name= "terraform-handson-key"
  public_key = file("~/.ssh/id_rsa.pub")
}

resource "aws_instance" "handson_ec2" {
  ami  = "ami-0b5c74e235ed808b9"
  instance_type = "t3.micro"
  key_name= aws_key_pair.handson.key_name
  vpc_security_group_ids = [aws_security_group.handson_sg.id]

  tags = {
 Name = "terraform-handson-ec2"
  }
}

YOUR_IP の確認: curl ifconfig.me または https://checkip.amazonaws.com でグローバル IP アドレスを確認し、YOUR_IP/32 に設定してください。
セキュリティのため、SSH のインバウンドルールは自分の IP のみに制限することを強く推奨します。

AMI ID について: ami-0b5c74e235ed808b9 は ap-northeast-1 の Amazon Linux 2023 AMI です。最新の AMI ID は AWS コンソールの EC2 → AMI カタログ で “Amazon Linux 2023” を検索するか、以下の AWS CLI コマンドで確認してください。
bash
aws ssm get-parameter \
--name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--region ap-northeast-1 \
--query "Parameter.Value" \
--output text

SSH キーペアの準備: ~/.ssh/id_rsa.pub が存在しない場合は、以下のコマンドで作成してください。
bash
ssh-keygen -t rsa -b 4096 -C "terraform-handson"


リソース間の依存関係

ec2.tfaws_instance リソースを注目してください。

key_name= aws_key_pair.handson.key_name
vpc_security_group_ids = [aws_security_group.handson_sg.id]

aws_key_pair.handson.key_nameaws_security_group.handson_sg.id という参照を使用しています。
Terraform はこの参照を解析して、暗黙的な依存関係(Implicit Dependency) を自動的に検出します。

つまり、EC2 インスタンスを作成する前に必ずキーペアとセキュリティグループが作成されます。
Terraform が依存関係グラフ(Dependency Graph)を構築し、正しい順序でリソースを作成・削除します。


terraform init & plan

terraform init
terraform plan

plan では 4 つのリソース(S3 バケット + セキュリティグループ + キーペア + EC2)が追加対象として表示されます。

Plan: 4 to add, 0 to change, 0 to destroy.

terraform apply

terraform apply

yes と入力して適用します。

aws_key_pair.handson: Creating...
aws_security_group.handson_sg: Creating...
aws_s3_bucket.handson: Creating...
aws_key_pair.handson: Creation complete after 0s [id=terraform-handson-key]
aws_s3_bucket.handson: Creation complete after 2s [id=terraform-handson-bucket-YOURNAME]
aws_security_group.handson_sg: Creation complete after 2s [id=sg-0xxxxxxxxxxxxxxx]
aws_instance.handson_ec2: Creating...
aws_instance.handson_ec2: Still creating... [10s elapsed]
aws_instance.handson_ec2: Still creating... [20s elapsed]
aws_instance.handson_ec2: Creation complete after 22s [id=i-0xxxxxxxxxxxxxxxx]

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

S3 バケットとキーペア・セキュリティグループは並列に作成され、EC2 はそれらの完了後に作成されることが分かります。


SSH 接続確認

EC2 の パブリック IP アドレスを確認します。

terraform show | grep public_ip

または、AWS コンソールの EC2インスタンス から確認します。

[スクリーンショット: EC2 インスタンス一覧、terraform-handson-ec2 が running 状態]

SSH 接続を試みます。

ssh -i ~/.ssh/id_rsa ec2-user@<パブリックIP>
,  #_
~\_  ####_  Amazon Linux 2023
  ~~  \_#####\
  ~~  \###|
  ~~ \#/ ___https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
 ~~~/
~~._._/
_/ _/
 _/m/'
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$

接続できたら、exit で切断します。

接続できない場合: セキュリティグループの cidr_blocks に設定した IP アドレスが正しいか確認してください。

Step C に進む前に、リソースを削除してコストを抑えます。

terraform destroy

3-4. Step C: 変数化

Step C では、ハードコードされた値を 変数(Variables) に置き換え、環境ごとに異なる設定を柔軟に管理できるようにします。

cd ~/terraform-handson/step-c

ファイル構成

step-c/
├── main.tf# S3バケット・EC2リソース(var.xxx 参照に更新)
├── variables.tf # 変数定義
└── terraform.tfvars# 変数の値

variables.tf の作成

variable "aws_region" {
  description = "AWSリージョン"
  type  = string
  default  = "ap-northeast-1"
}

variable "environment" {
  description = "環境名 (dev/stg/prod)"
  type  = string
  default  = "dev"
}

variable "bucket_name" {
  description = "S3バケット名(グローバル一意)"
  type  = string
}

variable "instance_type" {
  description = "EC2インスタンスタイプ"
  type  = string
  default  = "t3.micro"
}

bucket_name には default がありません。これは必須変数(Required Variable)であり、terraform.tfvars や CLI オプションで必ず値を指定する必要があります。


terraform.tfvars の作成

bucket_name= "terraform-handson-bucket-YOURNAME"
environment= "dev"
instance_type = "t3.micro"

YOURNAME を自分の識別子に変更してください。

aws_regionvariables.tfdefault 値(ap-northeast-1)が使われるため、tfvars への記載は省略しています。


main.tf の作成(変数参照版)

Step B の main.tf + ec2.tf を 1 ファイルにまとめ、変数を参照するように更新します。

terraform {
  required_version = ">= 1.7.0"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

provider "aws" {
  region = var.aws_region
}

resource "aws_s3_bucket" "handson" {
  bucket = var.bucket_name
  tags = {
 Name  = "terraform-handson"
 Environment = var.environment
  }
}

resource "aws_security_group" "handson_sg" {
  name  = "terraform-handson-sg"
  description = "Allow SSH"

  ingress {
 from_port= 22
 to_port  = 22
 protocol = "tcp"
 cidr_blocks = ["YOUR_IP/32"]
  }

  egress {
 from_port= 0
 to_port  = 0
 protocol = "-1"
 cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_key_pair" "handson" {
  key_name= "terraform-handson-key"
  public_key = file("~/.ssh/id_rsa.pub")
}

resource "aws_instance" "handson_ec2" {
  ami  = "ami-0b5c74e235ed808b9"
  instance_type = var.instance_type
  key_name= aws_key_pair.handson.key_name
  vpc_security_group_ids = [aws_security_group.handson_sg.id]

  tags = {
 Name  = "terraform-handson-ec2"
 Environment = var.environment
  }
}

変数の優先順位

Terraform の変数は、以下の優先順位で値が決定されます(上位が優先)。

優先順位指定方法
1-var CLI オプションterraform apply -var="environment=prod"
2-var-file CLI オプションterraform apply -var-file="prod.tfvars"
3*.auto.tfvars / terraform.tfvarsterraform.tfvars に記述
4環境変数 TF_VAR_xxxexport TF_VAR_environment=prod
5defaultvariables.tfdefault

dev / prod 環境分離の実践

terraform.tfvars を環境ごとに分けることで、dev/prod 環境の設定を管理できます。

step-c/
├── main.tf
├── variables.tf
├── dev.tfvars  # 開発環境の変数値
└── prod.tfvars # 本番環境の変数値

dev.tfvars

bucket_name= "terraform-handson-bucket-YOURNAME-dev"
environment= "dev"
instance_type = "t3.micro"

prod.tfvars

bucket_name= "terraform-handson-bucket-YOURNAME-prod"
environment= "prod"
instance_type = "t3.small"

環境を切り替えてデプロイします。

# 開発環境
terraform apply -var-file="dev.tfvars"

# 本番環境
terraform apply -var-file="prod.tfvars"

terraform plan で変数の確認

terraform init
terraform plan

plan 出力で var.bucket_nameterraform.tfvars の値に置き換えられていることを確認できます。

  + resource "aws_s3_bucket" "handson" {
+ bucket = "terraform-handson-bucket-YOURNAME"
...
+ tags= {
 + "Environment" = "dev"
 + "Name"  = "terraform-handson"
  }
 }

確認できたら terraform apply を実行してください。Step D に進む際にはリソースをそのまま残しておきます(destroy は不要です)。


3-5. Step D: Output

Step D では、terraform apply 後に重要な情報(バケット名・EC2 の IP など)を Output(出力値) として定義し、簡単に参照できるようにします。

cd ~/terraform-handson/step-d

ファイル構成

Step C のファイルをコピーして outputs.tf を追加します。

step-d/
├── main.tf# Step C と同じ
├── variables.tf # Step C と同じ
├── terraform.tfvars# Step C と同じ
└── outputs.tf# 出力値の定義(新規)

outputs.tf の作成

output "s3_bucket_name" {
  description = "S3バケット名"
  value = aws_s3_bucket.handson.id
}

output "s3_bucket_arn" {
  description = "S3バケットARN"
  value = aws_s3_bucket.handson.arn
}

output "ec2_public_ip" {
  description = "EC2パブリックIP"
  value = aws_instance.handson_ec2.public_ip
}

output "ec2_instance_id" {
  description = "EC2インスタンスID"
  value = aws_instance.handson_ec2.id
}

terraform apply

terraform init
terraform apply

apply の最後に Output の値が表示されます。

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

ec2_instance_id = "i-0xxxxxxxxxxxxxxxx"
ec2_public_ip= "54.xxx.xxx.xxx"
s3_bucket_arn= "arn:aws:s3:::terraform-handson-bucket-YOURNAME"
s3_bucket_name  = "terraform-handson-bucket-YOURNAME"

terraform output コマンド

apply 後いつでも Output を確認できます。

# 全 Output を表示
terraform output

# 特定の Output のみ表示
terraform output ec2_public_ip

# JSON 形式で出力(スクリプト連携に便利)
terraform output -json
# ec2_public_ip のみ表示した場合
$ terraform output ec2_public_ip
"54.xxx.xxx.xxx"

# -raw オプションで引用符なしの値を取得
$ terraform output -raw ec2_public_ip
54.xxx.xxx.xxx

-raw オプションの活用例: SSH 接続をワンライナーで実行できます。

ssh -i ~/.ssh/id_rsa ec2-user@$(terraform output -raw ec2_public_ip)

Output の活用シーン

Output は単なる確認機能ではなく、モジュール間のデータ連携にも使われます。
例えば、ネットワークモジュールが出力した VPC ID を、EC2 モジュールが参照するといったパターンです。

# モジュール参照の例(モジュールはSection 4で詳しく解説)
module "network" {
  source = "./modules/network"
}

resource "aws_instance" "app" {
  subnet_id = module.network.public_subnet_id  # モジュールのOutput を参照
  ...
}

Step D のリソースはそのまま残して Step E に進みます。


3-6. Step E: リモート State 設定

Step E では、ローカルに保存されていた State ファイルを S3 + DynamoDB に移行し、チームでの Terraform 運用に必要なリモート State 管理を実装します。

なぜリモート State が必要なのか?

ローカル State(terraform.tfstate)には以下の問題があります。

問題内容
チーム共有不可各自の PC に State があると、他のメンバーの変更が反映されない
競合リスク複数人が同時に terraform apply すると State が壊れる
消失リスクPC 障害や誤削除で State ファイルが失われる
機密情報State には パスワードなどの機密情報が含まれる場合がある

S3 + DynamoDB を使ったリモート State では:
– S3 で State ファイルを安全に保管(バージョニング有効)
– DynamoDB でロック管理(同時実行を防ぐ)


State 管理用リソースの事前作成

S3 バックエンドを設定する前に、State を保存する S3 バケットと DynamoDB テーブルを別のディレクトリで作成します。

cd ~/terraform-handson/state-backend

以下の内容で main.tf を作成します。

terraform {
  required_version = ">= 1.7.0"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_s3_bucket" "tfstate" {
  bucket = "terraform-state-YOURNAME"
  tags = {
 Name = "terraform-state"
  }
}

resource "aws_s3_bucket_versioning" "tfstate" {
  bucket = aws_s3_bucket.tfstate.id
  versioning_configuration {
 status = "Enabled"
  }
}

resource "aws_dynamodb_table" "tfstate_lock" {
  name= "terraform-state-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key  = "LockID"

  attribute {
 name = "LockID"
 type = "S"
  }

  tags = {
 Name = "terraform-state-lock"
  }
}
terraform init
terraform apply
aws_s3_bucket.tfstate: Creating...
aws_s3_bucket.tfstate: Creation complete after 2s [id=terraform-state-YOURNAME]
aws_s3_bucket_versioning.tfstate: Creating...
aws_s3_bucket_versioning.tfstate: Creation complete after 1s [id=terraform-state-YOURNAME]
aws_dynamodb_table.tfstate_lock: Creating...
aws_dynamodb_table.tfstate_lock: Creation complete after 6s [id=terraform-state-lock]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
[スクリーンショット: AWS コンソールの DynamoDB テーブル一覧、terraform-state-lock が ACTIVE 状態]

Step E: backend.tf の設定

cd ~/terraform-handson/step-e

Step D のファイルをすべてコピーして、main.tfterraform ブロックを以下に差し替えます(backend "s3" ブロックを追加)。

terraform {
  required_version = ">= 1.7.0"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
  backend "s3" {
 bucket= "terraform-state-YOURNAME"
 key= "handson/terraform.tfstate"
 region= "ap-northeast-1"
 dynamodb_table = "terraform-state-lock"
 encrypt  = true
  }
}

YOURNAME を自分の識別子に変更してください(state-backend で使用した名前と一致させること)。

各設定の意味:
| 設定項目 | 内容 |
|———-|——|
| bucket | State ファイルを保存する S3 バケット名 |
| key | バケット内のパス(State ファイルのキー) |
| region | S3 バケットのリージョン |
| dynamodb_table | ロック管理に使用する DynamoDB テーブル名 |
| encrypt | S3 サーバーサイド暗号化を有効化 |


terraform init -migrate-state

バックエンドを変更した際は、terraform init で移行が必要です。

terraform init -migrate-state
Initializing the backend...
Acquiring state lock. This may take a few moments...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "s3" backend. No existing state was found in the newly
  configured "s3" backend. Do you want to copy this existing state to the new
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: 

yes と入力します。

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the lock file
- Using previously-installed hashicorp/aws v5.94.1

Terraform has been successfully initialized!

リモート State の確認

S3 コンソールで State ファイルが保存されているか確認します。

  1. S3terraform-state-YOURNAME バケットを開く
  2. handson/terraform.tfstate が存在することを確認
[スクリーンショット: S3 バケット内の terraform.tfstate ファイル]

バージョニングが有効なので、以前のバージョンも保存されています。過去の State に戻すことも可能です。


DynamoDB ロックの動作確認

別のターミナルを開き、同時に terraform apply を実行してみます。

ターミナル 1:

cd ~/terraform-handson/step-e
terraform apply

ターミナル 2(ターミナル 1 が実行中の間に):

cd ~/terraform-handson/step-e
terraform apply

ターミナル 2 では以下のエラーが表示されます。

Acquiring state lock. This may take a few moments...
╷
│ Error: Error acquiring the state lock
│ 
│ Error message: ConditionalCheckFailedException: The conditional request failed
│ Lock Info:
│ID:  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
│Path:terraform-state-YOURNAME/handson/terraform.tfstate
│Operation: OperationTypeApply
│Who: user@hostname
│Version:1.14.8
│Created:2026-04-13 13:30:00.000000000 +0000 UTC
│Info:
╵

DynamoDB によるロックが正常に機能していることが確認できます。チームでの同時実行を防ぐ重要な機能です。


Step E のクリーンアップ

ハンズオンが完了したら、作成したリソースを削除します。

# Step E のリソース削除
cd ~/terraform-handson/step-e
terraform destroy

# State 管理用リソースの削除
cd ~/terraform-handson/state-backend
# S3 バケットを空にする
aws s3 rm s3://terraform-state-YOURNAME --recursive
terraform destroy

注意: S3 バケットを削除するには、先にバケット内のオブジェクトをすべて削除する必要があります。


Section 3 まとめ

Step学んだ内容使用コマンド
A基本ワークフロー、S3バケット作成init / plan / apply / destroy
B複数リソース、暗黙的依存関係plan(4 to add の確認)
C変数化、dev/prod 環境分離-var-file による切り替え
DOutput 定義、値の参照output / output -raw
EリモートState、S3+DynamoDB連携init -migrate-state

Terraform の基本ワークフローから始まり、変数化・Output・リモート State まで、実務で必要な基礎スキルを一通り習得できました。

コスト管理: ハンズオン完了後は必ず terraform destroy を実行してリソースを削除してください。
特に EC2 インスタンスは起動しているだけで課金が発生します。


Section 4: 実践Tips — チームでTerraformを使いこなす

ここまでで、Terraformの基本的な使い方(init / plan / apply / destroy)を学びました。このセクションでは、実務でTerraformを使う際に「知っておかないとハマる」実践的なノウハウをまとめます。


4-1. .gitignore の設定

TerraformプロジェクトをGitで管理する際、コミットすべきでないファイルコミットすべきファイルを正しく区別することが重要です。以下の .gitignore テンプレートを使ってください。

# Terraformが自動生成するディレクトリ(プロバイダのバイナリなどが入る)
.terraform/

# ロックファイル:チームで共有するためコミットする(後述)
# .terraform.lock.hcl  ← コメントアウト = コミット対象にする

# stateファイル(AWSリソースの現在の状態を記録する重要ファイル)
# ローカルに置く場合もコミット禁止(機密情報・差分衝突の原因)
terraform.tfstate
terraform.tfstate.backup

# 変数ファイル(アクセスキー・パスワードなどの機密情報を含む可能性)
*.tfvars

# サンプル変数ファイルはコミット可(値は dummy でよい)
!example.tfvars

# 手動オーバーライド用ファイル(通常は不要)
override.tf
override.tf.json

# Terraformがクラッシュしたときに生成されるログ
crash.log

各エントリの説明

エントリ理由
.terraform/プロバイダのバイナリ(数十MB)が入る。terraform init で再生成できるため不要
terraform.tfstateAWSリソースIDやパスワードなどの機密情報を含む可能性。S3などのリモートバックエンドで管理するのがベストプラクティス
terraform.tfstate.backupstateの前バージョン。同上の理由でコミット禁止
*.tfvarsTF_VAR_ で渡す機密値を記載することが多いため除外。terraform.tfvars も対象
!example.tfvarsダミー値を入れたサンプルファイルは除外対象から外してコミット可能にする
crash.logクラッシュ時のスタックトレース。個人環境固有の情報が含まれる

⚠️ .terraform.lock.hcl はコミットする

.terraform.lock.hclコミット対象 です。このファイルには、使用するプロバイダのバージョンとチェックサムが記録されています。

# .terraform.lock.hcl(例)
provider "registry.terraform.io/hashicorp/aws" {
  version  = "5.40.0"
  constraints = "~> 5.0"
  hashes = [
 "h1:...",
 "zh:...",
  ]
}

このファイルをコミットすることで:
– チームメンバー全員が 同じバージョンのプロバイダ を使える
– CI/CD環境でも同じプロバイダが使われる
– 意図しないプロバイダのアップグレードを防げる


4-2. 日常ワークフロー

実務でTerraformを使う際の代表的なコマンドとその使いどころを紹介します。

terraform fmt — フォーマット統一

HCLファイルを公式スタイルに自動整形します。CIパイプラインに必須 です。

# カレントディレクトリ以下を再帰的にフォーマット
$ terraform fmt -recursive

# フォーマットが必要なファイルを確認だけ(CI用)
$ terraform fmt -check -recursive
# フォーマット対象があった場合の出力
main.tf
variables.tf

-check フラグを使うとフォーマットが崩れているとき終了コード1を返すため、CIでのゲートとして使えます。

terraform validate — 構文チェック

terraform plan の前に必ず実行します。構文エラーや不正な参照を検出します。

$ terraform validate
# 成功時
Success! The configuration is valid.

# エラー時
│ Error: Reference to undeclared resource
│on main.tf line 15, in resource "aws_s3_bucket" "example":
│15:bucket = aws_s3_bucket.typo.bucket

terraform plan — 差分確認(最重要コマンド)

apply 前に必ず実行し、変更内容をレビューします。チームでのコードレビュー時も plan の出力を共有するのがベストプラクティスです。

$ terraform plan
Terraform will perform the following actions:

  # aws_s3_bucket.example will be created
  + resource "aws_s3_bucket" "example" {
+ bucket = "my-terraform-bucket-12345"
+ id  = (known after apply)
...
 }

Plan: 1 to add, 0 to change, 0 to destroy.

出力の読み方:
+ : 新規作成
~ : 変更(in-place update)
-/+ : 削除して再作成(ダウンタイムが発生する可能性)
- : 削除

terraform plan -out=tfplanterraform apply tfplan — 安全な適用手順

plan の結果を保存して、その結果だけを apply する手順です。本番環境では必ずこの手順を使ってください。

# ステップ1: planの結果をファイルに保存
$ terraform plan -out=tfplan

# ステップ2: 保存したplanを確認(人間が読める形式で表示)
$ terraform show tfplan

# ステップ3: 保存したplanのみをapply(planと apply の間に差分が生じない)
$ terraform apply tfplan

この手順のメリット:
plan 実行後にリソースが変更されても、保存されたplan内容だけ が適用される
– レビュー済みのplanファイルをCIに渡してapplyするパターンにも使える

terraform show — 現在のstateを確認

$ terraform show
# aws_s3_bucket.example:
resource "aws_s3_bucket" "example" {
 bucket = "my-terraform-bucket-12345"
 id  = "my-terraform-bucket-12345"
 region = "ap-northeast-1"
 ...
}

terraform state list — 管理中リソース一覧

Terraformが管理しているリソースの一覧を確認します。

$ terraform state list
aws_s3_bucket.example
aws_s3_bucket_versioning.example
aws_iam_role.lambda_exec
aws_lambda_function.my_function

4-3. terraform.lock.hcl の役割

プロバイダバージョンを固定する仕組み

terraform init を実行すると、使用するプロバイダのバージョンと各プラットフォーム用のチェックサムが .terraform.lock.hcl に記録されます。

# 初回init時にロックファイルが生成される
$ terraform init

# 以降はロックファイルに記録されたバージョンが使われる
$ terraform init
# → "Terraform has been successfully initialized!"
# → プロバイダのダウンロードはスキップ(ロック済みのため)

チーム全員が同じプロバイダを使う重要性

プロバイダのバージョンが異なると、同じTerraformコードでも 動作が変わる ことがあります。

チームAのPC: aws provider 5.38.0 → plan結果: 変更なし
チームBのPC: aws provider 5.40.0 → plan結果: 差分あり(属性のデフォルト値が変わった)

.terraform.lock.hcl をコミットしておけば、terraform init 時に全員が同じバージョンをダウンロードします。

terraform init -upgrade でのプロバイダ更新

プロバイダを意図的に更新する場合は -upgrade フラグを使います。

# ロックファイルを無視して最新のプロバイダを取得 + ロックファイルを更新
$ terraform init -upgrade

# 更新後は必ずplanで差分確認
$ terraform plan

更新後は必ずチームでレビューし、問題がなければ更新された .terraform.lock.hcl をコミットします。


4-4. モジュール化の基礎概念

モジュールとは

モジュールとは、再利用可能な .tf ファイルのまとまり です。同じ構成(例:S3バケット + バージョニング + ライフサイクル)を複数の環境(dev/prod)で使い回す際に威力を発揮します。

ディレクトリ構造例

my-infra/
├── modules/# 再利用可能なモジュール置き場
│└── s3-bucket/
│ ├── main.tf  # S3バケットのリソース定義
│ ├── variables.tf# モジュールへの入力変数
│ └── outputs.tf  # モジュールからの出力値
└── environments/ # 環境ごとの設定
 ├── dev/
 │├── main.tf  # モジュールを呼び出す
 │└── terraform.tfvars
 └── prod/
  ├── main.tf  # 同じモジュールを呼び出す(値が異なる)
  └── terraform.tfvars
# environments/dev/main.tf
module "s3" {
  source= "../../modules/s3-bucket"
  bucket_name = "my-app-dev-bucket"
  environment = "dev"
}

# environments/prod/main.tf
module "s3" {
  source= "../../modules/s3-bucket"
  bucket_name = "my-app-prod-bucket"
  environment = "prod"
}

モジュール化のメリット:
DRY原則(Don’t Repeat Yourself)を実現
– バグ修正をモジュール側で行えば全環境に反映
– チームでの役割分担(モジュール開発者 / 利用者)が明確になる

公開モジュール(Terraform Registry)

Terraform Registry では、コミュニティや各クラウドベンダーが公開しているモジュールを利用できます。

# AWS VPCモジュール(公開モジュールの使用例)
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.5.2"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs = ["ap-northeast-1a", "ap-northeast-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
}

本番利用の際は version を必ず固定してください。


4-5. CI/CD との統合

GitHub Actions での自動plan

プルリクエスト時に terraform plan を自動実行し、結果をPRコメントに投稿する構成が広く使われています。

# .github/workflows/terraform.yml(簡略版)
name: Terraform Plan

on:
  pull_request:
 paths:
- "**.tf"

jobs:
  plan:
 runs-on: ubuntu-latest
 steps:
- uses: actions/checkout@v4

- name: Setup Terraform
  uses: hashicorp/setup-terraform@v3
  with:
 terraform_version: "1.7.5"

- name: Terraform Init
  run: terraform init
  env:
 AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
 AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

- name: Terraform Plan
  run: terraform plan -no-color

このワークフローにより:
– コードレビュー時に plan 結果を自動確認できる
terraform fmt -check を追加すればフォーマット漏れも検出できる

主要なTerraform自動化ツール

ツール特徴
AtlantisGitHubのPRコメントでplan/applyを操作。セルフホスト型
Terraform CloudHashiCorpが提供するSaaS型。stateのリモート管理も含む
Spaceliftより高度なポリシーやドリフト検出が可能なSaaS型

小規模なチームにはAtlantis、エンタープライズ要件があればTerraform CloudやSpaceliftが適しています。


4-6. Step Functionsシリーズへの接続

本記事で学んだTerraformの基礎知識は、AWSのStep Functionsを実践的に構築する記事の前提知識として活用できます。以下のシリーズ記事では、各セクションにTerraformによる実装ハンズオンが含まれています。

関連記事

  • AWS Step Functions 入門
    Step Functionsの基本概念からTerraformでのステートマシン構築まで。aws_sfn_state_machine リソースや ASL(Amazon States Language)の記述方法を解説しています。

  • ECS × Step Functions 入門
    ECSタスクをStep Functionsから起動するアーキテクチャのTerraform実装。aws_ecs_task_definitionaws_iam_role などを組み合わせた実践的な構成を学べます。

これらの記事のTerraformセクションでは、本記事で紹介した以下の知識が前提となっています:
terraform init / plan / apply の基本操作
– 変数(variables.tf)と出力(outputs.tf)の使い方
.gitignore によるstateファイルの管理
– プロバイダのバージョン固定(.terraform.lock.hcl


Section 4 まとめ

Tipsポイント
.gitignorestateとtfvarsは除外。ロックファイルはコミット
日常ワークフローfmt → validate → plan → apply の順番を守る
安全なapply-out=tfplan で計画を保存してから適用
ロックファイルチームの一致性のためコミット必須
モジュール化環境をまたぐ共通構成はモジュールに切り出す
CI/CDPRごとに自動planを実行してレビューに組み込む

Section 5: ハンズオン後の削除手順

5-1. terraform destroy の実行

ハンズオン完了後は terraform destroy でリソースを削除してください。EC2インスタンスは起動中でも課金が継続するため、不要になったら速やかに削除することを推奨します。

削除前に plan で確認

# 削除対象を事前に確認(dry-run)
terraform plan -destroy

plan -destroy を実行すると、削除されるリソースの一覧が表示されます。実際には何も削除されないので、安心して確認できます。

Plan: 0 to add, 0 to change, 4 to destroy.

  # aws_instance.handson_ec2 will be destroyed
  # aws_key_pair.handson will be destroyed
  # aws_s3_bucket.handson will be destroyed
  # aws_security_group.handson_sg will be destroyed

削除の実行

terraform destroy

確認プロンプトが表示されるので yes を入力してください。

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

💡 削除順序はTerraformが自動解決します。 aws_instanceaws_security_group のように依存関係を考慮した順序で削除されるため、手動で順序を意識する必要はありません。


5-2. State ファイルの後処理

ローカル State(Step A〜D の場合)

Step A〜D ではローカルの terraform.tfstate にインフラ状態が記録されています。terraform destroy 完了後は、このファイルをプロジェクトから除外してください。

# destroy 後に残っているStateファイルを確認
ls -la terraform.tfstate terraform.tfstate.backup

# git 管理外であることを確認(.gitignore に記載されているはず)
cat .gitignore | grep tfstate

⚠️ terraform.tfstate は機密情報を含む場合があります。 リソースのIDやARNなどが平文で記録されているため、誤って git にコミットしないよう .gitignore への記載を必ず確認してください。

リモート State(Step E の場合)

Step E でリモート State を設定した場合、S3 バケットと DynamoDB テーブルの削除には追加手順が必要です。これらのリソースは Terraform の backend 設定そのものに使われているため、terraform destroy だけでは削除されません。

# Step 1: backend を local に戻す(main.tf の backend "s3" ブロックをコメントアウト)
# backend "s3" { ... } をコメントアウトまたは削除した後:

terraform init -migrate-state
# プロンプト: "Do you want to copy existing state to the new backend?" → yes

# Step 2: State 管理リソースを削除
terraform destroy
# S3バケット(terraform-state-YOURNAME)と DynamoDBテーブルが削除対象になる

5-3. 削除時の注意事項

S3 バケットにオブジェクトが残っている場合

S3 バケットにオブジェクト(ファイル)が存在すると、terraform destroy が失敗します。事前にバケットを空にしてください。

# バケット内のオブジェクトをすべて削除
aws s3 rm s3://terraform-handson-bucket-YOURNAME --recursive

# 削除確認
aws s3 ls s3://terraform-handson-bucket-YOURNAME
# (何も表示されなければOK)

削除後の残存リソース確認

terraform destroy が完了したら、State にリソースが残っていないことを確認します。

# Stateに残っているリソースを確認(空であればOK)
terraform state list

No state file was found! または空の出力が返れば、すべて削除されています。

EC2 インスタンスの「停止」と「削除」の違い

操作コスト永続性
停止(Stop)EBSストレージ料金は継続発生データ保持
削除(Terminate)課金停止データ消去

terraform destroy削除(Terminate)を実行します。ハンズオンのEC2は削除で問題ありませんが、本番環境では誤削除に注意してください。


5-4. 部分削除(特定リソースのみ)

特定のリソースだけを削除したい場合は -target オプションを使います。

# EC2インスタンスのみ削除
terraform destroy -target=aws_instance.handson_ec2

# Security Group のみ削除
terraform destroy -target=aws_security_group.handson_sg

⚠️ -target は緊急時の手段です。 依存関係のあるリソースを個別に削除すると State と実際のインフラが不整合になる場合があります。通常のクリーンアップは -target を使わず terraform destroy を実行してください。


Section 6: まとめ

6-1. 学習内容の振り返り

本記事では Terraform の基礎から実践まで、段階的なハンズオンを通して習得しました。以下のスキルチェックリストで習得内容を確認してください。

インストール・セットアップ
– [ ] Terraform CLI をインストールし、terraform version で動作確認できる
– [ ] AWS CLI を設定し、aws sts get-caller-identity で認証確認できる
– [ ] .gitignore*.tfstate, *.tfvars, .terraform/ を追加できる

Step A: S3バケット最小構成
– [ ] terraform {} ブロックで required_version と required_providers を設定できる
– [ ] provider "aws" でリージョンを指定できる
– [ ] resource "aws_s3_bucket" でS3バケットをコードで作成できる
– [ ] terraform initplanapply のワークフローを実行できる

Step B: EC2インスタンス追加
– [ ] Security Group リソースで inbound/outbound ルールを定義できる
– [ ] aws_key_pair でSSHキーペアを登録できる
– [ ] aws_instance で EC2 インスタンスをコードで起動できる
– [ ] リソース間の参照(aws_security_group.handson_sg.id)を理解している

Step C: 変数化
– [ ] variable ブロックで type / default / description を設定できる
– [ ] terraform.tfvars で変数値を環境ごとに分離できる
– [ ] var.xxx で変数を参照できる

Step D: Output
– [ ] output ブロックで作成したリソースの情報(IP・ARN等)を出力できる
– [ ] terraform output コマンドで値を確認できる

Step E: リモート State
– [ ] S3 バケットと DynamoDB テーブルで State 管理基盤を構築できる
– [ ] backend "s3" ブロックでリモート State を設定できる
– [ ] terraform init -migrate-state でローカル State をリモートへ移行できる
– [ ] State ロックにより複数人同時実行が防止されることを理解している

削除・クリーンアップ
– [ ] terraform plan -destroy で削除対象を事前確認できる
– [ ] terraform destroy でリソースを安全に削除できる
– [ ] S3バケット内オブジェクトの事前削除など、destroy の前提条件を把握している


6-2. 本記事が前提知識となる Step Functions シリーズ

Terraform の基礎を習得したら、以下の Step Functions シリーズでサーバーレスワークフロー開発にステップアップしてください。すべての記事でコンソール操作と Terraform の両方を解説しています。

#記事習得スキル
第1回AWS Step Functions 入門 — コンソールとTerraformで学ぶハンズオンステートマシン基礎・Lambda連携
第2回ECS × Step Functions 入門 — CSVバッチをFargateタスクでジョブ化するハンズオンFargateバッチ・.sync統合
第3回Step Functions エラーハンドリング完全ガイドRetry/Catch・冪等性設計
第4回Step Functions 入出力データフロー制御完全ガイドInputPath/OutputPath/ResultPath
第5回Step Functions Callbackパターン完全ガイドwaitForTaskToken・非同期処理
第6回Step Functions Distributed Map完全ガイド大規模並列処理・S3データ処理
第7回Step Functions Express vs Standard完全ガイドワークフロータイプ選択基準
第8回Step Functions SDK Direct Integration完全ガイドLambda不要のSDK直接統合

6-3. 次のステップ

本記事で Terraform の基礎を押さえたら、以下のトピックで実践力をさらに高めましょう。

モジュール化(DRY な構成管理)

# モジュールを使うと環境間でコードを再利用できる
module "s3_bucket" {
  source= "./modules/s3"
  bucket_name = var.bucket_name
  environment = var.environment
}

同じリソースを dev/stg/prod で使い回す場合にモジュールが威力を発揮します。

Terragrunt による DRY 構成

Terragrunt は Terraform のラッパーツールで、複数環境・複数リージョンの構成を DRY(Don’t Repeat Yourself)に管理できます。大規模なマルチアカウント構成に特に有効です。

GitHub Actions による CI/CD

# .github/workflows/terraform.yml(例)
- name: Terraform Plan
  run: terraform plan -no-color

- name: Terraform Apply
  if: github.ref == 'refs/heads/main'
  run: terraform apply -auto-approve

Pull Request 時に terraform plan を自動実行し、マージ時に terraform apply を実行する CI/CD パイプラインを構築できます。

AWS CDK / Pulumi との比較

ツール言語学習コスト特徴
TerraformHCL低〜中マルチクラウド・エコシステムが豊富
AWS CDKTypeScript / Python 等中〜高AWSネイティブ・型安全
PulumiTypeScript / Python 等中〜高マルチクラウド・汎用言語で記述

AWS 専用ならCDKも強力な選択肢ですが、マルチクラウドや既存の Terraform エコシステムを活用したい場合は Terraform が定番です。


6-4. 参考リンク


6-5. 記事フッター


本記事は「AWS ハンズオン TechBlog」Terraform シリーズの第1回です。

次回以降は Step Functions シリーズと合わせて、Terraform で AWS サーバーレス基盤を構築する実践的な内容を公開予定です。

最新情報をチェックしよう!