OPS

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

2022.03.10

本記事のポイント

Terraformとは、クラウドサービスなどのインフラを宣言型構成ファイルで管理するためのオープンソースのInfrastructure as Code(IaC)ツールです。

本記事では、Terraformの状態(State)を保存するバックエンド(Backend)にS3 Backendを使用するための準備として、AWS CloudFormationを利用してS3 Backend用のAWSリソースを作成する方法をご紹介いたします。

 

はじめに

Infrastructure as Code(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の使用方法について説明いたします。 > AWS の「システム運用代行」詳細はこちら

Terraformの「State」と「Backend」とは?

「State」とは

先述のとおり、Terraformは管理対象のインフラとその構成に関する状態(State)を保存する必要があります。 Stateの主な目的は、実際のインフラと構成ファイルで宣言されたインフラのマッピングを格納することです。 Terraformは構成ファイルの変更を適用する際に、Stateに保存された状態と現在の構成ファイルを比較し、実際のインフラにどのような変更を加えるかの計画を作成します。 Stateはデフォルトで「terraform.tfstate」という名前のローカルファイルに保存されますが、リモートで保存することもできます。チーム開発でローカルファイルを使用する場合、各ユーザーがTerraformを実行する前に最新のStateを持っていることを確認し、他のユーザーが同時にTerraformを実行しないようにする必要があるため運用が複雑になります。 そのため、チーム開発ではStateをリモートに保存しすべてのチームメンバー間で共有するのがより適切な運用になります。 Stateをリモートに保存するためには構成ファイルでバックエンド(Backend)の設定を行う必要があります。

「Backend」とは

BackendはStateを保存し、Stateをロックする(オプション)役割を担います。 BackendはデフォルトのローカルBackend以外にリモートBackendとして各種クラウドサービスのオブジェクトストレージなどが選択できます。 利用可能なリモートBackendはこちらのサイト でご確認ください。

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でS3 Backendを使用する際には参考にしてみてください。 なお、最後に宣伝にはなりますが、弊社はAWSの監視・運用代行サービスをご提供しております。 ご興味のある方は こちらをご覧ください! > AWS の「システム運用代行」詳細はこちら