https://qiita.com/propella/items/9a16f51ea4d33b5b8e3d
たまに静的な HTML をちょこっと外部に公開したい時があります。そんな時に AWS S3 + CloudFront は便利なのですが、AWS Console を触るのは嫌なので Serverless Framework を使う方法を試しました。Serverless Framework を単に CloudFormation + アップローダとして使っています。
例えば out/index.html
というファイルがあるとします。これを今から公開します。
<h1>Hello, World!</h1>
まず必要なツールをインストールしたり、aws の権限を設定したりします。ここでは serverless コマンドだけグローバルで、プラグインをローカルにインストールしました。
npm install -g serverless
npm install -D serverless-s3-sync serverless-cloudfront-invalidate
export AWS_PROFILE=hoge
serverless.yml
というファイルを用意します。
service: sls-s3-cloudfront-sample
frameworkVersion: "3"
plugins:
- serverless-s3-sync
- serverless-cloudfront-invalidate
provider:
name: aws
region: ap-northeast-1
custom:
s3Sync:
- bucketName: ${self:service}-${sls:stage}
localDir: out/
acl: public-read
cloudfrontInvalidate:
- distributionIdKey: DistributionId
items:
- "/*"
resources:
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: PublicRead
BucketName: ${self:service}-${sls:stage}
WebsiteConfiguration:
IndexDocument: index.html
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
TargetOriginId: ${self:service}-${sls:stage}
ForwardedValues:
QueryString: false
ViewerProtocolPolicy: https-only
Enabled: true
DefaultRootObject: index.html
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: http-only
DomainName: ${self:service}-${sls:stage}.s3-website-${aws:region}.amazonaws.com
Id: ${self:service}-${sls:stage}
Outputs:
URL:
Value: !Sub "https://${Distribution.DomainName}"
DistributionId:
Value: !Ref Distribution
デプロイします。
serverless deploy --verbose
....
URL: https://d1dwqegtyup9lu.cloudfront.net ← このような物が表示されたら成功。
要所要所解説します。
custom:
s3Sync:
- bucketName: ${self:service}-${sls:stage}
localDir: out/
acl: public-read
ここでは serverless-s3-sync
というプラグインの設定をしています。localDir:
でアップロードしたいディレクトリを指定します。acl: public-read
で外部に公開します。
cloudfrontInvalidate:
- distributionIdKey: DistributionId
items:
- "/*"
serverless-cloudfront-invalidate
プラグインの設定です。これによりデプロイするたびに CloudFront のキャッシュが消えます。 distributionIdKey: DistributionId
というのが非常にわかりにくいのですが、ここで操作する CloudFront Distribution の ID を間接的に指定します。デプロイの事前に ID はわからないので、後述の CloudFormation の Output セクションで Distribution ID を出力して、その Logical ID を distributionIdKey として指定できます。
消したいキャッシュは items に指定します。ここでは "/*" なので全て消しています。
resources:
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: PublicRead
BucketName: ${self:service}-${sls:stage}
WebsiteConfiguration:
IndexDocument: index.html
S3 を作成してウェブサイトとして公開します。多くの場合 S3 だけで十分だと思います。加えて CloudFront が必要なのは次の事情がある時です。
- HTTP ではなく HTTPS を使いたい。
- HTTPS が欲しいだけなら S3 の Object URL で実現可能です。ただし IndexDocument 機能がありません。Object URL はいくつか種類があります。
- DomainName: https://(bucket).s3.amazonaws.com/index.html
- DualStackDomainName (ipv6): https://(bucket).s3.dualstack.(region).amazonaws.com/index.html
- RegionalDomainName: https://(bucket).s3.(region).amazonaws.com/index.html
- HTTPS が欲しいだけなら S3 の Object URL で実現可能です。ただし IndexDocument 機能がありません。Object URL はいくつか種類があります。
なんとか/
にアクセスした時になんとか/index.html
が出て欲しい。(IndexDocument 機能)- IndexDocument 機能が欲しいだけなら S3 の Static website hosting で実現可能です。ただし HTTPS が動きません。
- URL は http://(bucket).s3-website-(region).amazonaws.com です。
もしこれらに当てはまる場合だけ以下の CloudFront が必要になります。
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: http-only
DomainName: ${self:service}-${sls:stage}.s3-website-${aws:region}.amazonaws.com
Id: ${self:service}-${sls:stage}
DefaultCacheBehavior:
TargetOriginId: ${self:service}-${sls:stage}
ForwardedValues:
QueryString: false
ViewerProtocolPolicy: https-only
Enabled: true
DefaultRootObject: index.html
CloudFront の設定はこのようになります。ややこしい部分を解説します。
- まず、
Origins
でコンテンツ本体を指定します。- ここで、
DomainName
に S3 Static website hosting のドメインを指定するのがコツです。Object URL を指定してしまうとなんとか/
からなんとか/index.html
への変換ができません。もっというとルートだけならDefaultRootObject: index.html
の指定で変換可能なのですが、サブディレクトリを変換してくれません。 Id: ${self:service}-${sls:stage}
は何でも良いのですがDefaultCacheBehavior
で使います。
- ここで、
DefaultCacheBehavior
でキャッシュの動作を指定します。TargetOriginId
はOrigins
で指定したId
です。ForwardedValues
は不推奨ですが、指定が簡単なので使いました。QueryString: false
によってクエリ文字列が異なっても同じコンテンツとみなしてキャッシュします。
Outputs:
URL:
Value: !Sub "https://${Distribution.DomainName}"
DistributionId:
Value: !Ref Distribution
このような Output セクションがあると、デプロイ完了後 URL をすぐに知る事ができて便利です。DistributionId
の方は serverless-cloudfront-invalidate
プラグインの distributionIdKey
で必要な項目です。