- Authors
- Name
- はじめに
- Terraform Stateとは?
- Remote Backendの構成
- State Lockingの詳細
- State分離戦略
- Stateのマイグレーション
- Stateのバックアップとリカバリ
- セキュリティベストプラクティス
- まとめ

はじめに
Terraformを一人で使う場合は、ローカルのterraform.tfstateファイルで十分です。しかし、チーム環境ではStateの競合、同時修正、Stateの消失といった問題が発生します。この記事では、Terraform Stateを安全に管理する方法を実践的な例とともに解説します。
Terraform Stateとは?
Terraform Stateは、インフラの現在の状態を追跡するJSONファイルです。
# Stateファイルの確認
cat terraform.tfstate | jq '.resources[0]'
{
"mode": "managed",
"type": "aws_instance",
"name": "web",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"attributes": {
"id": "i-0abc123def456789",
"ami": "ami-0c55b159cbfafe1f0",
"instance_type": "t3.medium"
}
}
]
}
Stateが重要な理由
- マッピング: Terraformコードと実際のリソースのマッピング
- 依存関係の追跡: リソース間の依存関係を記録
- パフォーマンス: API呼び出しの最小化(まずStateで確認)
- 協業: チームメンバー間でインフラ状態を共有
Remote Backendの構成
AWS S3 + DynamoDB
最も一般的なRemote Backend構成です:
# backend.tf - S3 Backend設定
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/networking/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-state-lock"
# 追加セキュリティ設定
kms_key_id = "alias/terraform-state"
}
}
S3バケットの作成(ブートストラップ)
# bootstrap/main.tf - Stateストレージのブートストラップ
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state-bucket"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.terraform_state.arn
}
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# State LockingのためのDynamoDB
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
# 暗号化のためのKMS Key
resource "aws_kms_key" "terraform_state" {
description = "KMS key for Terraform state encryption"
deletion_window_in_days = 30
enable_key_rotation = true
}
resource "aws_kms_alias" "terraform_state" {
name = "alias/terraform-state"
target_key_id = aws_kms_key.terraform_state.key_id
}
# ブートストラップの実行(まずローカルStateで作成)
cd bootstrap
terraform init
terraform apply
# ブートストラップ自体のStateもS3にマイグレーション可能
GCS(Google Cloud Storage)
terraform {
backend "gcs" {
bucket = "my-terraform-state"
prefix = "prod/networking"
# GCSは自体でState Lockingをサポート
# DynamoDBのような別途のLockテーブル不要
}
}
# GCSバケットの作成
gsutil mb -p my-project -l asia-northeast3 gs://my-terraform-state
gsutil versioning set on gs://my-terraform-state
Azure Blob Storage
terraform {
backend "azurerm" {
resource_group_name = "terraform-state-rg"
storage_account_name = "tfstate2026"
container_name = "tfstate"
key = "prod/networking/terraform.tfstate"
# Azure Blob Leaseによる自動Locking
}
}
State Lockingの詳細
DynamoDB Lockの動作原理
# Lock取得のプロセス
# 1. terraform applyを実行
# 2. DynamoDBにLockID項目を作成(PutItem with ConditionExpression)
# 3. 他のユーザーがapplyを試みるとLock競合エラーが発生
# Lock状態の確認
aws dynamodb get-item \
--table-name terraform-state-lock \
--key '{"LockID": {"S": "my-terraform-state-bucket/prod/networking/terraform.tfstate"}}' \
| jq '.Item.Info.S | fromjson'
Lockが保持されている場合
# Lock情報の確認
terraform force-unlock <LOCK_ID>
# 注意: force-unlockは本当にLockが不正に残っている場合のみ使用
# 他のユーザーが作業中の場合、Stateが破損する可能性がある
Lockタイムアウトの設定
# Lock待機時間の設定(デフォルト0s = 即座に失敗)
terraform apply -lock-timeout=5m
State分離戦略
環境別の分離
terraform/
├── modules/
│ ├── networking/
│ ├── compute/
│ └── database/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ └── backend.tf # key = "dev/terraform.tfstate"
│ ├── staging/
│ │ ├── main.tf
│ │ └── backend.tf # key = "staging/terraform.tfstate"
│ └── prod/
│ ├── main.tf
│ └── backend.tf # key = "prod/terraform.tfstate"
Workspaceの活用
# Workspaceの作成
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Workspaceの切り替え
terraform workspace select prod
# 現在のWorkspaceを確認
terraform workspace show
# Workspaceに応じて設定を分岐
locals {
env = terraform.workspace
instance_type = {
dev = "t3.small"
staging = "t3.medium"
prod = "t3.large"
}
}
resource "aws_instance" "web" {
instance_type = local.instance_type[local.env]
# ...
}
TerragruntでDRYに管理
# terragrunt.hcl(ルート)
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "my-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
# environments/prod/networking/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../../modules/networking"
}
inputs = {
vpc_cidr = "10.0.0.0/16"
environment = "prod"
}
Stateのマイグレーション
ローカルからRemoteへ
# 1. backend.tfにRemote Backend設定を追加
# 2. terraform initを実行
terraform init
# Terraformが自動的にマイグレーションを提案
# "Do you want to copy existing state to the new backend?"
# → yesを入力
State間のリソース移動
# Stateからリソースを削除(実際のインフラは維持)
terraform state rm aws_instance.old_web
# 別のStateにリソースをインポート
terraform import aws_instance.new_web i-0abc123def456789
# State間の移動(Terraform 1.1以降)
terraform state mv -state-out=../other/terraform.tfstate \
aws_instance.web aws_instance.web
movedブロック(Terraform 1.1以降)
# リソース名変更時にStateを自動更新
moved {
from = aws_instance.web
to = aws_instance.web_server
}
moved {
from = module.old_vpc
to = module.networking
}
Stateのバックアップとリカバリ
S3バージョニングによるリカバリ
# Stateファイルのバージョン一覧を確認
aws s3api list-object-versions \
--bucket my-terraform-state \
--prefix prod/networking/terraform.tfstate \
| jq '.Versions[:5] | .[] | {VersionId, LastModified, Size}'
# 特定のバージョンに復元
aws s3api get-object \
--bucket my-terraform-state \
--key prod/networking/terraform.tfstate \
--version-id "abc123" \
terraform.tfstate.backup
# 復元されたStateをpush
terraform state push terraform.tfstate.backup
Stateバックアップの自動化
#!/bin/bash
# backup-state.sh
DATE=$(date +%Y%m%d-%H%M%S)
BUCKET="my-terraform-state-backup"
# すべてのStateファイルをバックアップ
aws s3 sync s3://my-terraform-state/ s3://$BUCKET/$DATE/ \
--include "*.tfstate"
echo "Backup completed: $BUCKET/$DATE"
セキュリティベストプラクティス
1. Stateファイルの暗号化
# S3 SSE-KMS暗号化(必須)
backend "s3" {
encrypt = true
kms_key_id = "alias/terraform-state"
}
2. IAMポリシーの最小権限
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::my-terraform-state/*"
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-terraform-state"
},
{
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem"],
"Resource": "arn:aws:dynamodb:*:*:table/terraform-state-lock"
}
]
}
3. sensitiveな値の保護
# Stateファイルに機密データが含まれる可能性がある
output "db_password" {
value = aws_db_instance.main.password
sensitive = true
}
# terraform.tfstateから直接確認は可能なため、
# Stateファイル自体のアクセス制御が重要
まとめ
Terraform State管理はIaCの基盤です。重要なポイント:
- Remote Backendは必須: S3/GCS + Lockingでチーム協業を安全に
- Stateの分離: 環境/サービス別にStateを分離してblast radiusを最小化
- バージョン管理: S3 VersioningでStateのリカバリを可能に
- セキュリティ: 暗号化 + IAM最小権限 + sensitiveマーキング
クイズ(6問)
Q1. Terraform Stateの主な役割3つは? リソースマッピング、依存関係の追跡、API呼び出しの最小化(パフォーマンス)
Q2. AWSでState Lockingに使用されるサービスは? DynamoDB
Q3. GCS Backendで別途のLockテーブルが不要な理由は? GCSが自体でObject Lockingをサポートしているため
Q4. terraform force-unlockはいつ使用すべきか? Lockが異常に残っている場合のみ使用。他のユーザーが作業中の場合は使用してはならない
Q5. movedブロックの用途は? リソース名の変更時にStateを自動更新し、リソースの再作成を防止する
Q6. Stateファイルにsensitiveな値が含まれる可能性があるため、どのようなセキュリティ対策が必要か? Stateファイル自体の暗号化(SSE-KMS)とIAMアクセス制御