BASEでエンジニアリングマネージャーを担当している加賀谷です。普段は採用に携わったり、1on1での経験学習の促進などを通じて、個人と組織のアウトプットが大きくなるようにサポートする仕事をしています。また、サービス開発に関わる体験を良くしていくこともしています。その中で今回は、静的コンテンツのCI/CDでしていることを紹介したいと思います。
静的コンテンツのホスティング
静的コンテンツは、サーバサイドでリクエストに応じてレスポンスする内容を作成しないデータです。主に、サイト内で使う画像、CSS、JS、ランディングページなどのHTMLファイルになります。これらのファイルはよく、AWSのS3に置いてホスティングして前段にはCDNを配置し、Webブラウザの同時接続数を考慮してサービスとは別のホストに分散したりしますが、BASEでもそうしています。
静的コンテンツ用のGitリポジトリを用意
CSSやJSが成果物となる開発はBASEにおいては主にデザイナーとフロントエンドエンジニアが担っています。以前はPHPもJSもCSSも同じリポジトリで開発していたのですが、今ではサービスのメインGitリポジトリとは別のリポジトリで開発〜デプロイをするようにしています。もちろんメインGitリポジトリからいっさいのJSやCSSを無くしているわけではなく、TwitterのBootstrapのようにコンポーネントとなるCSSやJS、ランディングページなど、メインから独立できるファイル群をこのような別リポジトリに入れています。
git pushでCircleCIからaws s3 syncする
デプロイ先はS3で、git push
を契機にCircleCI上から aws s3 sync
しています。以前は手動で本番S3に画像をアップロードする属人的な場面も少なからずあったのですが、今ではリポジトリの中に画像を入れてCircleCI経由でS3に配置するようにしています。
開発ワークフローと環境別のURL
静的コンテンツリポジトリの開発ワークフローはメインのGitリポジトリと同じようにしています。
develop
ブランチから開発用ブランチfeature/xxx
を切って開発develop
へプルリクエスト&マージ- リリース時は
develop
からmaster
へ git-pr-release でリリース用プルリクエストを作成 master
を本番環境へデプロイ
環境ごとに静的コンテンツのURLが欲しいので、S3はそれぞれ用意してCircleCIが回るブランチでaws s3 sync
先を変えています。 develop
ブランチの時にはステージング用のS3、master
ブランチの時には本番、それ以外のブランチは開発用S3へ対応させています。
.circleci/config.yml
CircleCIではだいたい以下のようなことをしています。
- checkout
- awscliをインストール
- リポジトリ内の特定ディレクトリ配下の各ファイルにACLとcache-control、content-typeをつけて
aws s3 sync
- syncしたファイルのパスのCloudFrontのキャッシュを削除
また、アップロードされたファイルのURLなどをSlackへ通知して気付けるようにもしています。
version: 2 jobs: build: docker: - image: docker:17.12.0-ce-git environment: - TZ: "/usr/share/zoneinfo/Asia/Tokyo" - SYNC_OPTIONS: "--cache-control \"max-age=86400\" --acl public-read --size-only --no-progress --delete" - S3: "static-example-net" steps: - checkout - run: name: Install dependencies command: | apk add --no-cache \ py-pip=9.0.1-r1 \ curl \ curl-dev \ openssl pip install \ awscli==1.14.40 - run: name: Upload files to S3 command: | set -x tmpfile=`mktemp` for i in `cat .s3ignore | grep -v "^#"` do IGNORE_OPTIONS="${IGNORE_OPTIONS} --exclude \"**/${i}\"" done # リポジトリ内webroot/配下の各ファイルを適切なcontent-typeをつけてsync eval `printf "aws s3 sync webroot/ s3://%s/ %s %s --exclude \"*\" --include \"%s\" %s --content-type \"%s\"" "${S3}" "${AWS_PROFILE}" "${SYNC_OPTIONS}" *.css "${IGNORE_OPTIONS}" text/css` | tee -a $tmpfile eval `printf "aws s3 sync webroot/ s3://%s/ %s %s --exclude \"*\" --include \"%s\" %s --content-type \"%s\"" "${S3}" "${AWS_PROFILE}" "${SYNC_OPTIONS}" *.js "${IGNORE_OPTIONS}" application/javascript` | tee -a $tmpfile eval `printf "aws s3 sync webroot/ s3://%s/ %s %s --exclude \"*\" --include \"%s\" %s --content-type \"%s\"" "${S3}" "${AWS_PROFILE}" "${SYNC_OPTIONS}" *.json "${IGNORE_OPTIONS}" application/json` | tee -a $tmpfile eval `printf "aws s3 sync webroot/ s3://%s/ %s %s --exclude \"*\" --include \"%s\" %s --content-type \"%s\"" "${S3}" "${AWS_PROFILE}" "${SYNC_OPTIONS}" *.html "${IGNORE_OPTIONS}" text/html` | tee -a $tmpfile eval `printf "aws s3 sync webroot/ s3://%s/ %s %s --exclude \"*\" --include \"%s\" %s --content-type \"%s\"" "${S3}" "${AWS_PROFILE}" "${SYNC_OPTIONS}" *.png "${IGNORE_OPTIONS}" image/png` | tee -a $tmpfile eval `printf "aws s3 sync webroot/ s3://%s/ %s %s --exclude \"*\" --include \"%s\" %s --content-type \"%s\"" "${S3}" "${AWS_PROFILE}" "${SYNC_OPTIONS}" *.jpg "${IGNORE_OPTIONS}" image/jpeg` | tee -a $tmpfile if [ -s $tmpfile ]; then tmpdir=`mktemp -d` split -l 30 $tmpfile $tmpdir/ for splited in `find $tmpdir -maxdepth 1 -type f`; do # CloudFrontからのキャッシュを削除 PATHS=`grep -ao "s3://${S3}/.*$" ${splited} | sed "s/^s3:\/\/${S3}//g"` aws cloudfront create-invalidation ${AWS_PROFILE} --distribution-id ${CDN_DISTRIBUTION_ID} --paths ${PATHS} done fi
サーバサイドのリポジトリと分けて開発、デプロイするメリット
1日に何度も本番環境へデプロイをする状況下においても、1度にデプロイするコード量が減り確認範囲が狭くなることで、よりカジュアルにデプロイできることがメリットかなと思います。サーバサイドと同じリポジトリで開発していたときには、例えばランディングページのちょっとしたスタイル変更にも、CircleCIでサーバサイドの全テストを回してBlue-Green Deploymentリリースフローをする流れを必要とするために比較的変更の反映に時間がかかっていましたが、これもなくなり手続き的にも早いデプロイができるようになりました。
Slackからgit-pr-release
リリース用プルリクエストを作るときに、git-pr-release
コマンドを実行していますが、これをSlack Botでもできるようにしています。スマホのSlackアプリからも実行できるので便利です。GitHubのアカウントをSlackのアカウントに変換してメンションさせたり、リリースのサマリをSlackへ通知して変更がざっくり共有できるように工夫しています。
1. Botにメンションするとセレクトボックスを返すのでデプロイするリポジトリを選ぶ
2. 選択したリポジトリをその場で確認される
3. Yesを押すとgit-pr-releaseを実行
4. スレッドでメンションが返ってくる
5. 作成されたリリース用プルリクエストのURLが通知されるので、確認してmasterマージするとCircleCI経由で本番デプロイ
まとめ
今回は、GitHubとCircleCIでS3へデプロイするワークフローを紹介しました。まだリポジトリ内には画像のようなバイナリファイルがあまり多くないのでリポジトリが肥大化してgit pull/pushが苦になることはありませんが、多くなってきたら Git LFS も検討してみたいと思います。
BASEの開発チームでは良いサービスをつくっていくために開発体験の改善も楽しみながら活動しています。ご興味を持たれた方いらっしゃいましたら是非ご連絡いただければ幸いです。