TerraformでReact+S3+CloudFront環境構築

AWS

今回やること

Terraformを使用して、以下のインフラを構築し、Reactアプリケーションをホスティングする環境を作ります。

  • S3: Reactアプリケーションをホストする静的ウェブサーバー。
  • CloudFront: コンテンツ配信を最適化し、セキュリティと高速化を提供。

S3とは?

S3 (Simple Storage Service) は、AWSが提供するオブジェクトストレージサービスです。以下の特徴を持っています:

  • 静的ウェブホスティングが可能(HTML、CSS、JavaScriptなどを公開)。
  • 高い耐久性と可用性を備えたデータ保存。
  • アクセス管理が柔軟で、CloudFrontとの連携も容易。
AWS S3によるファイルストレージの設定【エンジニア初心者向けガイド】
AWS S3バケット作成からファイルアップロード、アクセス管理、バージョニングまで、エンジニア初心者向けにわかりやすく解説します。

CloudFrontとは?

CloudFrontは、AWSのCDN(Content Delivery Network)サービスです。以下のメリットがあります:

  • 低遅延: グローバルなエッジロケーションを利用して、ユーザーに近い場所からデータを配信。
  • セキュリティ: DDoS攻撃から保護し、HTTPSによる暗号化通信をサポート。
  • S3と統合: S3バケットからコンテンツを安全かつ効率的に配信。
初心者エンジニア向け CloudFrontによるコンテンツ配信とセキュリティ概要
AWS CloudFrontでのコンテンツ配信を最適化し、SSL/TLS証明書によるセキュリティを強化する設定方法を解説。キャッシュ管理やオリジン設定も詳しく紹介します。

Reactの環境構築

Reactアプリのセットアップ

下記記事を参考にセットアップしてください。

Viteを使ってReactプロジェクトを作成する方法
Viteを使ってReactプロジェクトを手軽に構築する方法を解説。初期設定や生成されたファイル構成についても詳しく説明しています。

ビルドファイルの作成

下記コマンドを実行してビルドファイルの生成です。

Bash
npm run build

build/フォルダにHTML、CSS、JavaScriptなどの静的ファイルが出力されます。

GithubActionsの設定

mainにpushした場合にs3にアップロードするようにしています。
事前に下記シークレットキーを登録してください。

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • S3_BUCKET_NAME
YAML
name: Deploy to S3

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
    # 1. リポジトリのチェックアウト
    - name: Checkout repository
      uses: actions/checkout@v3

    # 2. Node.jsのセットアップ
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: 16

    # 3. パッケージのインストールとビルド
    - name: Install dependencies and build
      run: |
        npm install
        npm run build

    # 4. AWS CLIの設定
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v3
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-1

    # 5. S3へのファイル同期
    - name: Sync files to S3
      run: |
        aws s3 sync ./dist s3://${{ secrets.S3_BUCKET_NAME }} --delete

S3の環境構築

ここでは、Terraformを使用してAWS S3バケットを作成し、Reactアプリケーションの静的ファイルをホストする環境を構築します。

流れは下記です。

  1. S3バケットの作成
    • Reactアプリの静的ファイルをホストするためのバケットを作成。
    • バケットに適切なタグを付与し、環境ごとに識別可能に。
  2. パブリックアクセスの設定
    • 一般的なパブリックアクセス制御を無効化し、CloudFront経由での安全なアクセスを確保。
  3. バケットポリシーの適用
    • CloudFrontからのアクセスのみ許可するセキュリティポリシーを設定。

S3バケットの作成

