0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TerraformステートをS3+DynamoDBでリモート管理する手順

Last updated at Posted at 2025-11-28

1.はじめに

Terraformステートファイル(terraform.tfstate)は、Terraformが管理するインフラの『現状』を記録したファイルです。Terraformはこのファイルを使って、前回までに作成したインフラの状態(リソース)を管理し、変更が走ればステートファイルと変更内容(コード)を比較してリソースを更新します。

◇ なぜTerraformステートをリモート化すると良いのか

Terraformは、デフォルトではステートファイルをローカルに保存します。
しかしこの場合、チーム内での共有ができず、ローカルPCの破損による消失のリスクもあるため、リモート環境でステートファイルを管理することで、共有化と安全性を確保します。

◇ なぜS3とDynamoDBを採用するのか

S3は耐久性・可用性に優れており、暗号化やアクセス制御もできるため、ステートファイルの保存先として適しています。
また、DynamoDBによるステートロック機能を使うことで、複数人が同時にapplyすることによる、設定の競合を防ぎます。

注意
以前は S3 バックエンドでステートを管理する際、排他制御(ロック)のために DynamoDB が必須でした。
しかし Terraform v1.10 以降では S3 自体がネイティブにロック機能をサポートするようになり、DynamoDB を使わなくてもステートの競合防止が可能になっています。
そのため、今後は DynamoDB を併用する必要は徐々になくなりつつあります。

◇ 前提条件

ステートファイルをリモート管理する手順を始める前に、前提になるものを記します。

  • terraform initterraform apply などのコマンドを実行できる環境 (Windowsなら PowerShell、Linux/Macならターミナル)
  • Terraformを実行するサーバに AWS CLI がインストール済み
    aws --versionで確認可能
  • AWSアカウントの作成
    → IAMユーザーまたはロールに必要な権限(S3、DynamoDB)を付与しておく
  • 認証情報の設定
    • 環境変数(AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
    • または AWS CLI の ~/.aws/credentials に保存

2.必要リソースの作成(S3 & DynamoDB)

・プロバイダ設定

はじめにプロバイダをAWSに指定します。

main.tf
# プロバイダ設定
provider "aws" {
    region = "ap-northeast-1"
}

・S3の設定

S3バケットを作成するTerraformコードを書いて、ローカルにデプロイします。
bucket = ""で定義するS3バケット名はグローバルに一意なものである必要があるため、自分のバケット名に変更してください。

main.tf
# aws_s3_bucketリソースを使って"terraform_state"というS3バケットを作成
resource "aws_s3_bucket" "terraform_state" {
    bucket = "terraform-example-state"
    # 誤ってS3バケットを削除するのを防止
    lifecycle {
        prevent_destroy = true
    }
}

次に、ステートファイルを『世代管理』するための設定を行います。
これにより、誤操作からの復旧やチームでの競合対策、監査・履歴管理が可能になります。

※『世代管理』によりterraform apply を実行するたびに、S3バケットにステートファイルのバージョンが蓄積されていきます。
ただし、ステートファイルは通常1MB未満と非常に小さく、リクエスト数も微小であるため、今回の使用範囲でのコストの心配はほぼ不要です。

main.tf
# バージョニング
resource "aws_s3_bucket_versioning" "enabled" {
    bucket = aws_s3_bucket.terraform_state.id
    versioning_configuration {
        status = "Enabled"
    }
}

続いて、S3バケットを暗号化します。
S3バケットはインターネット経由でアクセス可能なストレージなので、不正アクセスがあっても大丈夫なようにサーバサイド暗号化をデフォルトで有効化しておきます。

main.tf
# デフォルトでサーバサイド暗号化を有効化
resource "aws_s3_bucket_server_side_encryption_configuration" "default" {
    bucket = aws_s3_bucket.terraform_state.id
    rule {
        apply_server_side_encryption_by_default {
            sse_algorithm = "AES256"
        }
    }
}

さらに、S3バケットのパブリックアクセスもブロックすることで二重に保護しておきます。

main.tf
# 明示的にこのS3バケットに対する全パブリックアクセスをブロック
resource "aws_s3_bucket_public_access_block" "public_access" {
    bucket                  = aws_s3_bucket.terraform_state.id
    block_public_acls       = true
    block_public_policy     = true
    ignore_public_acls      = true
    restrict_public_buckets = true
}

・DynamoDBの設定

DynamoDBテーブルを作成するTerraformコードを追記します。
Terraformのステートロック用途ではアクセス頻度が低いため、"PAY_PER_REQUEST"でオンデマンド課金にします。

main.tf
# DynamoDBの作成
resource "aws_dynamodb_table" "terraform_locks" {
    name            = "terraform-up-and-running-locks"
    billing_mode    = "PAY_PER_REQUEST"
    hash_key        = "LockID"

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

ここまでで、S3とDynamoDBの作成に必要なTerraformコードは揃ったので、プロバイダのプラグインをダウンロードしてデプロイします。 main.tfと同じ階層に移動してコマンドを実行します。

>cd <project-directory>
>terraform init
>terraform apply

正常に完了すればリソースが作成されます。

3.ステートファイルをローカル → S3に移動する

リソースを作成すると、ローカル環境にステートファイルが作成されているはずなので、このステートファイルをS3に移行します。

ステートをS3バケットに保存するには、main.tfバックエンド設定 を追記します。
バックエンド設定 とは、「ステート」をどこに保存・管理するかを指定できる仕組みです。

■バックエンド設定の項目

  • bucket → ステートファイルを保存するS3バケット名
  • key → バケット内でステートファイルを保存する「ファイル名/パス」
  • region → バケットが存在するリージョン
  • dynamodb_table
    → ステートロック用のDynamoDBテーブル。単独利用なら省略可能ですが、競合防止のため設定推奨
  • encrypt → ステートがS3に保存される時に暗号化
main.tf
terraform {
    backend "s3" {
        # S3作成時のバケット名を指定する
        bucket          = "terraform-example-state"
        key             = "global/s3/terraform.tfstate"
        region          = "ap-northeast-1"
        # DynamoDB作成時のテーブル名を指定する
        dynamodb_table  = "terraform-up-and-running-locks"
        encrypt         = true
    }
}

バックエンド設定をmain.tfに追記できたら、再度terraform initコマンドを実行します。
すると、ローカルにあるステートファイルをS3バックエンドにコピーするか尋ねられます。

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 state to the new "s3"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes

yesと入力するとTerraformステートがS3に保存されます。
ブラウザでS3の管理コンソールを開いてバケットの中を確認すると、ステートが保存されていることが確認できます。

例)ステートファイルがS3に保存されている
image.png

4.実際に apply して動作確認

バックエンドが有効になっていると、コマンドを実行する前にTerraformは最新のステートをS3バケットから自動的にプルします。また実行したあとは最新のステートをS3バケットに自動的にプッシュします。
試しに以下のようにoutputをTerraformコードに追記して、applyの時の動きを確認してみます。

main.tf
output "s3_bucket_arn" {
    value       = aws_s3_bucket.terraform_state.arn
    description = "The ARN of the S3 bucket"
}

output "dynamodb_table_name" {
    value       = aws_dynamodb_table.terraform_locks.name
    description = "The name of the DynamoDB table"
}
>terraform apply
aws_dynamodb_table.terraform_locks: Refreshing state... [id=terraform-up-and-running-locks]
aws_s3_bucket.terraform_state: Refreshing state... [id=terraform-example-state]
aws_s3_bucket_public_access_block.public_access: Refreshing state... [id=terraform-example-state]
aws_s3_bucket_versioning.enabled: Refreshing state... [id=terraform-example-state]
aws_s3_bucket_server_side_encryption_configuration.default: Refreshing state... [id=terraform-example-state]

Changes to Outputs:
  + dynamodb_table_name = "terraform-up-and-running-locks"
  + s3_bucket_arn       = "arn:aws:s3:::terraform-example-state"

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

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

  Enter a value: 

変更するか尋ねられた状態で、一度DynamoDBの管理コンソールを覗いてみると、ロックレコード(LockID)が存在していると思います。このレコードが存在すれば、別の人がapplyを実行しようとしてもエラーになります。

例)Terraform のロック用のレコードが存在する
image.png

