
TerraformのS3 BackendをAWS CloudFormationで管理する方法とは?
はじめに

インフラ管理の効率化手法として、Infrastructure as Code(IaC)が挙げられます。IaCとは、コードを使用してインフラの管理を行うことを言います。 以下に示したDevOpsで必要となるデプロイパイプラインの基礎において、IaCは重要なプラクティスとなります。
バージョン管理システムからインフラを繰り返し確実に再現できる
インクリメンタルな変更を行い、それを簡単にデプロイできる
致命的な事故が起きた場合でも、予測可能な形でロールバックできる
クラウドサービスなどのインフラを宣言型構成ファイルで管理するためのIaCツールとしてTerraformが挙げられます。Terraformは管理対象のインフラとその構成に関する状態(State)を保存する必要があります。
Stateを保存するバックエンド(Backend)にS3 Backendを使用する場合、AWS上にS3 Backend用 のリソースを事前に作成する必要があります。そこで本記事では、S3 Backend用のリソース自体をIaCで管理するためにAWS CloudFormationを利用する方法をご紹介いたします。
まず、TerraformのStateとBackendについて簡単に説明し、その後CloudFormationによるS3 Backend用AWSリソースの作成とTerraformでのS3 Backendの使用方法について説明いたします。
Terraformについて
そもそもTerraformとは、IaCを実現する上で必要不可欠と言われるツールです。インフラの構成をコードで宣言し、自動でインフラを管理することができるようになります。
Terraformはオープンソースで提供されている、無料のツールであるのも特徴です。Go言語で構成されており、インフラの更新やプロビジョニングを、コードによって実現します。
Terraformによってインフラをコードで管理できるようになると、いくつかのメリットが期待できます。インフラ構成をコードによって視覚化し、理解度を深めて改善点を把握するようなアプローチも、Terraformで実現可能です。
また、コードで定義されているインフラは変更管理も簡単に行えます。変更がいつどこで実施されたのかを把握したいときに、非常に便利です。
Terraformの「State」と「Backend」とは?

「State」とは
先述のとおり、Terraformは管理対象のインフラとその構成に関する状態(State)を保存する必要があります。 Stateの主な目的は、実際のインフラと構成ファイルで宣言されたインフラのマッピングを格納することです。
Terraformは構成ファイルの変更を適用する際に、Stateに保存された状態と現在の構成ファイルを比較し、実際のインフラにどのような変更を加えるかの計画を作成します。逆にStateファイルが損なわれると、変更計画が丸ごと失われてしまうため、インフラ環境が重大なインシデントにさらされることとなります。Stateファイルを見失わないよう、管理体制を強化することが重要です。
Stateはデフォルトで「terraform.tfstate」という名前のローカルファイルに保存されますが、リモートで保存することもできます。チーム開発でローカルファイルを使用する場合、各ユーザーがTerraformを実行する前に最新のStateを持っていることを確認し、他のユーザーが同時にTerraformを実行しないようにする必要があるため運用が複雑になります。
そのため、チーム開発ではStateをリモートに保存しすべてのチームメンバー間で共有するのがより適切な運用になります。 Stateをリモートに保存するためには構成ファイルでバックエンド(Backend)の設定を行う必要があります。
「Backend」とは
BackendはStateを保存し、Stateをロックする(オプション)役割を担います。 Terraformをどうやって管理するかについての仕組みを支えているのが特徴です。
Backendの主な保存方法は、ローカルディスク上への直接的な保存と、クラウド上に保存する2つのアプローチです。前者は直接State情報をディスクに書き込み、必要に応じて読み込みが行われます。負担の小さい、小規模開発の場合はこの方法で十分対応できるでしょう。
規模の大きな開発の場合は、クラウドへの保存がおすすめです。リモートのストレージ環境にファイルを保存し、必要に応じて接続と読み込みを行うだけでなく、他の関係者との連携も簡単に行えます。
AWS CloudFormationの概要
AWS CloudFormationは、簡単に言えばサーバー構築を自動化することができるサービスです。ITインフラの獲得や設定といったプロビジョニングや、サーバー設定の自動化が行えます。
テンプレートが一度完成してしまえば、以降はそれを使って簡単に設定を行えるようになるため、非常に便利です。詳細な設定が手順として排除され、特別なスキルがなくともサーバーを立ち上げられます。また、手動設定に伴うヒューマンエラーのリスクも回避し、サーバーの品質向上にも貢献できるサービスです。
S3 Backend用 CloudFormation テンプレートとスタックの作成

