Workspace使用のローカル管理Terraform StateファイルをS3に移行してみる(backend-configオプションパターン)

Workspace使用のローカル管理Terraform StateファイルをS3に移行してみる(backend-configオプションパターン)

Clock Icon2024.10.08

Terraform Workspaceを使ってローカルでStateファイルを管理している環境で、S3にStateファイルを移行することがありました。

ハマりどころがあったので、ブログにしてみました。

前提

  • ローカルでStateファイルを管理している状態で、リモートバックエンド(S3)に移行をしようとしている
  • ディレクトリ構成は、tfvarsで環境差異を分けているパターン
  • Terraform Workspaceで環境を切り替えている(※)

コードベースの構造と組織のベストプラクティス - AWS 規範ガイダンス

├── data.tf
├── envs
│   ├── dev
│   │   └── terraform.tfvars
│   ├── prod
│   │   └── terraform.tfvars
│   └── test
│       └── terraform.tfvars
├── locals.tf
├── main.tf
├── outputs.tf
├── providers.tf
├── README.md
├── terraform.tfvars
├── variables.tf
└── versions.tf

※環境切り替えのイメージ

$ terraform workspace select dev #dev環境に切り替え
$ terraform plan -var-file=envs/dev/terraform.tfvars  #dev環境に対してplan
$ terraform apply -var-file=envs/dev/terraform.tfvars  #dev環境に対してapply
$ tree terraform.tfstate.d # Stateファイルの保存場所                       
terraform.tfstate.d
├── dev
│   └── terraform.tfstate
└── prod
    └── terraform.tfstate

バックエンド用のリソースを作成

バックエンド用のS3バケットとDynamoDBテーブルを用意します。

S3はStateファイルの置き場、DynamoDBはStateファイルの排他制御に使われます。(apply等の競合を防ぐ)

バックエンド用のリソースはCloudFormationで作成します。

backend.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: S3 & DynamoDB for Terraform Backend

Parameters:
  Name:
    Description: |
      Name of the S3 bucket, DynamoDB table;
      Defaults to "terraform-state-AWS_ACCOUNT_ID"
    Default: ""
    Type: String

Conditions:
  GenerateNames: !Equals [!Ref Name, ""]

Resources:
#------------------------------------------------------------------------------#
# S3
#------------------------------------------------------------------------------#
  StateBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !If
      - GenerateNames
      - !Sub "terraform-state-${AWS::AccountId}"
      - !Ref Name
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
#------------------------------------------------------------------------------#
# DynamoDB
#------------------------------------------------------------------------------#
  LockTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Ref StateBucket
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      AttributeDefinitions:
        - AttributeName: "LockID"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "LockID"
          KeyType: "HASH"

CloudFormationスタックを作成します。Stack nameは任意の名前を設定してください。

ParamatersのNameに設定した値が、S3バケット名とDynamoDBテーブル名になります。

デフォルトは、terraform-state-AWS_ACCOUNT_IDです。必要に応じて設定してください。

CloudFormation_-_Create_Stack.png

スタックの作成が完了すると、S3バケットとDynamoDBテーブルが作成されます。

CloudFormation_-_Stack_terraform-backend-20241013.png

Tips: State管理用のリソースを1つのAWSアカウントに集約するする

本ブログでは、Terraformで管理するAWSアカウント毎に上記のCloudFormationスタックを作成する想定です。

別のアカウントのS3バケットを利用して、State管理用のリソースを1つのAWSアカウントで集約することも可能です。

詳細は以下をご確認ください。

Backend Type: s3 | Terraform | HashiCorp Developer

バックエンド用設定ファイルの用意

バックエンド用の設定ファイルを用意します。

envs/<環境名>/terraform.tfbackend
bucket         = <CloudFormaitonで作成したS3バケット名> # デフォルト: terraform-state-AWS_ACCOUNT_ID
region         = "ap-northeast-1"
key            = "<システム名>/terraform.tfstate"
dynamodb_table = <CloudFormaitonで作成したDynamoDBテーブル名> # デフォルト: terraform-state-AWS_ACCOUNT_ID

State管理用のS3バケットやDynamoDBテーブルの名前を記述します。(前述のCloudFormaitonを使った場合は、S3バケットとDynamoDBテーブルの名前は同一になります)

このファイルは環境毎に用意します。

StateファイルをS3に移行

ローカルのStateファイルをS3に移行します。

以下の手順で進めます。

  1. terraform.tfstate.dをコピーする
  2. Workspace設定を削除する
  3. tfファイルにバックエンド設定を追加する
  4. 環境のstateファイルをルートディレクトリに移動する
  5. terraform initを実行してS3バックエンドに移行
  6. 環境分繰り返す
  7. terraform.tfstate.d.bk削除

