リモートバックエンド管理

この講座では、Terraformのステートファイル管理について学びます。

  • tfstateファイルの役割と重要性
  • ローカル管理の問題点
  • S3をバックエンドとしたリモート管理
  • DynamoDBによるステートロック
  • DynamoDBを使わずにロック管理(use_lockfile)

1. リモートバックエンドとは

これまで terraform apply を実行すると、ディレクトリに terraform.tfstate というファイルが作成されていました。これには現在のインフラの状態が記録されています。

しかし、このファイルをローカル(自分のPC)だけで持っていると、チーム開発で以下のような問題が起きます。

  • 他のメンバーが最新の状態を知ることができず、共有できない。
  • AさんとBさんが同時に変更しようとすると競合して、状態が壊れる可能性がある。
  • PCが壊れたらインフラの状態がわからなくなる紛失リスクがある。

これを解決するために、tfstate ファイルをクラウド上(S3など)で管理する仕組みが「リモートバックエンド」です。

📝 tfstateファイルの取り扱い注意
terraform.tfstate ファイルには、データベースのパスワードなどの機密情報が平文で含まれる場合があります。このファイルをGitリポジトリにコミットしてはいけません。.gitignore*.tfstate*.tfstate.* を追加しておくことを強く推奨します。

これはProtecting sensitive data in the Terraform state file(AWS Prescriptive Guidance公式ドキュメント)でも明示されています。原文(英語表示時)では以下のように述べられています。

Sensitive data is visible in plain text in the Terraform state file.

「Terraformのstateファイル内では機密データが平文で見える」と読み取れます。なお、AWSドキュメントは画面右上の言語セレクタから日本語表示に切り替えることもできます。

2. シンプルなバックエンド管理

まずは最も基本的な、S3バケットにファイルを置くだけの方法です。

2.1 仕組み

terraform.tfstate ファイルを、指定したS3バケットに保存します。これで「共有」と「紛失リスク」が解決します。

2.2 事前準備

S3バケットを手動、または別のTerraformコードであらかじめ作成しておく必要があります(今回は tf-state-bucket-12345 とします)。

作成方法ですが

  • ローカルでterraformにて作成し、あとでリモートバックエンドとして設定
  • AWSマネジメントコンソールで作成し、リモートバックエンドとして設定する(S3自体をTerraformで管理したい場合は、さらに terraform import を行う)

のどちらかの方法が取れます。

2.3 記載方法

main.tfterraform ブロックを追加し、backend "s3" を定義します。

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

  # ここがバックエンドの設定
  backend "s3" {
    bucket = "tf-state-bucket-12345" # 作成済みのバケット名
    key    = "dev/terraform.tfstate" # ファイルのパス(フォルダ構成)
    region = "ap-northeast-1"
  }
}

2.4 設定の適用

バックエンドの設定を変更した際は、必ず初期化が必要です。

terraform init

2.5 問題点

この方法だけだと、「排他制御(ロック)」ができません。 もしチームメンバー2人が同時に terraform apply を実行してしまうと、S3上のファイルが同時に書き換えられ、データが破損する恐れがあります。

そのため、次に説明するロック機能が有効です。

3. DynamoDBでロック管理

AWSを利用する場合の、最も一般的で推奨される構成です。DynamoDBを使って「今、誰かが実行中です」というロック情報を管理します。

3.1 仕組み

DynamoDBによるステートロックの流れを以下に示します。

sequenceDiagram
  participant A as ユーザA
  participant DDB as DynamoDB
  participant S3 as S3(tfstate)
  participant B as ユーザB

  A->>DDB: ロックを取得
  DDB-->>A: ロック成功
  A->>S3: tfstateを読み書き
  B->>DDB: ロックを確認
  DDB-->>B: ロック中(ブロック)
  A->>S3: tfstateを更新
  A->>DDB: ロックを解放

Terraformは実行開始時にDynamoDBに「ロック」を書き込み、終了時に削除します。ロックがある間は、他の人は実行できません。

3.2 事前準備

ロック管理用のDynamoDBテーブルをあらかじめ作成します。制約として、パーティションキー: LockID (String) という名前である必要があります。

DynamoDBテーブルのキー名や、S3バックエンドで利用できる設定項目の一覧は、Terraform公式ドキュメントのBackend Type: s3に記載があります。

3.3 記載方法

バックエンド設定に dynamodb_table を追加します。

terraform {
  backend "s3" {
    bucket = "tf-state-bucket-12345"
    key    = "dev/terraform.tfstate"
    region = "ap-northeast-1"

    # ロック用テーブルを指定
    dynamodb_table = "terraform-state-lock-tbl"
  }
}

これで、安全にチーム開発ができるようになります。

4. DynamoDBを使わずにロック管理

「ロックはしたいけど、わざわざDynamoDBテーブルまで管理したくない」という場合の選択肢です。

これまでAWS環境では「S3 + DynamoDB」が必須でしたが、Terraformの最新バージョン(v1.10以降など)では、S3の機能だけでロックが可能になりました。

S3の「条件付き書き込み(Conditional Writes)」という機能を利用して、DynamoDBなしで安全に排他制御を行います。

4.1 記載方法

use_lockfile のオプションを利用することで、DynamoDBを使わずに、ロック管理ができるようになります。