実際にCloudFormationを使用してS3 Backend用のAWS リソースを作成してみます。
CloudFormationテンプレートの作成
以下に、S3 Backendに必要なAWS リソースを作成するためのテンプレートの例を示します。
AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation template for Terraform S3 backend
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
DynamodbTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: LockID
AttributeType: S
BillingMode: PAY_PER_REQUEST
KeySchema:
- AttributeName: LockID
KeyType: HASH
TerraformS3BackendPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: s3:ListBucket
Resource: !GetAtt S3Bucket.Arn
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
Resource: !Sub ${S3Bucket.Arn}/*
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:DeleteItem
Resource: !GetAtt DynamodbTable.Arn
TerraformOperator:
Type: AWS::IAM::User
Properties:
ManagedPolicyArns:
- !Ref TerraformS3BackendPolicy
TerraformOperatorAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref TerraformOperator
TerraformOperatorAccessKeySecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub ${TerraformOperator}_accessKeys
SecretString: !Sub '{"accessKeyId":"${TerraformOperatorAccessKey}","secretAccessKey":"${TerraformOperatorAccessKey.SecretAccessKey}"}'
Outputs:
TerraformOperatorAccessKeySecret:
Description: Secrets Manager secret of TerraformOperator access keys
Value: !Ref TerraformOperatorAccessKeySecret
S3Bucket:
Value: !Ref S3Bucket
DynamodbTable:
Value: !Ref DynamodbTable
S3BucketはStateを保存するS3 Bucketです。各プロパティについては利用する環境に合わせて適宜修正してください。 DynamodbTableはStateの更新の競合を防ぐためのロックで使用します。Stateのロックが必要ない場合は作成する必要はありません。
作成する場合は以下のプロパティの指定が必須となります。
AttributeDefinitions:
- AttributeName: LockID
AttributeType: S
KeySchema:
- AttributeName: LockID
KeyType: HASH
その他のプロパティについては利用する環境に合わせて適宜修正してください。 S3 Backendに必要なリソースはS3BucketとDynamodbTableのみですが、例ではTerraformで使用するための最小権限を持ったIAMユーザーのアクセスキーも作成しています。
また、Terraform側の設定で必要な情報を出力するためにOutputsを設定しています。
スタックの作成
テンプレートを作成したら、そのテンプレートを使用してスタックを作成します。 スタックを作成することで、テンプレートで定義したAWSリソースを作成することができます。 以下はテンプレートを「tf-backend-s3.yaml」というファイルに保存してある前提で、スタックを作成するためのコマンド例です。
aws cloudformation create-stack \
--stack-name tf-s3-backend \
--template-body file://tf-backend-s3.yaml \
--capabilities CAPABILITY_IAM
テンプレート内でIAM関連のリソースを定義しているため「–capabilities CAPABILITY_IAM」オプションが必要となります。 また、適切な権限を持ったプリンシパルでコマンドを実行する必要があります。 コマンドを実行するとスタックが非同期で作成されます。
作成が完了したら、以下のコマンドでTerraform側の設定で必要な情報を確認できます。
aws cloudformation describe-stacks --stack-name tf-s3-backend
上記コマンドの実行結果の「Outputs」にTerraform側の設定で必要な情報が出力されています。 (出力されていない場合はコマンド実行結果の「StackStatus」が「CREATE_COMPLETE」になっていることを確認してください。)
"Outputs": [
{
"OutputKey": "TerraformOperatorAccessKeySecret",
"OutputValue": "arn:aws:secretsmanager:xxxxxx",
"Description": "Secrets Manager secret of TerraformOperator access keys"
},
{
"OutputKey": "S3Bucket",
"OutputValue": "tf-s3-backend-s3bucket-xxxxxx"
},
{
"OutputKey": "DynamodbTable",
"OutputValue": "tf-s3-backend-DynamodbTable-xxxxxx"
}
]
IAMユーザーのアクセスキーの情報を取得するには以下のコマンドを追加で実行します。
aws secretsmanager get-secret-value \
--secret-id arn:aws:secretsmanager:xxxxxx
以上、TerraformでS3 Backendを使用するためのAWS リソースの作成と、Terraform側の設定で必要な情報の取得が完了しました。
参考サイト
https://www.terraform.io/language/settings/backends/s3
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html
TerraformでのS3 Backendの使用

S3 Backendを設定したルートモジュールの作成
Backendはルートモジュールで設定します。
以下は特にリソースは作成せずに「Hello S3 Backend!」という文字列だけをアウトプットとして出力するルートモジュールです。
terraform {
backend "s3" {
bucket = "tf-s3-backend-s3bucket-xxxxx"
key = "terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "tf-s3-backend-DynamodbTable-xxxxx"
}
}
output "message" {
value = "Hello S3 Backend!"
}
「bucket」、「dynamodb_table」にはCloudFormationスタックから取得した情報を入力します。「region」はスタックを作成したリージョンを入力し、「key」は任意で変更してください。
動作確認
TerraformがS3 Backend用のAWSリソースにアクセスできるように認証情報を設定する必要があります。 Secrets Managerから取得した認証情報を使用して、こちらの資料を参考に認証情報を設定します。 認証情報を設定したら、「main.tf」というファイル名でルートモジュールを作成し、ルートモジュールがあるディレクトリで以下のコマンドを実行します。
terraform init
Terraformの初期化が実行され、backendの初期化も実行されます。 この時点でエラーが発生する場合は認証情報を正しく設定できていないので、認証情報の設定を確認してください。
次に以下のコマンドを実行します。
terraform apply
ルートモジュールで定義した情報を元に、どのような操作を行うかの計画が表示されます。 この時点では実際の操作は行われず、入力待ち状態となります。 まだ「yes」は入力せずに、Stateがロックされているか確認してみます。
別のターミナルで前述の「terraform apply」コマンドを実行します。 「Error: Error acquiring the state lock」が発生し、Stateがロックされていることが確認できます。 この時、DynamoDBのテーブルにはStateのロックのために1レコードが作成されており、以下のコマンドで確認できます。
aws dynamodb scan --table-name tf-s3-backend-DynamodbTable-xxxxxx
ロックの確認ができたので、元のターミナルでプロンプトに「yes」を入力して、Enterを押します。 正常に完了すると、S3 Bucketに「key」で指定した名前でStateが作成されます。 Stateの内容を確認するには以下のコマンドを実行します。
aws s3 cp s3://tf-s3-backend-s3bucket-xxxxx/terraform.tfstate -
ルートモジュールでアウトプットに設定したメッセージが出力されていることが確認できます。 以上、TerraformのStateがS3 Bucketに保存されていることとStateのロックにDynamoDB テーブルが使用されていることが確認できました。
まとめ

本記事では、CloudFormationを使用してTerraformのS3 Backend用のAWSリソースを作成する方法と、TerraformでS3 Backendをどのように使用するかをご紹介しました。TerraformもCloudFormationも、システム構築の自動化においては重要な役割を担うサービスです。コストパフォーマンスにも優れたそれぞれのツールを使いこなせるようになれば、大きな恩恵を受けられるでしょう。
TerraformでS3 Backendを使用する際には参考にしてみてください。
なお、最後に宣伝にはなりますが、弊社はAWSの監視・運用代行サービスをご提供しております。 ご興味のある方は こちらをご覧ください!