
TerraformのState管理を別のAWSアカウントのS3バケットに移行してみた
さがらです。
TerraformのState管理を別のAWSアカウントのS3バケットに移行してみたので、その内容をまとめてみます。
注意事項
本記事では、「移行元のAWSアカウントのS3バケットからローカルにstateをterraform init -migrate-state
で一度退避した後に、移行先のAWSアカウントのS3バケットに対してterraform init -migrate-state
でローカルに一時退避したstateを移行」という流れで行っています。
もっと安全に行うならば、「移行元AWSアカウント・移行先AWSアカウントそれぞれに対して必要な権限を持つIAMロールと信頼関係を設定して、移行元のAWSアカウントのS3バケットから移行先のAWSアカウントのS3バケットに対してterraform init -migrate-state
でstateを移行」という方法もあると思います。(私自身のAWS経験不足もあり、一通り移行を終えた後にこの方法が頭に浮かびました…)
そのため、この記事は「ローカルにstateを一時退避する場合の移行の手順」の参考としてご覧頂けると幸いです。
前提条件
Snowflakeのリソース管理用のTerraformのリポジトリで、Stateを移行していきます。
- 環境分離
environments
フォルダでdev
フォルダとprod
フォルダに分けている- GitHub Actionsを用いたCI/CDも実装済(こちらの記事で行った内容がベース)
- Terraform用のS3バケットやGitHub Actionsから認証させるためのOpenID Connectのプロバイダの管理
- CloudFormationで定義(こちらの記事で行った内容がベース)
- 各リソース名は、移行元・移行先のAWSアカウントどちらでも同じとする
- AWS CLIの認証情報管理
- aws-vaultを使用
- 本記事では、移行元・移行先のprofileについて以下のように記載する
- 移行元のprofile名:
migration-source
- 移行先のprofile名:
migration-target
- 移行元のprofile名:
- 追加で実施すること
- stateファイルのロックをS3だけで行うようにするため、DynamoDBのテーブルは移行先のAWSアカウントでは作成しない
- 使用するTerraformのバージョン
- v1.11.4
移行先のS3バケット・OpenID Connectのプロバイダ・IAMロールの作成
CloudFormationで、移行先のS3バケット・OpenID Connectのプロバイダ・IAMロールの作成をしていきます。
- CloudFormationのコード
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Setup for GitHub Actions with OIDC, including S3 and IAM Role'
Parameters:
S3BucketName:
Type: String
Default: terraform-state-bucket
Description: Bucket name for state file
GitHubAccountName:
Type: String
Default: youraccountname
Description: GitHub account name of the repository
GitHubRemoteRepoName:
Type: String
Default: youreponame
Description: Repository name of terraform
PolicyName:
Type: String
Default: GitHubActionsPolicy
Description: Policy for GitHub Actions
Resources:
# S3 Bucket for Terraform state
TerraformStateBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref S3BucketName
VersioningConfiguration:
Status: Enabled
# OIDC Provider for GitHub Actions
GitHubOIDCProvider:
Type: AWS::IAM::OIDCProvider
Properties:
Url: 'https://token.actions.githubusercontent.com'
ClientIdList:
- 'sts.amazonaws.com'
ThumbprintList:
- '6938fd4d98bab03faadb97b34396831e3780aea1'
# IAM Role for GitHub Actions
GitHubActionsRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Federated: !GetAtt GitHubOIDCProvider.Arn
Action: 'sts:AssumeRoleWithWebIdentity'
Condition:
StringLike:
token.actions.githubusercontent.com:sub: !Sub "repo:${GitHubAccountName}/${GitHubRemoteRepoName}:*"
Policies:
- PolicyName: !Ref PolicyName
PolicyDocument:
Version: '2012-10-17'
Statement:
# state関係の権限(S3のみ)
- Effect: Allow
Action:
- 's3:ListBucket'
- 's3:GetBucketLocation'
- 's3:ListBucketMultipartUploads'
- 's3:ListBucketVersions'
Resource:
- !Sub 'arn:aws:s3:::${TerraformStateBucket}'
- Effect: Allow
Action:
- 's3:GetObject'
- 's3:PutObject'
- 's3:DeleteObject'
Resource:
- !Sub 'arn:aws:s3:::${TerraformStateBucket}/*'
# IAMロール作成の権限(任意:Snowflake関連でIAM管理が必要な場合があるため)
- Effect: Allow
Action:
- 'iam:CreateRole'
- 'iam:DeleteRole'
- 'iam:GetRole'
- 'iam:PutRolePolicy'
- 'iam:DeleteRolePolicy'
- 'iam:AttachRolePolicy'
- 'iam:DetachRolePolicy'
- 'iam:UpdateRole'
- 'iam:UpdateRoleDescription'
- 'iam:TagRole'
- 'iam:UntagRole'
- 'iam:ListRolePolicies'
- 'iam:ListAttachedRolePolicies'
Resource: 'arn:aws:iam::*:role/*'
# IAMポリシー作成の権限(任意:Snowflake関連でIAM管理が必要な場合があるため)
- Effect: Allow
Action:
- 'iam:CreatePolicy'
- 'iam:DeletePolicy'
- 'iam:GetPolicy'
- 'iam:GetPolicyVersion'
- 'iam:ListPolicyVersions'
- 'iam:CreatePolicyVersion'
- 'iam:DeletePolicyVersion'
- 'iam:SetDefaultPolicyVersion'
Resource: 'arn:aws:iam::*:policy/*'
# S3イベント通知設定の権限(任意:Snowflake関連でS3イベント通知設定をする場合があるため)
- Effect: Allow
Action:
- 's3:PutBucketNotification'
- 's3:GetBucketNotification'
- 's3:PutBucketNotificationConfiguration'
- 's3:GetBucketNotificationConfiguration'
Resource: 'arn:aws:s3:::*'
Outputs:
TerraformStateBucketName:
Description: "Terraform state bucket name"
Value: !Ref TerraformStateBucket
GitHubActionsRoleArn:
Description: "IAM Role ARN for GitHub Actions"
Value: !GetAtt GitHubActionsRole.Arn
GitHubOIDCProviderArn:
Description: "OIDC Provider ARN"
Value: !GetAtt GitHubOIDCProvider.Arn
- 参考:CLIからのデプロイコマンド
aws-vault exec migration-target -- \
aws cloudformation deploy \
--template-file cfn/create_state_gha_resources.yaml \
--stack-name terraform-state-cicd-resources \
--capabilities CAPABILITY_NAMED_IAM
dev環境の移行
まず、dev環境の移行から行っていきます。移行作業中に他の方がTerraformの変更をかけようとするとstateの内容がズレてしまうので、注意しましょう。
backend.tf
を変更し、一時的にローカルでstate管理するように設定
devフォルダのbackend.tf
を、下記のように変更します。先の工程を見越して2つのbackendを記載して、片方をコメントアウトしながら進めていきます。
S3についてはuse_lockfile = true
とすることで、S3だけでstateロックが行えるようになります。
terraform {
# 移行先のS3バケット
# backend "s3" {
# bucket = "terraform-state-bucket-sagara"
# key = "snowflake-dev/snowflake-dev.tfstate"
# region = "ap-northeast-1"
# use_lockfile = true
# encrypt = true
# }
# 一時退避用のローカル
backend "local" {
path = "snowflake-dev.tfstate"
}
}
terraform init -migrate-state
の実行
移行元のprofileを使ってdevフォルダにて以下のコマンドを実行します。
aws-vault exec migration-source -- terraform init -migrate-state
すると下図のように移行元のS3バケットにあるstateをローカルにコピーしてそれを新しいローカルのstateとしてよいか確認がされますので、yes
にしてローカルにstateをコピーします。
backend.tf
を変更し、移行先のAWSアカウントのS3でstate管理するように設定
devフォルダのbackend.tf
を、下記のように移行先のAWSアカウントのS3バケットでstate管理するように変更します。
terraform {
# 移行先のS3バケット
backend "s3" {
bucket = "terraform-state-bucket-sagara"
key = "snowflake-dev/snowflake-dev.tfstate"
region = "ap-northeast-1"
use_lockfile = true
encrypt = true
}
# 一時退避用のローカル
# backend "local" {
# path = "snowflake-dev.tfstate"
# }
}
terraform init -migrate-state
の実行
移行先のprofileを使ってdevフォルダにて以下のコマンドを実行します。
aws-vault exec migration-target -- terraform init -migrate-state
すると下図のように、ローカルにあるstateを移行先のS3バケットにコピーしてそれを新しいstateとしてよいか確認がされますので、yes
にして移行先のS3にコピーします。
この後S3バケットを見ると、指定したフォルダとstateファイルが作られていることがわかります。
動作確認
念の為、terraform plan
を実行して差分がないことを確認しておきます。
aws-vault exec migration-target -- terraform plan
下図のように表示されたので、問題なさそうです。
prod環境の移行
次にprod環境の移行を行っていきます。移行作業中に他の方がTerraformの変更をかけようとするとstateの内容がズレてしまうので、注意しましょう。
GitHub Actions向けのIAMロールのARNの変更以外は、dev環境で行った時と同じ内容になります。
backend.tf
を変更し、一時的にローカルでstate管理するように設定
prodフォルダのbackend.tf
を、下記のように変更します。先の工程を見越して2つのbackendを記載して、片方をコメントアウトしながら進めていきます。
terraform {
# 移行先のS3バケット
# backend "s3" {
# bucket = "terraform-state-bucket-sagara"
# key = "snowflake-prod/snowflake-prod.tfstate"
# region = "ap-northeast-1"
# use_lockfile = true
# encrypt = true
# }
# 一時退避用のローカル
backend "local" {
path = "snowflake-prod.tfstate"
}
}
terraform init -migrate-state
の実行
移行元のprofileを使ってprodフォルダにて以下のコマンドを実行します。
aws-vault exec migration-source -- terraform init -migrate-state
すると下図のように移行元のS3バケットにあるstateをローカルにコピーしてそれを新しいローカルのstateとしてよいか確認がされますので、yes
にしてローカルにstateをコピーします。
backend.tf
を変更し、移行先のAWSアカウントのS3でstate管理するように設定
devフォルダのbackend.tf
を、下記のように移行先のAWSアカウントのS3バケットでstate管理するように変更します。
terraform {
# 移行先のS3バケット
backend "s3" {
bucket = "terraform-state-bucket-sagara"
key = "snowflake-prod/snowflake-prod.tfstate"
region = "ap-northeast-1"
use_lockfile = true
encrypt = true
}
# 一時退避用のローカル
# backend "local" {
# path = "snowflake-dev.tfstate"
# }
}
terraform init -migrate-state
の実行
移行先のprofileを使ってprodフォルダにて以下のコマンドを実行します。
aws-vault exec migration-target -- terraform init -migrate-state
すると下図のように、ローカルにあるstateを移行先のS3バケットにコピーしてそれを新しいstateとしてよいか確認がされますので、yes
にして移行先のS3にコピーします。
この後S3バケットを見ると、指定したフォルダとstateファイルが作られていることがわかります。
動作確認
念の為、terraform plan
を実行して差分がないことを確認しておきます。
aws-vault exec migration-target -- terraform plan
下図のように表示されたので、問題なさそうです。
GitHub Actions用にIAMロールのARNを登録
GitHub ActionsでCI/CDを実行しているため、GitHub Actions用にIAMロールのARNを登録します。
CloudFormationで作成したIAMロールのARNを確認します。
その上で、GitHubで対象のリポジトリのSecrets and variablesの画面で、元々IAMロールのARNを設定していたsecretを上書きします。
動作確認
今回backend.tf
などのコードを変更していたので、その変更をGitHubに反映させるためにプルリクエストを発行したついでに動作確認を行います。
下図のように、プルリクエスト発行時のterraform plan
、マージ後のterraform apply
どちらも問題なく動いたことがわかります。