terraform {
  backend "s3" {
    bucket = "tf-state-bucket-12345"
    key    = "dev/terraform.tfstate"
    region = "ap-northeast-1"

    # S3でロック管理
    use_lockfile = true
  }
}

5. ハンズオン

この手順では、Terraformのステート(状態管理ファイル)を保存するためのS3バケットを作成し、そこへローカルのステートを移行します。

5.1 バックエンド用S3の作成

まずはローカルで state ファイルを管理しながら、リモートバックエンドでの保存先となるS3バケットをTerraformで作成します。

💡 ポイント
ここでのS3バケットの作成は、AWSマネジメントコンソールではなく、Terraformのterraform applyで行います。コンソールで手動作成してしまうと、terraform applyの実行時に「BucketAlreadyOwnedByYou」エラーが発生します。もし手動で作成してしまった場合は、コンソールからバケットを削除してからterraform applyを実行してください。

main.tf に以下を記述してください。

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

# 1. バケット本体
resource "aws_s3_bucket" "tf_state" {
  bucket = "tf-course-state-bucket-unique-name" # ご自身のバケット名にしてください
}

# 2. バージョニング設定
resource "aws_s3_bucket_versioning" "versioning" {
  bucket = aws_s3_bucket.tf_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

このファイルでは、保存先となるS3を作成し、さらにバージョニングを有効にする設定を行っています。state ファイルは誤って削除されたり、不正に更新されると今までの履歴がわからなくなってしまったり、不正なリソース操作が行われる恐れがあるため、バージョニング管理をしておくことが推奨されます。

バージョニングについては、Retaining multiple versions of objects with S3 Versioning(AWS公式ドキュメント)に「Versioning-enabled buckets can help you recover objects from accidental deletion or overwrite.」と明記されており、誤削除・誤上書きからの復旧手段としてバージョニングが有効であることが読み取れます(原文は英語表示時のもので、日本語表示への切り替えは画面右上の言語セレクタから行えます)。

5.2 リソースの作成

apply コマンドを実行し、S3バケットを作成します。

terraform apply

この時点では、Terraformの状態ファイル(terraform.tfstate)はまだパソコンの中(ローカル)にあります。

5.3 バックエンド設定の追加

次に、Terraformの state ファイルの保存先を、先ほど作成したS3バケットに向ける設定を行います。

main.tf に、terraform ブロックを追加します。

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

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

  backend "s3" {
    bucket       = "tf-course-state-bucket-unique-name" # 作成したバケット名
    key          = "dev/terraform.tfstate"
    region       = "ap-northeast-1"

    # DynamoDBを使わずにS3だけでロック管理
    use_lockfile = true
  }
}

# 1. バケット本体
resource "aws_s3_bucket" "tf_state" {
  bucket = "tf-course-state-bucket-unique-name" # ご自身のバケット名にしてください
}

# 2. バージョニング設定
resource "aws_s3_bucket_versioning" "versioning" {
  bucket = aws_s3_bucket.tf_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

5.4 移行の実行(ローカル → S3)

この後初期化を行うと、設定変更を適用し、実際にステートファイルをS3へアップロードします。

terraform init

実行中に以下のような確認メッセージが表示されます。

「ローカルにある tfstate を、新しく設定されたS3バックエンドにコピーしますか?」という意味です。

Do you want to copy existing state to the new backend?
...
Enter a value:

ここで yes と入力してEnterキーを押します。

Terraform has been successfully initialized! と表示されれば完了です。

これにより、手元の terraform.tfstate の内容がS3バケット内の dev/terraform.tfstate に移行されました。

念の為、作成したS3バケットを開き、dev フォルダをひらくと、terraform.tfstate ファイルが作成されていることを確認できます。

5.5 リソースの削除

terraform destroy を実行すると、バケット内にステートファイルが残っているため BucketNotEmpty(バケットが空ではない)というエラーが発生し、削除に失敗します。

これを解決するには、main.tf のS3リソース定義に force_destroy = true を追加します。これによって、Terraformが自動的にバケットの中身を空にしてからバケット自体を削除してくれるようになります。

resource "aws_s3_bucket" "tf_state" {
  bucket        = "tf-course-state-bucket-unique-name"
  force_destroy = true  # この行を追加
}

修正後、以下の手順で削除します。

  1. terraform applyforce_destroy の設定を一度反映させる)
  2. terraform destroy (削除実行)

※注意: 自身のステートファイルを保存しているバケットを自身で削除するため、コマンドの最後の最後で「ステートの更新に失敗しました(バケットがないため)」という旨のエラーが出ることがありますが、リソース自体は正常に削除されます。

💡 ポイント
実務では、バックエンド用のリソース(S3バケット・DynamoDBテーブル)とアプリケーション用のリソースを別のTerraform構成(ディレクトリ)で管理するのが一般的です。こうすることで、アプリケーション側で terraform destroy を実行してもバックエンドのリソースが巻き込まれる心配がなくなります。

6. まとめ

この講座では、Terraformのリモートバックエンド管理について学びました。

  • リモートバックエンドはtfstateファイルをクラウド上で管理する仕組み
  • S3バケットに保存することで、チームでの共有や紛失リスクの軽減が可能
  • DynamoDBを使った排他制御(ロック)で同時実行による競合を防止できる
  • Terraform v1.10以降ではuse_lockfileオプションでDynamoDBなしのロック管理が可能
  • バックエンド設定を変更した際はterraform initでの再初期化が必要