用語について調べてみる
- Amazon S3は、スケーラビリティ、データ可用性、セキュリティ、およびパフォーマンスを提供するオブジェクトストレージサービス
- CloudFrontは、静的および動的なWebコンテンツ、ビデオストリーム、APIを世界中に安全かつ大規模に配信するコンテンツ配信ネットワーク(CDN)サービス
フロントエンドをS3とCloudFrontでデプロイするメリット
- CloudFront経由で配信しておくと、エンドユーザーからのリクエストはエッジロケーションにルーティングされるのでWebページの表示パフォーマンス高い
- キャッシュされていなくても、コンテンツは公共のインターネットではなくAWSのプライベートネットワークを通過し、CloudFrontはTCPハンドシェイクを最適化するため、リクエストとコンテンツのリターンは公共のインターネットを介したアクセスよりもはるかに高速
デプロイの流れ
- バージニア北部リージョンで、ACM Certificateを作成 (サイトのドメイン名を既に取得している前提)
- S3バケットを作成
- reactアプリケーションをbuildして、/buildディレクトリのファイルを作成したS3バケットに配置する
- アクセスポリシーを設定する
- CloudFront Distributionを作成
- Origin Domain Nameには作成したS3バケットを選択する
- バケットサクセスの制限には「はい」を選択する
- Origin Access Identityには、[新しい ID の作成] を選択する
- [バケットの読み取りアクセス許可を付与] には、[はい、バケットポリシーを更新します] を選択する
CloudFormationを使用する場合
acm.yml
## バージニア北部リージョンでスタックを作成する
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
DomainName:
Type: String
Resources:
ACMCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName'
SubjectAlternativeNames:
- !Sub '*.${DomainName}'
DomainValidationOptions:
- DomainName: !Ref DomainName
ValidationDomain: !Ref DomainName
ValidationMethod: DNS
static.yml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
ProjectName:
Type: String
DomainName:
Type: String
AcmCertificateARN:
Type: String
Resources:
StaticBucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
BucketName: !Ref ProjectName
StaticOai:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Ref AWS::StackName
StaticBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref StaticBucket
PolicyDocument:
Statement:
- Action: s3:GetObject
Effect: Allow
Resource: !Sub arn:aws:s3:::${StaticBucket}/*
Principal:
AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${StaticOai}
S3WebsiteOriginCachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
DefaultTTL: 86400 # default = one day
MaxTTL: 31536000 # default = one year
MinTTL: 60
Name: !Sub ${ProjectName}-s3-website
ParametersInCacheKeyAndForwardedToOrigin:
CookiesConfig:
CookieBehavior: none
EnableAcceptEncodingBrotli: true
EnableAcceptEncodingGzip: true
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Aliases:
- !Ref DomainName
Enabled: true
HttpVersion: http2
PriceClass: PriceClass_200
ViewerCertificate:
AcmCertificateArn: !Ref AcmCertificateARN
MinimumProtocolVersion: TLSv1.2_2019
SslSupportMethod: sni-only
Origins:
- Id: S3WebsiteOrigin
DomainName: !GetAtt StaticBucket.DomainName
S3OriginConfig:
OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${StaticOai}
CustomErrorResponses:
- ErrorCode: 403 # SPAをリロードしても大丈夫なように
ResponseCode: 200
ResponsePagePath: /index.html
DefaultRootObject: index.html
DefaultCacheBehavior:
TargetOriginId: S3WebsiteOrigin
CachePolicyId: !Ref S3WebsiteOriginCachePolicy
ViewerProtocolPolicy: redirect-to-https
CacheBehaviors:
- TargetOriginId: S3WebsiteOrigin
AllowedMethods: [GET, HEAD]
CachePolicyId: !Ref S3WebsiteOriginCachePolicy
Compress: true
PathPattern: '*'
ViewerProtocolPolicy: redirect-to-https
GitHubActionsでデプロイしてみる
frontend-deploy.yml
name: Deploy Frontend
on:
push:
branches:
- "master"
env:
REACT_APP_BACKEND_HOST: ${{ secrets.API_HOST }}
jobs:
deploy-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2.3.4
- 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
- name: Install dependencies
run: yarn
- name: Build
run: yarn build
- name: Deploy
run: aws s3 sync ./build s3://${{ secrets.S3_BUCKET_NAME }} --delete
APIもCloudFront経由で配信する
CloudFrontのBehaviorsにPathPatternを設定して、
例えば/api
の時はApplication Load Balancerなどに通信を流し、バックエンドAPIの処理を呼び出すことができます。
そうなると、同じドメインでバックエンドもフロントエンドもデプロイすることができるので良さそうです。
上記したCloudFrontのデプロイだとSPAでリロードしてもエラーがでないように、CloudFrontのCustomErrorResponsesでステータスコード403, 404が帰ってきた場合にステータスコード200でindex.htmlを返す設定にしていました。
しかし、CloudFrontからAPI側、S3側両方に通信を流す場合には、これでは不具合が起きることがあるかと思います。
例えばAPIにリクエストした時に404 Not Foundが返ってくるべきところだったのに、設定のせいで200が返ってきてしまい適切なエラーメッセージをエンドユーザーに表示できないというようなことも起こるかと思います。
このような場合のベストな対処法に関しては、理解しきれていないところもあり、今後追記できたらと思います。