Terraformで遊ぶPart2~S3+CloudFrontで静的サイトホスティング

S3 + CloudFront を使った静的サイトホスティングを、Terraform を使って構築してみます。

全体像

  1. S3バケットを作り、静的サイトホスティング設定
    • バケットに静的コンテンツ(HTML, CSS, JS など)を配置
    • Websiteホスティングを有効化
  2. CloudFrontを利用してS3のコンテンツをCDN配信
    • S3をオリジン(Origin)としたCloudFrontのDistributionを作成
  3. Terraformの構成管理
    • コードでAWSリソースを定義
    • Terraformでモジュール化し、再利用性を高める

今回はパブリックアクセスブロックを解除せずに、S3バケットを private のままでCloudFront 経由にのみ静的ファイルを配信する構成をTerraform を使って構築します。
この構成により、誤ってバケットをインターネットに全公開してしまうリスクを大幅に低減できます。
また、Terraformのモジュール化により、S3 と CloudFront のリソース定義を整理して再利用しやすい形にしています。

事前準備

  • AWSアカウントの作成と初期設定(AWS CLI のインストールや IAM ユーザー作成など)
  • Terraformのインストール(バージョンは記事執筆時点では v1.4.x など)

参考レポジトリ

GitHub - 0ni4/terraform-playground-s3-and-cloudfront: This is playground with s3 and cloudfront
This is playground with s3 and cloudfront. Contribute to 0ni4/terraform-playground-s3-and-cloudfront development by creating an account on GitHub.

こちらをgit pullして使います

ディレクトリ構成イメージ

moduleにして可読性を高めています。

.
├─ main.tf # 全体のエントリーポイント
├─ variables.tf # 変数定義
├─ outputs.tf # 出力(Outputs)定義
├─ modules
│ ├─ s3
│ │ ├─ main.tf
│ │ ├─ variables.tf
│ │ └─ outputs.tf
│ ├─ cloudfront
│ │ ├─ main.tf
│ │ ├─ variables.tf
│ │ └─ outputs.tf
│ └─ route53
│ ├─ main.tf
│ ├─ variables.tf
│ └─ outputs.tf
└─ ...

Terraform解説

トップレベルのファイル

main.tf

  • module “s3_website”

バケット名や、index.html の名前などを設定

また、CloudFrontで使用する OAI の CanonicalUserID を取得するために module.cloudfront.oai_canonical_user_id を参照しています。

  • module “cloudfront”

S3バケットのリージョナルドメイン名をオリジンとして設定

CloudFrontはOAIを使ってS3へプライベートアクセスします。

variables.tf

今回は主要な変数として、S3バケット名・index.html・error.html を指定

これらにデフォルト値を入れてもOKですが、バケット名はユニークである必要があるため、実行時に .tfvars などで指定するのが一般的です。

outputs.tf

terraform output コマンドで確認できるようになっています。

cloudfront_domain_name に出力される値(例: d111111abcdef8.cloudfront.net) が、実際にブラウザでアクセスするURLになります。

S3モジュール

modules/s3 ディレクトリ配下に、バケット本体やウェブサイト設定、バケットポリシーを定義します。ここではバケットを privateに保ったまま、CloudFront OAIにのみ s3:GetObject を許可します。

main.tf

S3 を完全に privateに設定したいため、aws_s3_bucket_aclpublic-read などを指定せず、Block Public Access 設定を維持したままでもエラーになりません。

ウェブサイトホスティング設定 (aws_s3_bucket_website_configuration) は、S3のウェブサイトエンドポイントを有効化しますが、直接アクセスは原則403になる場合が多いです。

  • 実際の配信は CloudFront ドメイン経由
  • これで静的サイトのトップページなどが index.htmlerror.html となることを指定

cloudfrontモジュール

modules/cloudfront ディレクトリでは、OAI (Origin Access Identity) を作り、CloudFront Distributionと紐付けます。

main.tf

  • aws_cloudfront_origin_access_identity
    • S3バケットへプライベートアクセスするためのIDを作成
  • aws_cloudfront_distribution
    • S3をオリジンに設定 (s3_domain_name)
    • origin_access_identity に、上記のOAIを指定する
  • viewer_protocol_policy = "redirect-to-https"
    • HTTP でアクセスしても HTTPS にリダイレクト

