S3 + CloudFrontで静的サイトホスティングしたので作業ログを残す
AWSで静的サイトをホスティングする際の王道パターンと呼ばれているS3 + CloudFrontの構成、 今更ながら初めて構築したので作業ログを残しておく。
構成
こんな構成。
S3
まずはバケットを作る。本番用とStage用。
後から上書きするが、まずはローカルでindex.htmlファイルを作ってアップロードしておく。
今回はS3のファイルはpublicにせずCloudFront経由のみアクセスを許可するようにしたので、 S3の設定で「ホスティングの有効」はしない。
自分が作ってるときこの設定を有効にしていて、 CloudFront経由でAccess deniedで弾かれてハマった。
CloudFront
Distributionを作る
「Create Distribution」からWebの「Get Started」と遷移していく。
設定入力画面ではこんな感じの設定になった。
Origin Domain Name: テキストフィールドにフォーカスするとバケットがサジェストされるので何も考えずにそれを選択。
Restrict Bucket Access: Yesを選ぶと「Origin Access Identity」に自動で値がセットされる。
Grant Read Permissions on Bucket: 「Yes, Update Bucket Policy」を選ぶことでバケットポリシーも更新してくれる。
TTL: 普通にS3の静的ファイルを配信するだけなのでCloudFrontにキャッシュさせる時間を入れる。自分の場合は一ヶ月にした。
Compress Objects Automatically: Yes, ファイルのgzip配信を有効にする。
SSL Certificate: 独自ドメインを取ってSSLで配信する場合は設定する必要があるが今回は省略。
「Create Distribution」ボタンを押すとDistributionの作成が始まる。
完了するまでだいたい20~30分ほどかかる。
GitHub
普通にリポジトリ作る。
CircleCI
CircleCIからS3にファイルをアップロードする用のiamユーザーを事前に作っておく。
自分はユーザー名を「circleci」にした。
CircieCIのページに行って Add Project -> 設定 -> AWS Permission と遷移して 上記iamユーザーのAccess Key ID と Secret Access Keyを入力して保存しておく。
最終的に .circleci/config.yml はこんな感じになった。
version: 2 jobs: deploy: working_directory: ~/circle-ci docker: - image: circleci/node:latest steps: - checkout - run: name: Setup AWS credentials command: | sudo apt-get update && sudo apt-get install -qq -y python-pip libpython-dev curl -O https://bootstrap.pypa.io/get-pip.py && sudo python get-pip.py sudo pip install -q awscli --upgrade mkdir -p ~/.aws printf "[default]\nregion = ap-northeast-1\nnoutput = json" > ~/.aws/config printf "[default]\naws_access_key_id = ${AWS_ACCESS_KEY_ID}\naws_secret_access_key = ${AWS_SECRET_ACCESS_KEY}" > ~/.aws/credentials chmod 600 ~/.aws/* - run: name: deploy static files to s3 stage command: | if [ "${CIRCLE_BRANCH}" == "stage" ]; then aws s3 sync ./public s3://<your stage bucket>/ --delete fi if [ "${CIRCLE_BRANCH}" == "master" ]; then aws s3 sync ./public s3://<your prod bucket>/ --delete fi workflows: version: 2 deploy: jobs: - deploy
解説としては
- 無駄なファイルをS3に上げないように公開用のディレクトリを用意してそれをS3と同期するようにした
- stage, masterで別々のjobを用意する方法はうまくいかなかったので、commandでブランチの分岐を書いた
- awscli用のimageを用意しておけばよかったっぽい
いずれにしても、これでGitHubでstage, masterにマージされたタイミングでS3にファイルがアップロードされるようになった。
Lambda
CircleCIからS3にアップロードするだけだと不十分で、最新のファイルを配信するためにはS3のファイル更新時にCloudFrontのキャッシュをinvalidationしたい。 ので、S3のファイル更新をLambdaで検知してLambdaからCloudFrontにinvalidationする。
以下、lambdaのコード。
import json import urllib.parse import boto3 import os import time s3 = boto3.client('s3') def lambda_handler(event, context): print("Received event: " + json.dumps(event, indent=2)) bucket = '<your bucket name>' key_orig = event['Records'][0]['s3']['object']['key'] key_path = "/" + urllib.parse.unquote_plus(key_orig, encoding='utf-8') print("bucket: " + bucket) print("key path: " + key_path) client = boto3.client('cloudfront') invalidation = client.create_invalidation(DistributionId=os.environ['CLOUDFRONT_DISTRIBUTION_ID'], InvalidationBatch={ 'Paths': { 'Quantity': 1, 'Items': [key_path] }, 'CallerReference': str(time.time()) })
stage, masterそれぞれのDistribution用にfunctionも作った。
おわり
書き疲れたのでこのへんで。
S3にファイルを上げるためにCIでコンテナ立ち上げるのはリソースの無駄使いな気がしてならないが、
まあ凝ったことしなければ無料で使えるから、自動化できる部分はできるだけ自動化しましょうということで。