Terraform信者がAWS CDKをはじめて使ってTerraform Backend(S3 + DynamoDB)環境を構築してみた

Terraform信者にとってAWS CDK上達の近道は、「Terraformで書いてきたコードを変換すること」と社内メンバーに教わったため、土台のTerraform環境設定から初めてみました。
2022.08.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。

今回は、Terraform信者である私がAWS CDKを使ってTerraform Backend環境を構築してみようと思います。

AWS CDK触る前と後の印象

AWS CDKを触ってみる前の私の印象は次の通りでした。

  • 最終的にCloudFormationスタックでデプロイする仕組みは理解していた
  • 「L2, L3コンストラクタが便利!」って聞くけどTerraformのmoduleみたいなものかな?

実際使ってみたところ次の印象を率直に思いました。

  • 今回は、「L2コンストラクタ」を触ってみたがmoduleとはまた違った感覚の「便利すぎ!」を感じた
  • リソース削除にはremovalPolicyが必要な場合がある
    • スタックの削除時にスキップされていったからびっくりした
  • CloudFormationになかったautoDeleteObjectsがあるのとても良い

作ったコード

実際に作ってみたコードは次のコードになります。

「もっと良い書き方あるよ!」といったフィードバックもいただけると大変嬉しいです。

import { CfnOutput, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'

export class TerraformEnvStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const stateBucket = new s3.Bucket(this, 'stateBucket', {
      bucketName: `terraform-state-${this.account}`,
      encryption: s3.BucketEncryption.S3_MANAGED,
      enforceSSL: true,
      versioned: true,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
      
      // 検証のため設定
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true
    })

    const stateLockTable = new dynamodb.Table(this, 'stateLockTable', {
      tableName: `terraform-statelock-${this.account}`,
      partitionKey: {
        name: 'LockID',
        type: dynamodb.AttributeType.STRING
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: RemovalPolicy.DESTROY
    })

    new CfnOutput(this, 'stateBucketName', {
      value: stateBucket.bucketName
    })

    new CfnOutput(this, 'stateLockTableName', {
      value: stateLockTable.tableName
    })
  }
}

触った後の印象を深掘り

先程述べた、AWS CDKを触ってみた印象を深掘りしてみます。

L2コンストラクタ

今回、S3バケットをL2コンストラクタで作成した際にとても便利だなと感じたのでご紹介します。

    const stateBucket = new s3.Bucket(this, 'stateBucket', {
      bucketName: `terraform-state-${this.account}`,
      encryption: s3.BucketEncryption.S3_MANAGED,
      enforceSSL: true,
      versioned: true,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
      
      // 検証のため設定
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true
    })

enforceSSL

enforceSSLプロパティは、Security Hubで定義されているAWS Foundational Security Best PracticesのS3.5をクリアするために使用するプロパティです。

trueまたはfalseで設定し、Terraformに比べて記述量が段違いだったため感動しました。

ちなみにTerraformで記載すると次のような記述になります。

resource "aws_s3_bucket" "stateBucket" {
  bucket = "terraform-state-${data.aws_caller_identity.self.account_id}"
}

data "aws_iam_policy_document" "bucket_policy_enforce_ssl" {
  version = "2012-10-17"

  statement {
    effect = "Deny"
    actions = [ "s3:*" ]
    resources = [
      aws_s3_bucket.stateBucket.arn,
      "${aws_s3_bucket.stateBucket.arn}/*"
    ]
    condition {
      test = "Bool"
      variable = "aws:SecureTransport"
      values = [ false ]
    }
    principals {
      type = "*"
      identifiers = [ "*" ]
    }
  }
}

resource "aws_s3_bucket_policy" "enforce_ssl" {
  bucket = aws_s3_bucket.stateBucket.id
  policy = data.aws_iam_policy_document.bucket_policy_enforce_ssl.json
}

removalPolicy

AWS CDKでは、リソースによって削除時や置き換え時のリソースの削除ポリシーが異なります。

今回作成した、S3やDynamoDBテーブルは、削除ポリシーがデフォルトでRETAINです。そのため、スタック削除時はリソースがDELETION_SKIPPEDで進行し、初めは仕様に気がつかず困惑しました。

削除ポリシーをDeleteに設定するには、removalPolicyで制御する必要がありました。

参考

autoDeleteObjects

CloudFormationの場合、スタック削除時にS3バケットが空でない場合、エラーを返します。

よって、次のブログのようなカスタムリソースを使用したオブジェクトの削除処理を組み込むなどの一工夫が必要でした。

Terraformの場合、オブジェクトも削除するプロパティが用意されていたためCloudFormationも対応しないかなと思っていましたが、AWS CDKではサポートされていました。控えめに言って歓喜です。

ドキュメントに記載はありますが、autoDeleteObjectsをtrueにする際は、removalPolicyで削除ポリシーをDESTROYにする必要があるみたいです。

    const stateBucket = new s3.Bucket(this, 'stateBucket', {
      bucketName: `terraform-state-${this.account}`,
      encryption: s3.BucketEncryption.S3_MANAGED,
      enforceSSL: true,
      versioned: true,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
      
      // 検証のため設定
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true
    })

まとめ

以上、「Terraform信者がAWS CDKをはじめて使ってTerraform Backend(S3 + DynamoDB)環境を構築してみた」でした。

AWS CDKも使えるようになって、IaCおじさんになれるよう頑張っていこうと思います。

以上、AWS事業本部コンサルティング部のたかくに(@takakuni_)でした!