yesを入力して変更を加えます。

 Enter a value: yes


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

Outputs:

dynamodb_table_name = "terraform-up-and-running-locks"
s3_bucket_arn = "arn:aws:s3:::terraform-example-20250930-state"

applyが完了すると、ロックレコードも削除されていると思います。
Outputにも、今回設定したリソースの情報が表示されています。

◇補足

applyを実行したあと、以下のようなレコードが残っている場合があります。
image.png

このレコードは、tfstate 本体の整合性確認用メタデータを記録するもので、次回以降の整合性確認などに用いられるものです。apply後に残っていても問題ないです。(むしろ正常にバックエンドが機能したと解釈できます。)

5.切り戻し手順

ステートファイルをリモート管理する設定を切り戻してリソースを削除したい場合は、今回行った手順を逆にたどれば切り戻せます。

すなわち、

  1. バックエンド設定を削除する
  2. terraform initでローカルにステートを戻す
  3. terraform destroyを実行する

この手順で、ステート管理に使用したS3とDynamoDBのリソースも問題なく削除できます。

6.最後に

⚠️「backend 用の S3・DynamoDB のコードは消さないでください」

main.tfで作成した

  • S3(terraform.tfstate 保存用)
  • DynamoDB(state ロック用)

のリソース定義は、Terraform を安全に使うための基盤です。

EC2 や VPC の構築を始めるために
「バックエンド以外のコードを削除して整理したい」
と思うかもしれないですが、

このバックエンド用リソースのコードだけは絶対に削除しないでください。

Terraform がステートファイルを参照できなくなり、予期せぬ破壊的な変更を招きます。

そのため、
実務では「remote state 用 Terraform」と「実際のリソース作成用 Terraform」はフォルダを分けて管理するのが一般的です。

このステートファイルの分離構成については、改めて別記事でまとめたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?