AWS CLIでHugoサイト構築

Posted on | 400 words | ~2 mins

Prerequisites

  • foo.comのドメイン
  • AWSアカウント
  • blog.foo.com(ないしワイルドカード)のACM証明書(us-east-1 regionのもの)
  • aws cli
  • jq

Goal

  • Hugoで生成したstatic siteコンテンツをS3でホストしてCloudFrontで独自ドメイン配信
    • 独自ドメイン= blog.foo.com
    • 配信はhttpsへリダイレクト

Hugoプロジェクト作成

brew install hugo
hugo new site site
cd site
hugo --minify

static site用S3バケット作成

  • s3bucketpolicy.json
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "PublicReadGetObject",
          "Effect": "Allow",
          "Principal": "*",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::blog.foo.com/*"
        }
      ]
    }
    
aws s3 mb s3://blog.foo.com --region="ap-northeast-1"
aws s3api put-bucket-policy --bucket blog.foo.com --policy file://s3bucketpolicy.json
aws s3 website s3://blog.foo.com/ --index-document index.html
aws s3 sync "public/" "s3://blog.foo.com" --delete --acl "public-read"

http://blog.foo.com.s3-website-ap-northeast-1.amazonaws.com/へアクセスできるか確認

配信用CloudFront

ログ出力用のS3バケット作成

aws s3 mb s3://logs.blog.foo.com --region="ap-northeast-1"

aws cliでCloudFront Distribution作成

完成形JSON

aws cloudfront create-distributionに渡すやつ

cf_distribution.json
{
  "DistributionConfig": {
    "CallerReference": "1610246116",
    "Aliases": {
      "Quantity": 1,
      "Items": [
        "blog.foo.com"
      ]
    },
    "DefaultRootObject": "index.html",
    "Origins": {
      "Quantity": 1,
      "Items": [
        {
          "Id": "s3-blog.foo.com",
          "DomainName": "blog.foo.com.s3-website-ap-northeast-1.amazonaws.com",
          "OriginPath": "",
          "CustomHeaders": {
            "Quantity": 0
          },
          "CustomOriginConfig": {
            "HTTPPort": 80,
            "HTTPSPort": 443,
            "OriginProtocolPolicy": "http-only",
            "OriginSslProtocols": {
              "Quantity": 1,
              "Items": [
                "TLSv1.2"
              ]
            },
            "OriginReadTimeout": 30,
            "OriginKeepaliveTimeout": 5
          }
        }
      ]
    },
    "OriginGroups": {
      "Quantity": 0
    },
    "DefaultCacheBehavior": {
      "TargetOriginId": "s3-blog.foo.com",
      "ForwardedValues": {
        "QueryString": true,
        "Cookies": {
          "Forward": "none"
        },
        "Headers": {
          "Quantity": 0
        },
        "QueryStringCacheKeys": {
          "Quantity": 0
        }
      },
      "TrustedSigners": {
        "Enabled": false,
        "Quantity": 0
      },
      "ViewerProtocolPolicy": "redirect-to-https",
      "MinTTL": 0,
      "AllowedMethods": {
        "Quantity": 2,
        "Items": [
          "HEAD",
          "GET"
        ],
        "CachedMethods": {
          "Quantity": 2,
          "Items": [
            "HEAD",
            "GET"
          ]
        }
      },
      "SmoothStreaming": true,
      "DefaultTTL": 0,
      "MaxTTL": 0,
      "Compress": true,
      "LambdaFunctionAssociations": {
        "Quantity": 0
      },
      "FieldLevelEncryptionId": ""
    },
    "CacheBehaviors": {
      "Quantity": 0
    },
    "CustomErrorResponses": {
      "Quantity": 0
    },
    "Comment": "",
    "Logging": {
      "Enabled": true,
      "IncludeCookies": false,
      "Bucket": "logs.blog.foo.com.s3.amazonaws.com",
      "Prefix": "cdn/"
    },
    "PriceClass": "PriceClass_100",
    "Enabled": true,
    "ViewerCertificate": {
      "ACMCertificateArn": "arn:aws:acm:us-east-1:ACCOUNT_ID:certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "SSLSupportMethod": "sni-only",
      "MinimumProtocolVersion": "TLSv1.2_2019",
      "Certificate": "arn:aws:acm:us-east-1:ACCOUNT_ID:certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "CertificateSource": "acm"
    },
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "none",
        "Quantity": 0
      }
    },
    "WebACLId": "",
    "HttpVersion": "http2",
    "IsIPV6Enabled": true
  }
}

Keys

Makefile

CLOUDFRONT_DISTRIBUTION_JSON := cf_distribution.json

.PHONY: create-cloudfront-distribution
create-cloudfront-distribution: update-cloudfront-callerreference
	aws cloudfront \
		create-distribution \
			--cli-input-json file://$(CLOUDFRONT_DISTRIBUTION_JSON)

.PHONY: update-cloudfront-callerreference
update-cloudfront-callerreference:
	cat $(CLOUDFRONT_DISTRIBUTION_JSON) | jq -r '.DistributionConfig.CallerReference = "'$$(date +%s)'"' > $(CLOUDFRONT_DISTRIBUTION_JSON)

$ make create-cloudfront-distributionで作成

Route53

Aliases.Itemsで指定した代替ドメイン名でCloudFrontのエイリアスレコードを作成

  • add_alias_record.json
    {
      "Comment": "Creating Alias resource record sets in Route 53",
      "Changes": [
        {
          "Action": "CREATE",
          "ResourceRecordSet": {
            "Name": "blog.foo.com.",
            "Type": "A",
            "AliasTarget": {
                "HostedZoneId": "ZXXXXXXXXXXXXX",
                "DNSName": "xxxxxxxxxxxxxx.cloudfront.net.",
                "EvaluateTargetHealth": false
            }
          }
        }
      ]
    }
    
    • DNSNameaws cloudfront list-distributionsのDistributionList.Items[i].DomainNameを参照(i値は適宜)
  • aws route53 change-resource-record-sets --hosted-zone-id ZXXXXXXXXXX --change-batch file://add_alias_record.jsonで作成

Test

curl -LI blog.foo.com -o /dev/null -w '%{http_code}\n' -s
200