【所要時間10分】Terraformを使って、S3でWebサイトを公開する!

August 13, 2020

S3で静的ウェブサイトを公開するための方法を解説します。

※公式ドキュメントでは、「静的ウェブサイトをホスティングする」という文言で表現されています。本記事では、分かりやすさを優先して「ウェブサイトをインターネットに公開する」という表現に変えています。

※静的ウェブサイト: サーバ側スクリプト(PHP等)を使用しないサイト。

目次

  • 環境
  • 処理の概要
  • バケットポリシーを作成する。
  • S3バケットを作成する。
  • Terraformを実行する。
  • 公開するHTMLをS3にアップロードする。

環境

Terraformを使用します。ソースコードの構成は以下の通り。

public-s3
 |-- main.tf    ←モジュールを読み込むトップのファイル。
 `-- modules
     |-- iam.tf ←バケットポリシーを設定するファイル。
     `-- s3.tf  ←S3バケットを設定するファイル。

GitHub

また、Terraformのトップファイルは以下の通りです。共通の設定のため解説は割愛します。

./main.tf

provider "aws" {
  region = "ap-northeast-1"
  assume_role {
    role_arn = "arn:aws:iam::XXXXXXXXXX:role/SystemAdmin"
  }
}

terraform {
  required_version = "0.12.24"
  backend "s3" {
    bucket   = "tfstate.mini-schna.com"
    region   = "ap-northeast-1"
    key      = "blog/public-s3.tfstate"
    encrypt  = true
    role_arn = "arn:aws:iam::XXXXXXXXXX:role/SystemAdmin"
  }
}

module "aws" {
  source = "./modules"
}

処理の概要

下記の図は、S3でウェブサイトを公開した時の流れになります。

S3で静的ウェブサイトをホスティングする仕組み

大きな流れとしては、Webサーバで公開する時と変わりません。ただ、いくつかS3特有の情報があるので少し補足したいと思います。

