saeki’s blog

The limits of my code mean the limits of my world.

S3 + CloudFrontで静的サイトホスティングしたので作業ログを残す

AWSで静的サイトをホスティングする際の王道パターンと呼ばれているS3 + CloudFrontの構成、 今更ながら初めて構築したので作業ログを残しておく。

構成

f:id:t_saeki:20180501212914j:plain

こんな構成。

S3

まずはバケットを作る。本番用とStage用。
後から上書きするが、まずはローカルでindex.htmlファイルを作ってアップロードしておく。

f:id:t_saeki:20180430165202j:plain

今回はS3のファイルはpublicにせずCloudFront経由のみアクセスを許可するようにしたので、 S3の設定で「ホスティングの有効」はしない。

f:id:t_saeki:20180430164715j:plain

自分が作ってるときこの設定を有効にしていて、 CloudFront経由でAccess deniedで弾かれてハマった。

CloudFront

Distributionを作る

「Create Distribution」からWebの「Get Started」と遷移していく。

設定入力画面ではこんな感じの設定になった。

f:id:t_saeki:20180430171117j:plain

Origin Domain Name: テキストフィールドにフォーカスするとバケットがサジェストされるので何も考えずにそれを選択。
Restrict Bucket Access: Yesを選ぶと「Origin Access Identity」に自動で値がセットされる。
Grant Read Permissions on Bucket: 「Yes, Update Bucket Policy」を選ぶことでバケットポリシーも更新してくれる。

f:id:t_saeki:20180430171152j:plain

TTL: 普通にS3の静的ファイルを配信するだけなのでCloudFrontにキャッシュさせる時間を入れる。自分の場合は一ヶ月にした。
Compress Objects Automatically: Yes, ファイルのgzip配信を有効にする。

f:id:t_saeki:20180430171218j:plain

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でコンテナ立ち上げるのはリソースの無駄使いな気がしてならないが、
まあ凝ったことしなければ無料で使えるから、自動化できる部分はできるだけ自動化しましょうということで。

「現場で役立つシステム設計の原則」読んだ

読んだ。内容としてはDDD本の実践編という感じで非常に勉強になった。

自分は仕事でも趣味でも MVC + Service でレイヤを組んでwebアプリを作ることが多いんだけど、雰囲気でServiceをやってるように感じていつもモヤモヤしてた。
特にRailsだとどうしてもActiveRecordを中心とした作りになるから、業務ロジックがテーブル設計に引っ張られて疎結合にならない。
それに画面の設計にも引っ張られるとデータの加工なんかもServiceでゴリゴリやっちゃってコードの見通しが悪くなる。
そもそも自分が理解してたモデルの上位層としてのService、という位置付けがよくないのかもしれない。

オブジェクト指向設計の他には、プロジェクトにおける上流工程、テーブル設計、Web Api、マイクロサービスなどweb開発において抑えておくべきトピックをオブジェクト指向の観点から網羅的に書かれていて勉強になった。

ファイルの内容をgrepして行を絞り込む

Apacheのログを調べる必要があったのでメモ。
賢い人はFluentdやElasticsearchやKibanaを使ってます。使いたい。

$ grep -i <検索文字> <ファイル名> | cat
$ grep -i "get /***" /var/log/httpd/access_log | cat

-iオプションがあると大文字小文字を区別しない。

catする前に絞り込んだ行数を確認すると良いかも。

$ grep -i <検索文字> <ファイル名> | wc -l
$ grep -i "get /***" /var/log/httpd/access_log | wc -l
10

Ubuntuでシステム日時を変更する

webサービスをテストするときに行ったオペレーション覚え書き

現在日時確認

$ date
Wed Sep 13 05:03:32 UTC 2017

変更

$ date -s "09/30 18:00 2017"
Sat Sep 30 18:00:02 UTC 2017
# 2017年9月30日18:00 にセット

変更確認

$ date
Sat Sep 30 18:00:02 UTC 2017

iTerm2起動時に複数窓を開く

1. 複数窓を開く

f:id:t_saeki:20170403112905p:plain

2. Window -> Save Window Arrangement

f:id:t_saeki:20170403113259p:plain

名前入力ボックスが出るので適当に設定。

3. Preferences -> General

Startup欄の「Open Default Window Arrangement」 を選択
Open profiles window を選択

f:id:t_saeki:20170403113545p:plain

4. Preferences -> Arrangements

f:id:t_saeki:20170403113906p:plain

先ほど設定した Window Arrangement を選択、Set Default で設定完了。