実行手順

  • terraform init
    • 使用するプロバイダ(AWSなど)をダウンロード
  • terraform plan
    • どのリソースが作成(変更)されるかを確認
    • bucket名を入力してくださいというダイアログが出てくると思いますが、tfvarsファイルを作ってそこにbucket名を指定してあげると便利です
  • terraform apply
    • 作成を実行し、数分待つ(CloudFrontの反映に数分~10分程度かかることも)

Terraformが完了すると、以下のように出力が得られます。

Apply complete! Resources: X added, 0 changed, 0 destroyed.

Outputs:

cloudfront_distribution_id = "EABCDEFG12345"
cloudfront_domain_name = "d1abc2xyz.cloudfront.net"
s3_bucket_name = "my-unique-private-bucket"

S3やcloudfrontのダッシュボードを見に行くと以下のようにしっかり作成されておりました。

動作確認

  • S3バケットに index.html, error.html などをアップロード
    • index.htmlがレポジトリにありますのでお使い下さい
  • ブラウザで https://<CloudFrontドメイン>/ にアクセス(terraformの実行時に出力されたcloudfront_domain_name )
    • 例: https://d1abc2xyz.cloudfront.net
    • 数分かかる場合がありますが、S3上の index.html が表示されれば成功です。(レポジトリのものを使っていれば以下画像のようになります。

S3のウェブサイトエンドポイントに直接アクセスしても、403エラーになる(あるいはブロックされる)はずです。これはバケットが完全privateで、パブリックアクセスがブロックされている証拠です。

後片付け

動作確認が終わったらterraform destroyを忘れずに!!

バケットを空にしないとバケットは削除できないのでまずindex.htmlを消してからdestroyしてください

補足

  1. パブリックアクセスブロックがアカウントレベルで有効
    • この構成なら解除しなくても大丈夫です。誤って public-read ACL やパブリックポリシーを設定しようとしてもエラーになりますが、今回のようにすべて privateにしている分には問題ありません。
  2. OAI (Origin Access Identity) と OAC (Origin Access Control)
    • 2022年以降、OAC という新しい仕組みも登場しています。OAI が標準的ですが、将来的にOACへ移行するケースも増えるかもしれません。
    • OACを使う場合は設定方法が少し異なりますが、**「バケットをprivateに保ち、CloudFrontだけがアクセスできる」**という基本コンセプトは同じです。
  3. S3の静的ウェブサイトホスティング自体は、パブリックアクセス前提で設計されている面があります。
    • バケットに website ブロックや aws_s3_bucket_website_configuration を設定しても、バケットそのものがprivateだと、ウェブサイトエンドポイントの直接アクセスは403になる場合が多いです。
    • 実際にはCloudFront経由でサイトを公開するため、問題ありません。
  4. キャッシュ無効化など
    • CloudFrontのキャッシュ設定やInvalidationについては別途検討してください。
    • 変更が頻繁な場合は、キャッシュ期限を短くしたり、Terraform外で aws cloudfront create-invalidation を実行したりする運用が考えられます。

まとめ

  • S3をprivateにし、Origin Access Identity (OAI) を使ってCloudFrontのみがS3にアクセスできるようにすることで、パブリックアクセスブロックを解除せずに安全に静的コンテンツを配信できます。
  • Terraformモジュール化により、S3 と CloudFront の定義を分割しつつ、トップレベルの main.tf でシンプルに呼び出す構造にするのがおすすめ。
  • 実行手順terraform init -> plan -> apply の通常フローでOKですが、CloudFrontのデプロイには数分~10分程度かかることを理解しておきましょう。
  • 出来上がったCloudFrontドメインにアクセスすれば、S3バケットを直接公開せずともHTTPSで静的サイトを配信できます。

というわけでえーあいさんに教えてもらいながら進めてみました。terraformのモジュールの書き方なども学んだのでもっとterraformについて知識を深めてみようと思います。

タイトルとURLをコピーしました