HCL
resource "aws_s3_bucket" "sample_bucket" {
  bucket = var.bucket_name

  tags = {
    Name        = "${var.project}-bucket"
    Environment = var.environment
  }
}
  • resource "aws_s3_bucket" "sample_bucket":
    • aws_s3_bucketは、TerraformでS3バケットを作成するためのリソースタイプです。
    • "sample_bucket"は、このリソースをTerraform内で識別するための一意の名前です。
  • bucket = var.bucket_name:
    • S3バケットの名前を指定します。
    • バケット名はTerraformの変数(var.bucket_name)から取得します。
    • バケット名はグローバルで一意である必要があります(例: my-unique-bucket-name)。
  • tags:
    • バケットにタグを付与します。
    • タグはAWSリソースを識別しやすくするために利用され、運用時に非常に便利です。
    • Name: バケットの名前(プロジェクト名に基づいて設定)。
    • Environment: 環境の種類(例: 開発、ステージング、本番)。

ブロックパブリックアクセスの無効化

HCL
resource "aws_s3_bucket_public_access_block" "sample_bucket_public_access" {
  bucket = aws_s3_bucket.sample_bucket.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}
  • aws_s3_bucket_public_access_block:
    • このリソースは、S3バケットのパブリックアクセスを制御する設定を定義します。
  • bucket = aws_s3_bucket.sample_bucket.id:
    • この設定が適用されるS3バケットを指定します。ここでは、上記で作成したsample_bucketのIDを使用します。
  • パブリックアクセスの設定:
    • block_public_acls: バケットのパブリックACLをブロックするかどうか。
    • block_public_policy: パブリックポリシーをブロックするかどうか。
    • ignore_public_acls: パブリックACLを無視するかどうか。
    • restrict_public_buckets: バケットをパブリックアクセスから制限するかどうか。
    • ここでは、すべての設定をfalseにしてパブリックアクセスを許可しています。CloudFront経由でのアクセスに依存するため、後述のバケットポリシーでセキュリティを強化します。

S3バケットポリシーの設定

