CloudFrontの継続的デプロイ環境をTerraformで作ってみた

2024.02.15

しばたです。

CloudFrontの継続的デプロイを試すにあたり検証環境をTerraformで作成しました。
その際の知見を踏まえ、簡単なサンプル実装をGitHubに公開したので本記事で紹介します。

GitHubリポジトリ

リポジトリはこちらになります。

リポジトリのコードから環境を作ると下図の構成を実現できます。

簡単な動作確認用なのでオリジンは単一のS3バケットとし、オリジンパスを分けることでプライマリとステージング環境を分離しています。

継続的デプロイの基本と注意事項

CloudFrontの継続的デプロイでは3つのAWSリソースを必要とし、Terraformでは以下のリソースで表現されます。

マネジメントコンソールから継続的デプロイ環境を作る場合、最初にプライマリディストリビューションがありその内容をコピーしてステージングディストリビューションを作る手順になっていますが、これが必須というわけではありません。
最初からステージングディストリビューションを作成することは可能ですしプライマリと異なる設定にしても構いません。
(最終的に昇格すれば設定は同期されるので。なお、ステージング環境では設定不可な項目が存在します。)

このためTerraformでは最初からプライマリ、ステージングディストリビューション両方を作成してやります。
両者の違いはstagingパラメーターで決まり、ステージング環境ではCNAMEの設定ができないといった点に気を付けてやればOKです。

/modules/cloudfront-cd/main.tfより抜粋

// staging = false だとプライマリ
resource "aws_cloudfront_distribution" "primary" {
  // ・・・省略・・・

  staging = false

  // ・・・省略・・・
}

// staging = true だとステージング
resource "aws_cloudfront_distribution" "staging" {
  // ・・・省略・・・

  staging = true

  // ・・・省略・・・

  aliases = null // ステージング環境ではCNAMEは設定不可

  // ・・・省略・・・

続けてデプロイメントポリシーstaging_distribution_dns_namesの設定でステージングディストリビューションと紐づけてやります。

/modules/cloudfront-cd/main.tfより抜粋

resource "aws_cloudfront_continuous_deployment_policy" "this" {
  enabled = true

  // デプロイメントポリシーはステージングディストリビューションと紐づく
  staging_distribution_dns_names {
    items    = [aws_cloudfront_distribution.staging.domain_name]
    quantity = 1
  }

  // ・・・省略・・・
}

最後にプライマリディストリビューションとデプロイメントポリシーを紐づけてやる必要があるのですが、ここで少しハマりました。 

はじめて環境を作ろうとした際にすべてのリソース作成と紐づけを一回にまとめようとしたのですが、

Error: creating CloudFront Distribution: InvalidArgument: Continuous deployment policy is not supported during distribution creation.

というエラーになってしまいました。

どうやらAWS側の仕様でディストリビューションの作成とデプロイメントポリシーの紐づけを同時にできない様 *1で、このため

  1. 各種リソースの作成
  2. プライマリディストリビューションとデプロイメントポリシーの紐づけ

を分けて行う必要があります。

今回のサンプルではlink_deployment_policyという変数を用意し、この変数がtrueの時だけ紐づけを行う様にしました。
最初はlink_deployment_policy = falseで環境を作成し、その後link_deployment_policy = trueにして環境を更新することで対応しています。

/modules/cloudfront-cd/main.tfより抜粋

// link_deployment_policy 変数が true のときだけ紐づけを行う
resource "aws_cloudfront_distribution" "primary" {
  // ・・・省略・・・

  // NOTE: A continuous deployment policy cannot be associated to distribution on creation. Set this argument once the resource exists.
  // see : https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_continuous_deployment_policy
  continuous_deployment_policy_id = var.link_deployment_policy ? aws_cloudfront_continuous_deployment_policy.this.id : null

  // ・・・省略・・・
}

その他パラメーターについては通常のCloudFront作成時と同様です。

ビルド方法

前述の制限があるためビルドは2ステップに分けて行います。

初期状態でlink_deployment_policy = falseにしているので、最初はリポジトリからコードをCloneしてルートディレクトリでterraform initterraform applayを実行すればOKです。

# GitクライアントやTerraformはインストール済みの前提
git clone https://github.com/stknohg/terraform-cloudfront-cd-sample.git
cd ./terraform-cloudfront-cd-sample

# link_deployment_policy = false なことを確認

# 最初のリソース作成
terraform init
terraform apply

これで各種リソースが作成されます。

その後link_deployment_policy = trueに更新してやり、

/main.tf

module "cloudfront" {
  // snip...

  // ここの値を true に更新
  link_deployment_policy = true
}

もう一度terraform applyしてやります。

# 2回目のterraform apply
terraform apply

これでプライマリディストリビューションとデプロイメントポリシーが紐づけられて期待した状態になります。

動作イメージ

手元の環境で試した結果を共有するとこんな感じです。

デプロイメントポリシーが設定されプライマリとステージング環境が紐づいていることが見て取れます。

curlコマンドで両方のディストリビューションにアクセスして動作確認可能です。

# プライマリディストリビューションへのアクセス
$ curl https://d1gmxxxxxxxxxx.cloudfront.net
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>V1 Contents</title>
  </head>
  <body>
    This is /v1/index.html
  </body>
</html>

# ヘッダを付けてステージングディストリビューションへアクセス
$ curl -H 'aws-cf-cd-sample:true' https://d1gmxxxxxxxxxx.cloudfront.net
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>V2 Contents</title>
  </head>
  <body>
    This is /v2/index.html
  </body>
</html>

最後に

以上となります。

デプロイメントポリシーの設定にひと癖ありましたがそれ以外は割と直感的に理解できると思います。
作ったサンプルが皆さんの役に立てば幸いです。

脚注

  1. AWS側の仕様が明記されたドキュメントを見つけることはできませんでした...