こんにちは!
プロダクトエンジニアリング部の吉永です。
LIFULLには2020年8月に入社しました。
今回は入社後、早々に任せていただいた仕事について記事を書きたいと思います。
内容としては、静的サイトをホスティングしている、とあるサービスのAWS構成を刷新し、CI/CDを導入したプロジェクトについて、どんな理由でどんなことに考慮して刷新していったのかについてご紹介していきます。
アジェンダ
刷新対象サイトの旧AWS構成図
まずは今回刷新の対象となったサイトの旧AWS構成図を見てください。
静的サイトをホスティングしているだけなのですが、EC2インスタンスがいたり、S3のバケットも複数あったりで実現している機能に対して、構成がやや複雑に見えます。
この構成のポイントを要約すると下記になります。
- S3のバケットが二つあり、それぞれPC用ページ、SP用ページのリソース類(html/css/jsなど)が格納されていた。
- サイトにアクセスしてきたデバイスに応じて、どちらのS3からコンテンツを返却するかをEC2インスタンス内で稼働しているnginxで振り分けていた。
- デバイス判定にはHTTPリクエストヘッダーのユーザーエージェントを使用。
また、上記サイトのローカル開発環境は下記のようになっていました。
- サイトのローカル開発環境はRuby on Rails。
- サイトの更新を行うエンジニアはローカルで更新後、Railsアプリケーションを起動し、シェルスクリプトを実行して静的コンテンツをローカルに保存、保存した静的コンテンツをS3にデプロイ、その後リポジトリを更新して、リモートへプッシュしていた。
AWS構成刷新の背景
今回、AWS構成を刷新した理由は下記2点です。
- Amazon Linux AMI のサポート期間終了に伴う対応が必要だったから。
- https://aws.amazon.com/jp/blogs/news/update-on-amazon-linux-ami-end-of-life/
- 上記ページにもあるように、EC2インスタンスを更新するなり、何かしらの対応を2020年中に行う必要があった。
- また、旧AWS構成を手動で構築したこともあり、テスト環境と本番環境の設定が微妙に異なっているなどが発生していた。
- https://aws.amazon.com/jp/blogs/news/update-on-amazon-linux-ami-end-of-life/
- 生産性の観点から、ビルドやテストだけでなくリリースプロセス全体を自動化したかったから。
- 必須ではなかったが、先述したようにローカルで更新したリソースをほぼ手動でデプロイしており、作業担当者がリポジトリのコミット、プッシュを忘れると、リポジトリの内容とデプロイされている静的コンテンツの内容が一致しない可能性があった。
- また、ローカル開発環境が作業者PCに直接環境を構築する前提だったこともあり、環境構築手順書のメンテナンスや、引継ぎ時の環境再現コストなどがかかっており、このあたりも改善したかった。
- 必須ではなかったが、先述したようにローカルで更新したリソースをほぼ手動でデプロイしており、作業担当者がリポジトリのコミット、プッシュを忘れると、リポジトリの内容とデプロイされている静的コンテンツの内容が一致しない可能性があった。
新AWS構成について
これらを踏まえ、現在使用できるAWSマネージドサービスから最適かつシンプルな組み合わせを模索して、新構成を設計しました。
AWS構成の大きな変更点は下記になります。
- EC2インスタンス内で稼働していたnginxの役割をCloudFront前段で動作するLambda@Edgeに変更。
- EC2インスタンスが不要になったので、前段にいたALBも一緒に削除。
- S3バケットも一つにまとめ、バケット内に/pc、/spフォルダを設け、それぞれのページリソースを格納。
- CloudFrontはS3の内容をそのままキャッシュするように変更。
EC2が構成からなくなったことにより、ランニングコストが事前の試算では従来の1/3くらいにまで削減できそうだということもこの構成にした大きな理由です。
Lambda@Edgeについて
Lambda@EdgeはCloudFrontのビューワーリクエストをトリガーにして動作するように設定し、HTTPリクエストに含まれるリクエストパスをユーザーエージェントに応じて書き換えるようになっています。
イメージが湧きやすいように設定したLamda@Edgeソースの一部を下記に記します。
// User-AgentからPC・SP判定を行ないリクエストURIを変換する exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const headers = request.headers; if (headers['user-agent']) { var uri = request.uri; // UA判定 if (isSpBrowser(headers['user-agent'])) { // スマホ uri = '/sp' + uri; } else { // PC uri = '/pc' + uri; } request.uri = uri; callback(null, request); return; } callback(null, request); };
インフラのコード化について
旧AWS構成が手作業で構築した環境だったので、テスト環境と本番環境で微妙に差異がありましたが、今回1からインフラ構成を作ったのでCloudFormationを用いてインフラのコード化を行いました。
コード化したことにより、テスト環境で構築した構成を本番環境で再現するのが容易でしたし、今後何かしらの変更を加える際にもコードベースで管理しておけば、テストと本番で設定が異なるなどの状態には陥りにくいと思います。
※実際には少しの変更ならAWS管理画面から直接行いたくなりますが・・・それをやってしまうとせっかくコード化した意味がないので、手動設定はなるべく排除しましょう。
CloudFormationについては別途Qiitaにて、この案件を通じて学んだ内容を備忘録代わりに投稿した記事があるので、もし興味があれば参照してみてください。
AWS CloudFormation入門
CI/CDについて
今回改善したかったもう一つの個所がローカルでサイトを更新してから、デプロイするまでの流れでした。
改善点をまとめると下記になります。
- ローカル開発環境をDockerで構築し、環境再現コストを削減。
- masterブランチにマージされたら、GitHub Actionsでデプロイ実行。
デプロイ作業を自動化したことでヒューマンエラーが混入しなくなり、作業者も安心してリリースできるようになりました。
また、副産物として、GitHub Actionsにてバージョン管理TAGの自動付加も行えるようにしたので、その件についてもQiitaに投稿しました。
もし興味があれば参照してみてください。
GitHub ActionsでX.Y.Z形式のバージョニング自動タグ付けを行う
まとめ
AWSについては前職では少し触った程度の浅い知識しかなく、入社早々任せていただいた仕事がAWS構成の刷新だったので一抹の不安はありましたが、最終的にはかなり色々なものを吸収させていただき、とても良い経験になりました。
今回の業務を通じて得られたものを下記にまとめました。
- AWS構成でどこにどれくらいのコストがかかっているのか?調べる力が身についた。
- AWSマネージドサービスは常に進化しており、数年前は実現できなかったことが近年ではシンプルな構成で実現できるようになっていたりするので、ベストプラクティスは常に変化していることを知れた。
- インフラのコード化を初めて行ったが、テスト環境で構築した環境を本番環境に簡単に構築できるメリットを実感できた
- 将来的に似たような構成を作る際に今回作ったCloudFormationが流用できる可能性を感じたので、インフラのコード化は業務効率化や標準化の観点からみても非常に有益な作業であると実感できた。
- CI/CDを導入することでリリース作業の負担を減らすことのメリットを実感でき、かつリリース作業を属人化することなく、だれでも簡単な操作でできるようになったので、リリース作業の心理的負担もだいぶ和らいだ。
以上となります。
最後までご覧いただき、ありがとうございました。