kentz blog

CloudFront + S3 + Hugoでブログを構築

はじめに

技術的な学びをアウトプットする機会を増やそうと思い、まずは自分でブログを構築してみることにしました。

フレームワークにはHugoを採用し、インフラはTerrafromで管理、Github ActionsでCDを組みました。 今回は個人ブログを構築する際にやったことを紹介したいと思います。 本記事ではブログをホストする環境を作成するところまでを書き、ActionsをつかったCDについては別記事で書きます。

構築するもの

構成図

書かないこと

  • Hugoのインストール方法や使い方
  • ドメインの取得方法
  • AWSアカウントの発行方法

書くこと

  • Terraformでブログをホストする環境を構築する手順

インフラ構成

今回個人ブログをホストする環境をAWSで用意しました。 昔AWSのCloud Solutions Architect Associate(SAA)の勉強をしていた時に発行したAWSアカウントがあったのですが、資格勉強以降ほとんどつかわれていなかったので今回AWSで環境を作成しました。 ただ、環境を作成するだけだとアレなので、Terraformをつかった構成管理 + Github ActionsでCDを組む + OIDCでAWSとActions間の認証をすることにしました。

ブログをホストする環境はCloudfront + S3環境で構築しました。 Cloudfront + S3をつかって静的サイトをつくる構成は、S3の静的ウェブサイトホスティング機能を使うパターンとCloudfrontのOACを使ってS3へのアクセス制御をするパターンがありますが、今回は後者を採用しました。

OACについて参考にした記事↓ https://dev.classmethod.jp/articles/amazon-cloudfront-origin-access-control/

Terrafromでリソースを作成

ディレクトリ構成は以下のようにしました。 別環境に複製することは想定していないので、以下の単純なディレクトリ構成になっています。 acm.tf,route53.tf,main.tf,variable.tfの説明は省きます。

ディレクトリ構成

terraform/
├── acm.tf
├── cloudfront.tf
├── code
│   └── addIndexFunction.js
├── main.tf
├── route53.tf
├── s3.tf
└── variable.tf

cloudfront.tf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# Create a CloudFront distribution for serving the static content
resource "aws_cloudfront_distribution" "example_domain" {
  origin {
    domain_name              = aws_s3_bucket.main.bucket_regional_domain_name
    origin_id                = aws_s3_bucket.main.id
    origin_access_control_id = aws_cloudfront_origin_access_control.example_domain.id
  }

  enabled             = true
  default_root_object = "index.html"

  aliases = ["${var.service_domain}"]

  default_cache_behavior {
    target_origin_id = aws_s3_bucket.main.id

    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }

    function_association {
      event_type   = "viewer-request"
      function_arn = aws_cloudfront_function.add-index-function.arn
    }
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate_validation.example_domain.certificate_arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2018"
  }
}

resource "aws_cloudfront_origin_access_control" "example_domain" {
  name                              = aws_s3_bucket.main.bucket_domain_name
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

resource "aws_cloudfront_function" "add-index-function" {
  name    = "add-index-function"
  runtime = "cloudfront-js-1.0"
  comment = "Add index.html to the path"
  publish = true
  code    = file("${path.module}/code/addIndexFunction.js")
}

###############################################################################
# Output
###############################################################################

output "cloud_front_destribution_domain_name" {
  value = aws_cloudfront_distribution.example_domain.domain_name
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
resource "aws_s3_bucket" "main" {
  bucket = "hogehoge"
}

resource "aws_s3_bucket_policy" "bucket_policy" {
  bucket = aws_s3_bucket.main.id
  policy = data.aws_iam_policy_document.s3_policy.json
}

data "aws_iam_policy_document" "s3_policy" {
  statement {
    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }
    actions = [
      "s3:GetObject",
      "s3:putBucketAcl",
    ]
    resources = [
      aws_s3_bucket.main.arn,
      "${aws_s3_bucket.main.arn}/*",
    ]
    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = [aws_cloudfront_distribution.n_kent2_net.arn]
    }
  }
}

さきほど説明したとおりCloudfrontのOrigin Access Control(OAC)を使用してS3へのアクセス制御しています。 OACの設定は上記コードのハイライトした箇所で行っています。 Terraformのaws_cloudfront_origin_access_controlでOACの設定を行いディストリビューションに紐づけています。 S3側ではOACにバケットへのアクセス許可を付与するためにS3のバケットポリシーでActionを制御し、サービスプリンシパルにcloudfront.amazonaws.comをしていしています。さらにConditionで ディストリビューションのARNを指定することで特定のディストリビューションからのアクセスのみを許可するように設定しています。

Viewer-requestでadd-index-functionというCloudfront Functionを実行するようにしています。 これはルートオブジェクトに設定されているindex.htmlは問題なく表示されるものの、それ以外のサブディレクトリが表示されない問題を回避するために設定しています。 関数の中身は公式が用意しているコードをそのまま使用しています。

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example-function-add-index.html

以上でブログをホストするための環境は用意できました。 次はGithub ActionsをつかってCDを作成について書きたいとおもいます。

参考 https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html https://dev.classmethod.jp/articles/cloudfront-and-s3-using-hugo-with-github-actions/ https://zenn.dev/kou_pg_0131/articles/tf-cloudfront-oac