ウェブサイトエンドポイント(http://[バケット名].s3-website-[リージョン].amazonaws.com)

簡単に表現すると、外部からウェブサイトとしてアクセスできるURLのことです。ぱっとイメージが付きづらいかと思うので、index.html にアクセスしたい場合を例に取ってみます。

(例) mini-schna.comバケット

index.htmlを表示するURLは下記のように2種類存在します。

・ウェブサイトエンドポイント: http://mini-schna.com.s3-website-ap-northeast-1.amazonaws.com
・オブジェクトURL: https://s3-ap-northeast-1.amazonaws.com/mini-schna.com/index.html

オブジェクトURLでアクセスする場合は、ファイル名まで指定しないとアクセスすることができません。ウェブサイトにアクセスしたいクライアントは、ファイル名まで知らないことが多いため、これだとウェブサイトとしてなかなか機能しません。

そこでこの問題を解決するのが、ウェブサイトエンドポイント。 ウェブサイトエンドポイントにアクセスすると、デフォルトで設定されたコンテンツが表示され、このURLをベースに画面遷移していくことができます。

バケットポリシー

S3はデフォルトで外部からアクセスすることができません。そのため、外部からのアクセスを許可する必要があるのですが、その設定を行うのがバケットポリシーになります。


バケットポリシーを作成する。

外部からS3バケットへのアクセスを許可します。

./modules/iam.tf

# S3 Bucket Policy
data "aws_iam_policy_document" "s3_bucket_policy" {
  statement {
    sid    = ""
    effect = "Allow"

    ## アクセス元の設定。
    principals {
      identifiers = ["*"] ## 誰でもアクセスできるように設定。
      type        = "*"
    }

    ## バケットに対して制御するアクションを設定する。
    actions = [
      "s3:GetObject" ## オブジェクトの読み取りアクション。
    ]

    ## アクセス先の設定。
    resources = [
      "arn:aws:s3:::mini-schna.com",  ## mini-schna.comバケットへのアクセス。
      "arn:aws:s3:::mini-schna.com/*" ## mini-schna.comバケット配下へのアクセス。
    ]
  }
}

それでは、解説していきます。

## アクセス元の設定。
    principals {
      identifiers = ["*"] ## 誰でもアクセスできるように設定。
      type        = "*"
    }

どこからでもアクセスできるように identifiers & type に「*(ワイルドカード)」を指定します。


## バケットに対して制御するアクションを設定する。
    actions = [
      "s3:GetObject" ## オブジェクトの読み取りアクション。
    ]

ウェブサイトとして公開するため、クライアントはオブジェクト(HTMLファイル等)を表示することができればよいので s3:GetObject アクションを指定します。


## アクセス先の設定。
    resources = [
      "arn:aws:s3:::mini-schna.com",  ## mini-schna.comバケットへのアクセス。
      "arn:aws:s3:::mini-schna.com/*" ## mini-schna.comバケット配下へのアクセス。
    ]

mini-schna.comバケット配下だけアクセスできるようにします。


S3バケットを作成する。

Webサイトを公開するためのS3バケットを作成していきます。

./modules/s3.tf

# 変数
variable "bucket_name" {
  default     = "mini-schna.com"
  description = "s3 bucket name."
}

# S3 Bucket
resource "aws_s3_bucket" "mini_schna_com" {
  bucket = var.bucket_name
  policy = data.aws_iam_policy_document.s3_bucket_policy.json ## iam.tfで設定したポリシーを使用。

  ## バケットの削除設定。
  force_destroy = false ## バケットの中にオブジェクトが入っている場合にTerraformからバケットを削除できないようにする。

  ## Webサイト設定。
  website {
    ## バケットにアクセスした時にデフォルトで表示されるコンテンツを設定。
    index_document = "index.html"
  }

  ## オブジェクトのバージョン管理設定。
  versioning {
    enabled    = true
    mfa_delete = false ## オブジェクトへのアクセスにMFA(多段階認証)を使用しない。
  }

  ## バケットの料金を誰が支払うか設定。
  request_payer = "BucketOwner" ## 通常通り所有者が支払う。
}

# S3 Public Access Block
resource "aws_s3_bucket_public_access_block" "mini_schna_com" {
  bucket                  = aws_s3_bucket.mini_schna_com.bucket
  block_public_acls       = true
  block_public_policy     = false ## バケットポリシーで制御したいため無効にする。
  ignore_public_acls      = true
  restrict_public_buckets = false ## バケットポリシーで制御したいため無効にする。
}

それでは、解説していきます。

# 変数
variable "bucket_name" {
  default     = "mini-schna.com"
  description = "s3 bucket name."
}

後で変更しやすいようにバケット名を変数に設定しています。(特別必要な設定ではないため、直接S3の設定に記載する方法でも大丈夫です。)


resource "aws_s3_bucket" "mini_schna_com" {
  bucket = var.bucket_name
  policy = data.aws_iam_policy_document.s3_bucket_policy.json ## iam.tfで設定したポリシーを使用。

  ## バケットの削除設定。
  force_destroy = false ## バケットの中にオブジェクトが入っている場合にTerraformからバケットを削除できないようにする。

  ## Webサイト設定。
  website {
    ## バケットにアクセスした時にデフォルトで表示されるコンテンツを設定。
    index_document = "index.html"
  }

  ## オブジェクトのバージョン管理設定。
  versioning {
    enabled    = true
    mfa_delete = false ## オブジェクトへのアクセスにMFA(多段階認証)を使用しない。
  }

  ## バケットの料金を誰が支払うか設定。
  request_payer = "BucketOwner" ## 通常通り所有者が支払う。
}

bucketでバケット名を設定します。今回は、変数で設定したバケット名を使用しています。

poloicyでバケットポリシーを設定します。今回は、iam.tf で事前に設定したポリシーをJSONにシリアライズして値を設定します。

force_destroyは、バケットの中にオブジェクトが入っている状態でもバケットを削除できるようにするかの設定。オブジェクトが入っている状態で間違えて削除しないように無効にします。

website { index_document = ~ }は、ウェブエンドポイントにアクセスがあった時にデフォルトで返すファイルを設定します。

versioning { enabled = ~ }は、オブジェクトのバージョン管理を行うかどうか設定します。間違えてファイルを削除した時にすぐに戻せるようにするために有効にします。

versioning { mfa_delete = ~ }は、オブジェクトへのアクセスにMFA(多段階認証)が必要かどうか設定します。今回は、必要ないため無効にします。

request_payerは、バケットの料金を支払う対象を設定します。今回は、一般的なバケット所有者が支払うように設定します。
※バケット所有者以外にも、リクエストを実行したユーザーが支払うように設定することも可能です。

公式 - リクエスタ支払い


# S3 Public Access Block
resource "aws_s3_bucket_public_access_block" "mini_schna_com" {
  bucket                  = aws_s3_bucket.mini_schna_com.bucket
  block_public_acls       = true
  block_public_policy     = false ## バケットポリシーで制御したいため無効にする。
  ignore_public_acls      = true
  restrict_public_buckets = false ## バケットポリシーで制御したいため無効にする。
}

パブリックアクセスブロック設定は、ポリシーやACL設定を上書きしてパブリックアクセスを無効にする設定です。今回は、バケットポリシーでアクセス許可を行うため、ポリシーに関する機能は無効にします。


Terraformを実行する。

$ cd /infra/terraform/public-s3
$ terraform init
$ terraform apply

※Dockerコンテナ内での実行を前提にしています。


公開するHTMLをS3にアップロードする。

0.公開するためのHTMLファイル( index.html )を作成する。

index.html

<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>My Website Home Page</title>
</head>
<body>
  <h1>Welcome to my website</h1>
  <p>Now hosted on Amazon S3!</p>
</body>
</html>

1.S3バケットで「アップロード」をクリックする。 S3アップロード 01


2.「ファイルを追加」をクリックする。 S3アップロード 02


3.アップロードしたファイルを確認して、「次へ」をクリックする。 S3アップロード 03


4.「次へ」をクリックする。 S3アップロード 04


5.ストレージクラスから「スタンダード」を選択して、「次へ」をクリックする。 S3アップロード 05


6.「アップロード」をクリックする。 S3アップロード 06


7.アップロードした index.html が表示される。 S3アップロード 07


8.ウェブエンドポイント http://[バケット名].s3-website-[リージョン].amazonaws.com にアクセスするとWebページが表示されます。 S3アップロード 08


以上で、S3でウェブサイトを公開できるようになりました。

最後までご覧頂きありがとうございました。