2026年7月17日
2026年7月17日
AWSのインフラをTerraformでコード化する方法|認証・stateのS3保管・plan/applyの流れと既存リソースの取り込み
AWSのインフラをTerraformでコード化するには、まず「どのAWSアカウントに、どの権限で接続するか」と「state(Terraformが作ったものの記録)をどこに置くか」を決め、そのうえでリソースを .tf ファイルに書き、terraform plan で差分を読んでから terraform apply で反映する、という流れを作ります。この記事では、AWSでTerraformを使い始めるときの土台づくりとして、認証情報の扱い、stateを置くS3バケットの用意、最初のリソースの書き方、planの読み方、環境の分け方、手作業で作った既存リソースの取り込みまでを説明します。
WordPressのサーバー構成をTerraformで組む具体例は別の記事で扱っているので、ここではAWSでTerraformを運用するための共通の基礎に絞ります。TerraformとAWSプロバイダーは更新が速く、書き方が変わることがあります。ここでの説明は執筆時点(2026年10月現在)の情報をもとにしているので、使う版のドキュメントでも確認してください。
Terraformでコード化すると何が変わるか
AWSのコンソールで手作業で作ったリソースは、「誰が、いつ、なぜその設定にしたか」が残りません。同じ構成をステージング用にもう1つ作るのも、手順書を見ながらの作業になります。
Terraformでは、構成をコードとしてGitで管理し、変更はプルリクエストで確認してから反映できます。反映の前に「何が作られ、何が変更され、何が削除されるか」の一覧(plan)を見られるのが、手作業との大きな違いです。
なお、TerraformはHashiCorpのツールで、ライセンスはBSLです。オープンソースのフォークとしてOpenTofuがあり、基本的な書き方は共通しています。組織によってはライセンスの確認が必要です。
ステップ1:AWSへの認証を決める
.tf ファイルにアクセスキーを書くのは避けます。コードはGitで共有されるため、キーが漏れる原因になります。手元のPCからは、AWS CLIのプロファイルを使うのが基本です。
# IAM Identity Center(旧 AWS SSO)を使う場合
aws configure sso --profile tf-admin
aws sso login --profile tf-admin
# Terraform にプロファイルを渡す
export AWS_PROFILE=tf-admin
aws sts get-caller-identity
aws sts get-caller-identity で、意図したアカウントとロールが表示されることを確認してから作業します。本番と検証でアカウントを分けている場合、ここを確認せずに実行して別のアカウントに作ってしまう事故がよくあります。
CI/CD(GitHub Actionsなど)から実行する場合は、長期のアクセスキーを保存するのではなく、OIDC連携で一時的な認証情報を受け取る方式を使います。IAMロールの信頼ポリシーで、どのリポジトリ・どのブランチからの実行を許可するかを絞れます。
ステップ2:stateを置くS3バケットを用意する
stateは、Terraformが実際に作ったリソースと、コードの対応を記録したファイルです。手元に置いたままだと、別の人が同時に実行して壊れたり、PCの故障で失われたりします。AWSで使うならS3に置くのが一般的です。
state用のバケットはTerraformで管理する対象の外に置くと、管理がシンプルになります。最初の1回だけCLIで作ります。
aws s3api create-bucket \
--bucket your-tfstate-bucket \
--region ap-northeast-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1
aws s3api put-bucket-versioning \
--bucket your-tfstate-bucket \
--versioning-configuration Status=Enabled
aws s3api put-public-access-block \
--bucket your-tfstate-bucket \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
バージョニングを有効にしておくと、stateが壊れたときに前の版へ戻せます。stateにはデータベースのパスワードなど秘密の値が平文で入ることがあるため、公開をブロックし、読み取れるIAMの主体も絞ってください。
ステップ3:バージョンとバックエンドを固定する
# versions.tf
terraform {
required_version = ">= 1.11"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "your-tfstate-bucket"
key = "network/prod/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
use_lockfile = true
}
}
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
ManagedBy = "terraform"
Project = "example"
}
}
}
- **
use_lockfile**:S3だけでロックを取り、同時実行を防ぐ設定です。以前はDynamoDBのテーブルでロックを取る方式が使われていました。どちらが使えるかはTerraformの版によるので、使う版のドキュメントで確認してください。 - **
version = "~> 6.0"**:AWSプロバイダーのメジャーバージョンを固定します。メジャーバージョンが上がると書き方が変わることがあるため、上げるときはアップグレードガイドを読んでから計画的に行います。 - **
default_tags**:すべてのリソースに共通のタグを付けます。コンソールで見たときに「Terraformで管理しているもの」だと分かり、手で変更してしまう事故を減らせます。
terraform init を実行すると、プロバイダーがダウンロードされ、.terraform.lock.hcl(プロバイダーの版の記録)が作られます。このファイルはGitにコミットし、チーム全員が同じ版を使うようにします。
ステップ4:最初のリソースを書く
例として、バージョニングを有効にした非公開のS3バケットを作ります。
# main.tf
resource "aws_s3_bucket" "assets" {
bucket = "your-assets-bucket-example"
}
resource "aws_s3_bucket_versioning" "assets" {
bucket = aws_s3_bucket.assets.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "assets" {
bucket = aws_s3_bucket.assets.id
block_public_acls = true
ignore_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
現在のAWSプロバイダーでは、バケットの本体と、バージョニングや公開設定などの設定を別々のリソースとして書きます。古い記事では aws_s3_bucket の中にまとめて書く例がありますが、その書き方は現在の版では使えないか非推奨なので、参考にするときは書かれた時期に注意してください。
ステップ5:planを読んでからapplyする
terraform fmt
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
plan -out で保存した計画を apply に渡すと、確認した内容と違う変更が反映されるのを防げます。planの記号の意味は次のとおりです。
+:新しく作る~:その場で設定を変更する-:削除する-/+:いったん削除して作り直す
特に注意して読むのは -/+ です。属性の中に forces replacement と書かれていたら、その属性の変更でリソースが作り直されることを意味します。データベースやストレージで作り直しが起きると、データを失うおそれがあります。重要なリソースには lifecycle { prevent_destroy = true } を書いておくと、削除を伴う計画はエラーで止まるようになります。本番に反映する前には、データのバックアップやスナップショットを取っておいてください。
環境を分ける
ステージングと本番は、ディレクトリとstateのキーを分けるのが分かりやすい方法です。
infra/
├── modules/
│ └── storage/ # 共通の部品
└── envs/
├── staging/ # key = "storage/staging/terraform.tfstate"
└── prod/ # key = "storage/prod/terraform.tfstate"
共通の構成は modules/ にまとめ、各環境のディレクトリからは値だけを変えて呼び出します。Terraformのワークスペース機能でも環境を分けられますが、どのワークスペースを選んでいるかを取り違えると本番に反映してしまうため、本番と検証の分離にはディレクトリ(できればAWSアカウント)を分けるほうが安全です。
VPCのように部品の多いものは、コミュニティで広く使われているモジュール(Terraform Registryの terraform-aws-modules など)を使う手もあります。外部のモジュールを使う場合も版を固定し、中身が何を作るかをplanで確認してください。
手作業で作った既存リソースを取り込む
すでにコンソールで作ったリソースは、import ブロックでTerraformの管理下に移せます(Terraform 1.5以降)。
import {
to = aws_s3_bucket.legacy
id = "existing-bucket-name"
}
terraform plan -generate-config-out=generated.tf
-generate-config-out を付けると、既存の設定を元にしたコードの下書きが作られます。下書きには不要な属性も含まれるので、整理してから main.tf に移します。取り込んだあとに terraform plan を実行し、「変更なし」になることを確認してから、コードでの管理を始めます。
取り込んだあとは、そのリソースをコンソールで手で変更しないことが大事です。手での変更はコードとの食い違い(ドリフト)になり、次の apply で元に戻されます。食い違いがあるかは terraform plan -refresh-only で確認できます。
Gitに含めないもの
.gitignore に次を入れておきます。
.terraform/
*.tfstate
*.tfstate.*
tfplan
*.auto.tfvars
.terraform.lock.hcl はコミットします。秘密の値を .tfvars に書く運用は避け、AWS Secrets Managerなどで管理してTerraformからは参照するだけにすると、Gitに秘密情報が入る心配が減ります。
まとめ
- 認証はAWS CLIのプロファイルやOIDC連携で渡し、
.tfにアクセスキーを書かない。作業前にaws sts get-caller-identityで接続先を確認する。 - stateはバージョニングと公開ブロックを設定したS3バケットに置き、ロックを有効にする。stateには秘密の値が入ることがある。
- Terraformとプロバイダーの版を固定し、
.terraform.lock.hclをコミットする。 plan -outで保存した計画を読み、-/+とforces replacementに注意してからapplyする。重要なリソースにはprevent_destroyを付ける。- 環境はディレクトリとstateのキーで分け、既存リソースは
importブロックで取り込んでから手で触らない。