HCL
resource "aws_s3_bucket_policy" "sample_bucket_policy" {
  bucket = aws_s3_bucket.sample_bucket.id

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          "Service" : "cloudfront.amazonaws.com"
        },
        Action : "s3:GetObject",
        Resource : "${aws_s3_bucket.sample_bucket.arn}/*",
        Condition : {
          StringEquals : {
            "AWS:SourceArn" : "${aws_cloudfront_distribution.sample_cdn.arn}"
          }
        }
      }
    ]
  })
}
  • aws_s3_bucket_policy:
    • S3バケットに適用するポリシー(アクセス制御のルール)を定義します。
  • bucket = aws_s3_bucket.sample_bucket.id:
    • ポリシーを適用するS3バケットを指定します。
  • policy = jsonencode({...}):
    • JSON形式でポリシーを記述し、それをTerraformで適用可能な形式にエンコードします。
  • ポリシーの内容:
    • Effect: 許可する動作を指定します。Allowで動作を許可。
    • Principal: アクセスを許可する主体を指定します。ここではcloudfront.amazonaws.com(CloudFrontサービス)を指定。
    • Action: 許可するアクション。ここでは、s3:GetObject(オブジェクトの取得)を許可。
    • Resource: このポリシーが適用されるリソース。バケット内のすべてのオブジェクト(/*)を指定。
    • Condition:
      • CloudFront経由のアクセスのみ許可する条件を指定。
      • AWS:SourceArn: CloudFrontディストリビューションのARNを指定。
        (後ほど設定します。)

CloudFrontの環境構築

以下では、CloudFrontに関連するTerraformコードの各部分を解説します。

CloudFront Origin Access Control (OAC)

OACは、AWS CloudFrontがバックエンドのオリジン(例: S3バケット)に安全にアクセスできるようにする仕組みです。

HCL
resource "aws_cloudfront_origin_access_control" "sample_oac" {
  name                              = "sample-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}
  • aws_cloudfront_origin_access_control:
    • CloudFrontとS3バケットを連携させる際に使用する「Origin Access Control(OAC)」を作成します。
    • OACは、CloudFrontがS3バケットに安全にアクセスできるようにするための設定です。
  • 各属性の説明:
    • name:
      • OACの名前です。分かりやすい名前を付けてください(例: "sample-oac")。
    • origin_access_control_origin_type:
      • オリジンの種類を指定します。ここでは、CloudFrontがアクセスする先がS3であるため、"s3" を指定します。
    • signing_behavior:
      • すべてのリクエストに署名を付ける設定。"always" で常に署名付きリクエストを使用します。
    • signing_protocol:
      • S3とやり取りする際の署名プロトコル。最新かつ推奨される"sigv4"を指定します。

CloudFrontディストリビューションの設定

主に下記内容を設定しています。

  1. origin(オリジン設定)
  2. default_cache_behavior(キャッシュ設定)
  3. viewer_certificate(SSL証明書設定)
  4. restrictions(地域制限)
  5. default_root_object
  6. custom_error_response(カスタムエラーレスポンス)
  7. tags(リソースタグ)
HCL
resource "aws_cloudfront_distribution" "sample_cdn" {
  origin {
    domain_name              = aws_s3_bucket.sample_bucket.bucket_regional_domain_name
    origin_id                = "S3-${var.bucket_name}"
    origin_access_control_id = aws_cloudfront_origin_access_control.sample_oac.id
  }

  enabled = true

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-${var.bucket_name}"

    viewer_protocol_policy = "redirect-to-https"

    forwarded_values {
      query_string = true
      cookies {
        forward = "all"
      }
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  restrictions {
    geo_restriction {
      restriction_type = "whitelist"
      locations        = ["JP"]
    }
  }

  default_root_object = "index.html"

  custom_error_response {
    error_code         = 403
    response_code      = 200
    response_page_path = "/index.html"
  }

  custom_error_response {
    error_code         = 404
    response_code      = 200
    response_page_path = "/index.html"
  }

  tags = {
    Name        = "${var.project}-cdn"
    Environment = var.environment
  }
}

origin

  • 役割: CloudFrontがコンテンツを取得するオリジン(S3バケット)を指定します。
  • 各属性の説明:
    • domain_name:
      • オリジンとなるS3バケットのドメイン名を指定します。ここでは aws_s3_bucket.sample_bucket.bucket_regional_domain_name を使用し、バケットのリージョンに基づいたドメイン名を設定。
    • origin_id:
      • このオリジンを識別する一意のIDを指定します(例: "S3-${var.bucket_name}")。
    • origin_access_control_id:
      • 作成したOACのIDを指定し、CloudFrontがS3に安全にアクセスできるようにします。

default_cache_behavior

  • 役割: CloudFrontがキャッシュやHTTPリクエストをどのように処理するかを定義します。
  • 各属性の説明:
    • allowed_methods:
      • CloudFrontが許可するHTTPメソッドを指定します(例: ["GET", "HEAD"])。
      • 静的サイトでは、GETとHEADのみで十分です。
    • cached_methods:
      • キャッシュされるHTTPメソッドを指定します(例: ["GET", "HEAD"])。
    • target_origin_id:
      • キャッシュのターゲットとなるオリジンIDを指定。originブロックで設定したIDと一致させます。
    • viewer_protocol_policy:
      • クライアントがHTTPでリクエストした場合の動作を定義。
      • "redirect-to-https"を指定すると、HTTPリクエストをHTTPSにリダイレクトします。
    • forwarded_values:
      • CloudFrontがリクエスト時にオリジンに転送するデータの設定。
      • query_string:
        • クエリパラメータをS3に転送するかどうか。
      • cookies:
        • クッキー情報を転送するかどうか("all"で全て転送)。

viewer_certificate

  • 役割: CloudFrontで使用するSSL証明書を設定します。
  • cloudfront_default_certificate:
    • デフォルトのCloudFront提供のSSL証明書を使用(HTTPS通信が可能)

restrictions

  • 役割: コンテンツを配信する地域を制限します。
  • geo_restriction:
    • restriction_type:
      • 地域制限の種類を指定。ここでは"whitelist"を指定し、日本のみ許可。
    • locations:
      • 配信を許可する地域の国コード(例: "JP"

default_root_object

  • 役割: リクエストのURLにファイル名が含まれない場合に表示されるデフォルトファイルを指定します。
  • 例: "index.html"

custom_error_response

  • 役割: 特定のHTTPエラーコード(403, 404など)を処理し、カスタムページを返します。
  • 各属性の説明:
    • error_code:
      • エラーコード(例: 403)。
    • response_code:
      • クライアントに返すHTTPレスポンスコード(例: 200)。
    • response_page_path:
      • エラー時に表示するページのパス(例: /index.html)。

Terraformの変数ファイル設定

variables.tfの内容

HCL
variable "project" {
  type    = string
  default = "sample-infra"
}

variable "region" {
  type    = string
  default = "ap-northeast-1"
}

variable "environment" {
  type    = string
  default = "production"
}

variable "bucket_name" {
  type    = string
  default = "sample-infra-deploy"
}

今回作成したコード全文

今回作成したmain.tfです。
1ファイルに全てのリソースを記述すると、可読性が悪くなるので、適宜ファイル分割をしてください。

HCL
# 変数
variable "project" {
  type    = string
  default = "sample-infra"
}

variable "region" {
  type    = string
  default = "ap-northeast-1"
}

variable "environment" {
  type    = string
  default = "production"
}

variable "bucket_name" {
  type    = string
  default = "sample-infra-deploy"
}

provider "aws" {
  region = var.region
}

# oac
resource "aws_cloudfront_origin_access_control" "sample_oac" {
  name                              = "sample-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

# ディストリビューション
resource "aws_cloudfront_distribution" "sample_cdn" {
  origin {
    domain_name              = aws_s3_bucket.sample_bucket.bucket_regional_domain_name
    origin_id                = "S3-${var.bucket_name}"
    origin_access_control_id = aws_cloudfront_origin_access_control.sample_oac.id
  }

  enabled = true

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-${var.bucket_name}"

    viewer_protocol_policy = "redirect-to-https"

    forwarded_values {
      query_string = true
      cookies {
        forward = "all"
      }
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  restrictions {
    geo_restriction {
      restriction_type = "whitelist"
      locations        = ["JP"]
    }
  }

  default_root_object = "index.html"

  custom_error_response {
    error_code         = 403
    response_code      = 200
    response_page_path = "/index.html"
  }

  custom_error_response {
    error_code         = 404
    response_code      = 200
    response_page_path = "/index.html"
  }

  tags = {
    Name        = "${var.project}-cdn"
    Environment = var.environment
  }
}

# s3リソース
resource "aws_s3_bucket" "sample_bucket" {
  bucket = var.bucket_name

  tags = {
    Name        = "${var.project}-bucket"
    Environment = var.environment
  }
}

resource "aws_s3_bucket_public_access_block" "sample_bucket_public_access" {
  bucket = aws_s3_bucket.sample_bucket.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

# バケットポリシー
resource "aws_s3_bucket_policy" "sample_bucket_policy" {
  bucket = aws_s3_bucket.sample_bucket.id

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          "Service" : "cloudfront.amazonaws.com"
        },
        Action : "s3:GetObject",
        Resource : "${aws_s3_bucket.sample_bucket.arn}/*",
        Condition : {
          StringEquals : {
            "AWS:SourceArn" : "${aws_cloudfront_distribution.sample_cdn.arn}"
          }
        }
      }
    ]
  })
}

Terraformの実行手順

  1. 初期化
    terraform init
    • Terraformを初期化し、必要なプラグインをダウンロードします。
  2. 計画の確認
    terraform plan
    • 作成予定のリソースを確認します。
  3. インフラの適用
    terraform apply
    • 実際にインフラを構築します。
  4. Reactアプリのデプロイ
    • build/フォルダ内のファイルをS3バケットにアップロードします。
    • AWS CLIやS3の管理画面でアップロード可能です。

まとめ

Terraformを使ったReactアプリのデプロイ環境構築は、S3とCloudFrontの組み合わせで簡単に実現できます。本記事で紹介したコードをカスタマイズし、自身のプロジェクトに適用してみましょう!

コメント

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