AWS
S3
CloudFront
Terraform
acm

S3 + CloudFront + Aws Certificate Manager + Route53 + Middleman + terraformを使ってHTTPS対応した静的Webを100円/月で構築する

More than 1 year has passed since last update.

概要

タイトルが長い。

個人プロジェクトでSSL対応したWebサイトを作る機会があって、折角なので無料のACM(Aws Certificate Manager)を使ってみました。
そこそこ安く、SSL対応したWebサイトを構築します。

インフラはほぼAWSを使っています。

なぜこれらの技術を採用したか

S3

WebサイトはWordPress等で構築でも良かったのですが、サーバ費用をかけたくなかったため。
middlemanでビルドした静的WebをS3にホストしています。

CloudFront

S3 + ACMの組み合わせだと必須だったため、仕方なく使っています。
わざわざCloudFrontを使うほどのトラフィックではない。
S3で直接ACM使えたらいいのになぁ。

Aws Certificate Manager

Let’s Encryptでも良かったけど、サーバの運用とかしたくなかったため。
というか、そもそもACMを使ってみたいというモチベーションだった。

Route53

何かと相性が良いので。
ドメインはAWS外のサービスで取りました。

Middleman

静的サイトジェネレーターです。
https://middlemanapp.com/jp/

要件的には静的WebでOKかつ自分しか記事更新しないので、middleman-blogを採用しました。
もし非エンジニアの人が記事書くようなプロジェクトだと、middlemanは採用しづらいですね...

terraform

INFRASTRUCTURE AS CODEです。
https://www.terraform.io/

AWSの管理画面でぽちぽちするのがあまり好きでは無いので、なるだけterraformでAWSリソースの構築をしました。

記事時点では v0.6.15 を使っています。

構成

クライアント → CloudFront → S3 ← middleman ← おれ

terraformのtf

こちらを大変参考にさせて頂きました。
クラメソさんの記事いつもお世話になっております:bangbang:

そしてこの記事の一番価値あるのはここだよ:bangbang:

terraform.tf
variable "name" { default = "bucket-name.here" }

resource "aws_cloudfront_origin_access_identity" "sample" {
  comment = "${var.name}"
}

resource "template_file" "sample_s3_policy" {
  template = "${file(concat(path.module, "/sample_s3_policy.json.tpl"))}"

  vars {
    bucket_name            = "${var.name}"
    origin_access_identity = "${aws_cloudfront_origin_access_identity.sample.id}"
  }
}

resource "aws_s3_bucket" "sample" {
  bucket = "${var.name}"
  acl    = "private"
  policy = "${template_file.sample_s3_policy.rendered}"

  website {
    index_document = "index.html"
    error_document = "404.html"
  }
}

resource "aws_cloudfront_distribution" "sample" {
  enabled             = true
  comment             = "${var.name}"
  default_root_object = "index.html"
  price_class         = "PriceClass_200"
  retain_on_delete    = true
  aliases = ["${var.name}", "www.${var.name}"]

  origin {
    domain_name = "${aws_s3_bucket.sample.website_endpoint}"
    origin_id   = "${var.name}"

    custom_origin_config {
      http_port = "80"
      https_port = "443"
      origin_protocol_policy = "http-only"
      origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
    }
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "${aws_s3_bucket.sample.id}"
    compress         = true

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    acm_certificate_arn = "arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    ssl_support_method = "sni-only"
    minimum_protocol_version = "TLSv1"
  }
}

resource "aws_route53_zone" "sample" {
  name = "${var.name}"
}

resource "aws_route53_record" "sample" {
  zone_id = "${aws_route53_zone.sample.zone_id}"
  name    = "${var.name}"
  type    = "A"

  alias {
    name    = "${aws_cloudfront_distribution.sample.domain_name}"
    zone_id = "Z2FDTNDATAQYW2"
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "www-sample" {
  zone_id = "${aws_route53_zone.sample.zone_id}"
  name    = "www.${var.name}"
  type    = "A"

  alias {
    name    = "${aws_cloudfront_distribution.sample.domain_name}"
    zone_id = "Z2FDTNDATAQYW2"
    evaluate_target_health = false
  }
}
sample_s3_policy.json.tpl
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForGetBucketObjects",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${origin_access_identity}"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::${bucket_name}/*"
    }
  ]
}

クラメソさんの記事をベースに作っていますが、いくつか変更点があります。

  • CloudFrontのindexDocumentの設定
    • bucket-name.here/foo/index.htmlに対して、bucket-name.here/foo だけでアクセスできるようにしたかった
    • そのためcustom_origin_configを設定している
  • ACMを利用するためにCloudFrontのviewer_certificateの設定をしている

terraform管理外のリソース

できるだけterraformで完結したかったのですが、ACMに関してはまだterraformから扱えないようでしたので、管理画面からぽちぽちつくりました。

今回はwwwあり/なしそれぞれのドメインをあてているので、certificateもこの2つを含むものを作成しました。

また、ACM設定の際にメールの受信設定等が必要です。
メール受信するためにSESも使うのですが、同じくterraformから管理できないので管理画面からぽちぽちしました。
こちらを参考にさせて頂きました。

無事にACMの設定が完了するとarnがわかるので、その値をterraformのacm_certificate_arnにセットします。

費用

月に100円〜200円程度でしょうか。

月額(ざっくり)
ドメイン 10 〜 100円
Route53 60円
S3 10円
CloudFront 20円

まとめ

middlemanの話は無くても良かったですね...
(werckerの話も最初書いたけど関係ないから消した)

S3 + CloudFront + ACM を terraform で実現したい場合の参考になれば幸いです。