1. terraform.tfstate.dをコピーする/2. Workspace設定を削除する

StateファイルをS3に移行するには、Workspace設定の削除が必要です。

Workspace設定を削除するとStateファイルも削除されるため、コピーを作成します。

terraform workspaceを使っているとterraform.tfstate.d配下に各workspaceのStateファイルが保存されます。

$ cp -r terraform.tfstate.d terraform.tfstate.d.bk

workspaceの設定を削除します。先程のコピー元ディレクトリを消すと設定も消えます。

$ rm -rf terraform.tfstate.d/ # 削除
$ terraform workspace list # 削除の確認・defaultだけ表示されればOK                        
* default

3. tfファイルにバックエンド設定の追加

tfファイルのterraformブロックに以下を追加します。

terraform {
  required_version = "~> 1.9.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.67.0"
    }
  }
  backend "s3" {} # 追加
}

4.対象環境のStateファイルをルートディレクトリに移動

対象環境のStateファイルをルートディレクトリにコピーします。

$ cp ./terraform.tfstate.d.bk/<環境名>/terraform.tfstate .

5. terraform initを実行してS3バックエンドに移行/6. 環境分繰り返す

terraform initコマンドを実行して、S3に移行します。

$ terraform init -backend-config="envs/<環境名>/terraform.tfbackend"

この時点で、以下の状態になります。

  • ローカルのStateファイル(terraform.tfstate)は空になる
  • S3上にStateファイルが保存されている

これで移行の一連の作業は完了です。あとは、「4」~「5」の作業を環境分繰り返すだけです。

2環境目移行は、terraform init時に以下警告がでることがあります。

Initializing the backend...
╷
│ Error: Backend configuration changed
│ 
│ A change in the backend configuration has been detected, which may require migrating existing
│ state.
│ 
│ If you wish to attempt automatic migration of the state, use "terraform init -migrate-state".
│ If you wish to store the current configuration with no changes to the state, use "terraform init
│ -reconfigure".

バックエンド切り替え時に、-reconfigureオプションをが必要です。

メッセージ通りオプションを追加して、terraoform initを実行します。

$ terraform init -backend-config="envs/<環境名>/terraform.tfbackend" -reconfigure

7. terraform.tfstate.d.bk削除

移行がすべて完了したら、terraform.tfstate.d.bkは不要なため削除します。

$ rm -rf terraform.tfstate.d.bk

Tips: なぜWorkspaceを削除してから、S3バックエンドに移行するのか

Workspaceが存在すると、1つのバックエンドに全環境分が移動してしまうからです。

Workspaceは単一バックエンドを前提とした機能です。そのため、1つ1つのWorkspaceを移動することができません。

具体的にどんな動作になるのか見ていきましょう。

$ terraform workspace select prod # workspace prodを設定
$ terraform init -backend-config="envs/prod/terraform.tfbackend" # s3バックエンドへの移行

以下のメッセージが表示されます。

Initializing the backend...
Do you want to migrate all workspaces to "s3"?
  Both the existing "local" backend and the newly configured "s3" backend
  support workspaces. When migrating between backends, Terraform will copy
  all workspaces (with the same names). THIS WILL OVERWRITE any conflicting
  states in the destination.

  Terraform initialization doesn't currently migrate only select workspaces.
  If you want to migrate a select number of workspaces, you must manually
  pull and push those states.

  If you answer "yes", Terraform will migrate all states. If you answer
  "no", Terraform will abort.

  Enter a value: 

When migrating between backends, Terraform will copy
all workspaces (with the same names). THIS WILL OVERWRITE any conflicting
states in the destination.

注目してほしいのはこの部分です。すべてのWorkspaceが1つのバックエンドにコピーされるようです。

環境毎にバックエンド用のリソースは分けているので、困りますね。

あえて「yes」を押して、実行してみます。

S3バケットの中身がどう変わるか見てみます。

# 実行前
$ aws s3 ls --recursive <バックエンド用S3バケット> # S3バケットの中身は空
# 実行後
$ aws s3 ls --recursive <バックエンド用S3バケット>
2024-10-07 16:28:49    1.5 KiB env:/dev/prod/terraform.tfstate
2024-10-07 16:28:51    1.5 KiB env:/prod/prod/terraform.tfstate

移行後をtree形式で表示したものが以下です。

env:
├── dev
│   └── prod
│       └── terraform.tfstate
└── prod
    └── prod
        └── terraform.tfstate

Workspaceで選択したprodだけではなく、devも移行されていますね。

ローカルのStateファイルはどちらも(dev/prod)空になっていました。

このようにWorkspace設定が残っていると、バックエンドを分けた移行が困難です。

参考

https://developer.hashicorp.com/terraform/language/backend/s3

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.