Terraformを使いAWSにSSL通信と独自ドメインを使ったStatic websiteを構築する話し(GitHub Actionsもあるよ)
ざっくりしたいこと
Vue.js でできたStatic websiteを独自ドメイン with SSLで公開する
SSL証明書の発行、サブドメイン作成などもすべて、できる限りTerraformで作成する
使用技術:
・Vue.js
・Terraform(v0.12.26)
・GitHub Actions
その他実行環境:
- aws-cli/2.0.19
- Python/3.8.3
- Linux/5.4.0–37-generic botocore/2.0.0dev23
Vue.js
Static websiteの作成
Terraform
前提: State用のS3は作成済み。ドメインはRoute53で取得済み。
Deployで作ったものはすべて壊せるようにしたい
S3,Cloud Front,ACM,Route53あたりをTerraformで触っていく。
以下のことをすべてTerraformで自動化する
S3:Privateで作成&Force Deleteの有効化
blog-terraform-aws-static-site-s3.tf
GitHub Gist: instantly share code, notes, and snippets.

resource "aws_s3_bucket" "site" {
bucket = var.bucket_name
acl = "private"
tags = {
name = var.tag
}
force_destroy = true
versioning {
enabled = true
}
}
resource "aws_s3_bucket_policy" "site" {
bucket = aws_s3_bucket.site.id
policy = data.aws_iam_policy_document.s3_site_policy.json
}
data "aws_iam_policy_document" "s3_site_policy" {
statement {
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.site.arn}/*"]
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.site.iam_arn]
}
}
}
CloudFront:作成した(S3,証明書,Domain)と紐づけていく、存在しないページはすべて/index.htmlにリダイレクト
blog-terraform-aws-static-site-cloud-front.tf
GitHub Gist: instantly share code, notes, and snippets.

locals {
s3_origin_id = "s3-origin-${var.site_domain}"
}
resource "aws_cloudfront_origin_access_identity" "site" {
comment = var.site_domain
}
resource "aws_cloudfront_distribution" "site" {
tags = {
name = var.tag
}
origin {
domain_name = aws_s3_bucket.site.bucket_regional_domain_name
origin_id = local.s3_origin_id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.site.cloudfront_access_identity_path
}
}
enabled = true
is_ipv6_enabled = true
comment = var.site_domain
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id
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"
}
}
custom_error_response {
error_caching_min_ttl = 3000
error_code = 404
response_code = 200
response_page_path = "/index.html"
}
custom_error_response {
error_caching_min_ttl = 3000
error_code = 403
response_code = 200
response_page_path = "/index.html"
}
price_class = "PriceClass_200"
# CloudFrontドメインの証明書を利用
//viewer_certificate {
// cloudfront_default_certificate = true
//}
aliases = [var.site_domain]
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.acm_cert.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1"
}
}
Route53: 取得済みの独自ドメインから指定のサブドメインのHost Zoneを作成し、NSレコードを追加する。その後、作成したCloud Frontへ
blog-terraform-aws-static-site-dns.tf
GitHub Gist: instantly share code, notes, and snippets.

data "aws_route53_zone" "root_domain" {
name = var.root_domain
}
resource "aws_route53_zone" "sub_domain" {
name = var.site_domain
tags = {
name = var.tag
}
}
resource "aws_route53_record" "root_domain" {
depends_on = [aws_route53_zone.sub_domain]
allow_overwrite = true
name = var.site_domain
ttl = 30
type = "NS"
zone_id = data.aws_route53_zone.root_domain.zone_id
records = [
aws_route53_zone.sub_domain.name_servers.0,
aws_route53_zone.sub_domain.name_servers.1,
aws_route53_zone.sub_domain.name_servers.2,
aws_route53_zone.sub_domain.name_servers.3,
]
}
resource "aws_route53_record" "sub_domain" {
zone_id = aws_route53_zone.sub_domain.zone_id
name = aws_route53_zone.sub_domain.name
type = "A"
alias {
name = aws_cloudfront_distribution.site.domain_name
zone_id = aws_cloudfront_distribution.site.hosted_zone_id
evaluate_target_health = false
}
}
ACM:ワイルドカード証明書を発行し、認証する
blog-terraform-aws-static-site-acm.tf
GitHub Gist: instantly share code, notes, and snippets.

resource "aws_acm_certificate" "acm_cert" {
provider = aws.us-east-1
domain_name = var.root_domain
subject_alternative_names = ["*.${var.root_domain}"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
tags = {
name = var.tag
}
}
resource "aws_route53_record" "cert_validation" {
allow_overwrite = true
zone_id = data.aws_route53_zone.root_domain.id
name = aws_acm_certificate.acm_cert.domain_validation_options.0.resource_record_name
type = aws_acm_certificate.acm_cert.domain_validation_options.0.resource_record_type
records = [aws_acm_certificate.acm_cert.domain_validation_options.0.resource_record_value]
ttl = 60
}
resource "aws_route53_record" "cert_validation_alt" {
allow_overwrite = true
zone_id = data.aws_route53_zone.root_domain.id
name = aws_acm_certificate.acm_cert.domain_validation_options.1.resource_record_name
type = aws_acm_certificate.acm_cert.domain_validation_options.1.resource_record_type
records = [aws_acm_certificate.acm_cert.domain_validation_options.1.resource_record_value]
ttl = 60
}
resource "aws_acm_certificate_validation" "acm_cert" {
provider = aws.us-east-1
certificate_arn = aws_acm_certificate.acm_cert.arn
validation_record_fqdns = [aws_route53_record.cert_validation.fqdn, aws_route53_record.cert_validation_alt.fqdn]
}
完成物
GitHub - kooooohe/aws-static-site-terraform
Contribute to kooooohe/aws-static-site-terraform development by creating an account on GitHub.
Stateはローカルに保存するように作ってあるので、下記のようなファイルを作れば作成済みのS3に保存されるようになる
main.tf
```json
terraform {
backend "s3" {
bucket = "mybucket"
key = "path/to/my/key"
region = "us-east-1"
}
}
```特にModule化とかはしていないが、variables.tfの値を変更すればそれぞれのドメインで同等の環境を誰でも作ることができるようにはしてある。
GitHub Actions
S3のファイル更新とCloud Frontのキャッシュ削除をGitHub Actionsで行う。
※S3の更新からのLambda発火でinvalidation作成とかもやろうと思ったのだが、こっちのほうがシンプルなので採用
blog-terraform-aws-static-site-actions.yml
GitHub Gist: instantly share code, notes, and snippets.

name: Deploy Production
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- run: aws s3 sync ./dist/ s3://${{ secrets.BUCKET_NAME }}/ --delete --exact-timestamps
- run: aws cloudfront create-invalidation --distribution-id ${{ secrets.DISTRIBUTION_ID}} --paths "/*"