この記事はSafie Engineers' Blog! Advent Calendar 6日目の記事です。
はじめに
こんにちは。第2開発部でフロントエンドエンジニアをしている東條です。今回は、2024年新卒エンジニア研修におけるインフラ分野の開発についてお話ししたいと思います。
セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介
セーフィーでは全体研修後、約3ヶ月のエンジニア研修があり、エンジニアとしての基礎を学びつつ、社内課題を解決するプロダクトを開発しました。課題選定から開発言語、体制まで全て自分たちで決める形式で、私たちは「isai connect」を開発しました。これは、他部署の方とランチに行くと会社がランチ代を負担してくれる「異才ランチ」制度をより活用し、活発化を図るためのプロダクトです。詳しくは以下の記事でご覧ください。
- 新卒研修の紹介とチーム開発前にやったこと
- 2024年新卒エンジニア研修-アジャイル開発編
- 2024年新卒エンジニア研修-isai connectについて
- 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド
- 2024年新卒エンジニア研修-フロントエンド開発編
- 2024年新卒エンジニア研修-インフラ構築編 ←本記事
- 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編
- 2024年新卒エンジニア研修-新卒研修の成果発表とその後
導入
今回、個人としてはじめて IaC(Infrastructure as a Code
) によるインフラ構築を体験しました。使用したツールは Terraform です。そもそも記法やどんな仕組みで動いているのかも知らなかったため、他のインターン生がインターンのときに書いていたコードや公式のチュートリアルのコードとにらめっこしながら概要を把握していきました。
最終的な全体構成としては、
- フロントエンドアプリ
- S3 にビルドしたファイル群を配置
- CloudFront から配信
- APIサーバー
- ECS Fargate でコンテナを立ち上げ
- DB
- Aurora MySQL
のようになっています。
最初は Single AZ で組んでいたのですが、インフラの構築をしている時期にたまたま AWS の方が入門者向けにハンズオン形式で講義してくださる貴重な機会があり(https://engineers.safie.link/entry/safie_aws_starters_report)、そのなかで Multi AZ 構造をとることで可用性を担保できると教えていただいたので早速取り入れました。
余談ですが、AWS の DB 系の Multi AZ 構成では Writer (書き込み専用)と Reader (読み取り専用)が別れていて完全な2台構成になっていないということを初めて知りました。書き込みが複数の AZ で発生すると一貫性が崩れるので当たり前といえば当たり前ですが賢いですね。Writer が死ぬと Reader のうち1台が Writer に昇格するのは動物の生存本能みたいでとても合理的でおもしろい仕組みだと感じました。
この記事内では、インフラの構築および運用の中で工夫した点や苦労した点についていくつか紹介させていただきたいと思います。
定期実行について
24新卒チーム内で機能開発を進めていく中で、実際にコネクトしてお誘いのメッセージをおくったはいいものの返事をくれなかったときはどうすればいいのかという問題が浮き彫りになりました。ここで、3日経ったところで自動でキャンセルする機能の開発を行うことになりました。API のサーバーの機能的には、定期的に DB を見てコネクトの作成日から3日経っているかを判定してキャンセル処理をすればよいのですが、本番環境で定期実行をどうするかに結構悩みました。
最初思い浮かんだのは cron スケジュールですが、そもそも Fargate では Linux 自体を起動しているわけではなくPython:3.11-slim
のイメージを起動していますし、今後オートスケーリングなどをした場合は無駄に複数回処理を回すことになりバグが発生する可能性もあり却下しました。
そこで、AWS のサービス自体でなんとかできないかと色々調べ回った結果2つ選択肢の目星をつけました。
- Lambda を使用して Aurora MySQL を直接操作する
- Event Bridge Scheduler で ECS タスクを定期実行する
最終的には EventBridge Scheduler で ECS タスクを定期実行する方針で決めました。理由としては、学習コストが少ないことです。これは、チーム内のスケジュール的な問題ではありますが、この定期実行のタスクに取り掛かったのが最終のスプリントであったため、学習コストを抑えて短期で実行可能な状態まで持っていく必要がありました。
実際の実装としては単純なもので、API サーバーで定期実行用の API エンドポイントを用意して定期実行 ECS の中では curl イメージを使用してただその API を叩いています。このような実装にした理由は、開発において API サーバーとキャンセル機構が分離すると保守性が下がってしまうためです。
バージョン更新とメンテナンスについて
今回のインフラ構築では、アプリケーションの特性上アクセス頻度が高くないことと、集中した開発が研修中だけであることから、AWS のコストを抑えるためステージング環境を用意せず本番環境のみの構成としています。その中で困ったことが3点ありました。
- バージョンが更新されているのかどうかが判断できない
- バージョン更新中にアクセスするとエラーになる
- Slack への通知が確認できない
それぞれどのように解決したかをご紹介します。
バージョンが更新されているのかどうかが判断できない
これはステージング環境があっても起こることだとは思うのですが、イメージやファイルをあげてアップデートしてもそれが反映されているのか、エラーで反映されていないのか、が判断できませんでした。フロントエンドだとデザインの修正や機能の修正が目に見えるのである程度問題はないのですが、とくにサーバーサイドのバグ修正や DB のスキーマ修正などは API を叩くまでは判断できないためデプロイ時に判断できる仕組みが必要でした。
対策としては、ビルド時にバージョンを入れ込む仕組みを導入しました。デプロイする際に CI で自動で付与していたバージョン(vx.x.x
)を指定してビルド&デプロイすることで外部から簡単にバージョンを確認できるようにしました。
具体的には、フロントエンドではビルドするコマンドにバージョンを渡すことで
meta タグにバージョンが埋め込まれるようにしました。バックエンドではイメージのビルド時に Dockerfile で環境変数を ARG と ENV を用いて固定することで Swagger UI に表示し、それぞれバージョンの表示を実現しています。以下画像はv1.6.0
状態を確認した際のものです。
フロントエンド | バックエンド |
---|---|
このあたりはまだデプロイを含めて自動化できていないため、今後の課題として CI/CD を充実させたいです。
バージョン更新中にアクセスするとエラーになる
フロントエンドのアプリケーションはキャッシュを削除しない限り以前のバージョンが配信されますし、バックエンドも更新をかけて新しいコンテナが起動してから置き換わるのでそれぞれがエラーを吐くことはないのですが、デプロイ自体のラグが発生するためフロントエンドで叩いていた API が新しいパラメータを要求するようになっていたり、逆も然りが置きます。
そこで対策としてメンテナンス期間を導入しました。想像つきやすいのは銀行系のアプリなどである「◯月◯日◯時から◯時はメンテナンスのため一部機能がご利用できない可能性があります」だと思います。AWS の ALB に固定503 Service Unavailable
エラーを返すルールをメンテナンス中のみ追加し、フロントエンド側は503 Error
を返却されたときにメンテナンスページを開くように実装することでデプロイ中の数分間のみサービスを停止しています。
Slackへの通知が確認できない
本番環境の API を叩くと実際に DM が届いてしまうので、Slack からのアクションを確認できないという問題が発生しました。プレリリース前の開発中はテスト用の Slack チャンネルを作成することで実際に DM を飛ばすことなく確認できたのですが、本番環境ではそうはいかないので代替案を考える必要がありました。
色々考えた結果、最終本番環境に Slack App のテスト環境を作りました。具体的には、Slack App を開発検証用に DM 権限を制限した状態で作成し、図のように開発検証用の Slack App には本番環境のテスト用エンドポイントを指定、テスト用エンドポイントへのリクエストボディをそのままテスト用チャンネルに送信するように実装しました。こうすることでテスト用チャンネルに送られてきたリクエストボディをローカル環境で立ち上げている API サーバーに送信することができます。これによりで Slack のアクションを確認することが可能になりました。(ただし、参考として ngrok などを使ってローカル環境で Slack App 自体をモックすることは可能なようです)
感想
今こうして振り返るとこんな感じに構築していたらもっと楽だったかもしれない、今ならこうするといった反省点が山盛りではあるのですが、個人開発でも触ってこなかったインフラの分野についてチーム開発を通して試行錯誤しながら触れることができたのは良い経験だと考えています。また、新卒でのチーム開発を通して配属されたあとも未だに一緒にお昼ごはんを食べているくらい仲が良くなったので、何かあったときに気軽に相談できる仲間を社内に作れたという点はとても重要でした。