はじめに
この記事は STORES Advent Calendar 2023の 27日目の記事です。
STORES 予約 でバックエンドエンジニアをしているaao4seyです。 昨年 STORES 予約 はアプリケーション実行基盤をEC2からECS環境に移行しました。(以下ECS化と呼びます。)その際の設計〜移行まで内容を STORES 予約 コンテナ化への道のり 前編 にて公開させていただいておりました。この記事ではそれに続く後編として「切替後に出た問題」、「追加で対応したこと」や「当初の課題が解決されたのか」についてご紹介させていただければと思っています。
(前編を読んでいただいてからのほうがわかりやすいと思いますので、ぜひ前編も御覧ください。)
切替後に出た問題
ECS環境への切り替えの際には大きな問題は出なかったのですが、運用開始後にいくつか問題となる事象が発生したので、それらの問題にどう対応したのか、についてご紹介していきます。
ALB -> ECSタスク間の通信で504が多発する
運用開始してしばらくするとECS環境のALBとRailsアプリケーションが動くタスク間の通信がエラーになり5xxエラーが発生しDatadogのアラートが上がる事象が発生しました。デプロイのタイミングに重なっていたこともあり、デプロイに起因する問題なのかな?と考えていたのですが、ログを調査していく中で以下がわかりました。
- デプロイ(CodeDeployによるB/G デプロイメント)に起因する問題ではないということ。
- 当初は起動しきっていないタスク、またはkillされてしまったタスクにトラフィックが流れてしまっていたのでは?と推測していましたがそうでは有りませんでした。
- 特定のタスクのみがエラーを返していること。
- エラー近辺のALBログを確認したところ、
target_ip
が特定のIPであるものでのみエラーが観測されていました。
- エラー近辺のALBログを確認したところ、
調査時点ではこれ以上の情報はなく、調子の悪いタスクがたまたま割り当てられてしまっただけなのかなと考えていたのですが、同様の事象が何度か続き頻繁にアラートが上がるようになってしまったため対策が必要となりました。
対策
問題について調査していたところ以下の記事に出会いました。
ロードバランサ側でラウンドロビン等の方法でリクエストを振り分けるより、OS内にプロセスを複数立ち上げてリクエストを分散したほうが効率良い、といった内容が紹介されています。
問題が発生していた当時、4vCPU / 8GB をタスクにアサインしており、そのunicornのプロセス数はコア数と同じになるように設定しておりました。EC2環境ではより大きなインスタンスタイプを利用し1サーバ内でたくさんのunicorn プロセスが稼働していたのですが、Fargateの制約1により小さなタスクをたくさん並べる必要があり1サーバで稼働するunicornプロセス数も少なくなっているという状況でした。
これにより記事にあるように、すべてのプロセスがビジーなタスクに対してALBがリクエストを送ってしまい、その結果瞬間的にエラーになってしまっているのではという仮説ができました。
ただ、Fargateは1タスクに4vCPUしか割り当てられないしどうしよう...と思っていたところ、AWSからタイムリーなアップデートのお知らせがきました。
アップデートの内容を受けて以下のような対応を行いました。
- 1タスクに割り当てるリソースを増加
- before: 4 vCPU / 8 GB
- after: 8 vCPU / 16 GB
- タスク数を半分に減らす
この対応後、度々発生していた瞬断によりアラートが上がる問題が解消しました 🎉
GitHub Actionsでのデプロイがたまに失敗する
GitHub Actionsでデプロイ中にecspresso内でAWS APIのスロットリングエラーが発生しデプロイジョブが失敗してしまう事象が発生しました。また、 STORES 予約 の提供しているサービス上2複数環境に同一バージョンのアプリケーションを配布する必要があり、1デプロイで同時に複数のecspressoコマンドを同時に呼び出していたのが原因と思われました。
対策
愚直にGitHub CI上でリトライの処理を書き、失敗した場合に何度かリトライするようにしました。
# Retry configurations RETRY_COUNT=0 MAX_RETRY_COUNT=5 WAIT_TIME=2 # Loop until the command succeeds or RETRY_COUNT achives MAX_RETRY_COUNT. until (ecspresso deploy \ --config ${CONFIG_FILE_PATH}/config.yaml \ --envfile ${CONFIG_FILE_PATH}/env \ --latest-task-definition ; do if [ ${MAX_RETRY_COUNT} -eq ${RETRY_COUNT} ]; then exit 1 fi RETRY_COUNT=$(( RETRY_COUNT + 1 )) echo "Retry: ${RETRY_COUNT}, The command will be retried after $WAIT_TIME sec" sleep $WAIT_TIME WAIT_TIME=$(( WAIT_TIME * 2 )) done
今ではecspresso v2がリリースされ、スロットリングエラーが起こりづらいようになっているのですが、当時はエラー回避のために愚直なことをやっていました。
以下の「設定ファイルでCodeDeployアプリケーション名とデプロイグループ名を指定できます」のあたりにスロットリングエラーの改善について記述があります。
移行後に追加で対応したこと (オートスケーリング)
ここからはECS化後に追加で対応したオートスケーリングについてご紹介します。 STORES 予約 は比較的トラフィックの傾向がはっきり出ていたので、まずはApplication AutoScalingの機能を利用して夜間縮退を行うことにしました。
Application AutoScalingの設定は以下のようになっています。
- TargetTracking
- メトリクスとしてCPU利用率を指定しています。しきい値として指定したCPU使用率に応じて、Application AutoScalingのMinCapacity / MaxCapacityの間でタスク数を調整してくれるようになります。
- Scheduled Action
- 設定したスケジュールに応じてApplication AutoScalingのMinCapacity / MaxCapacity を更新します。
- こちらの設定で、夜間はCapacityを減らした設定に更新し、朝にその設定を元に戻すといった具合です。
EC2のAutoScaling Groupと違い、desireCountに相当する設定がないため、b.のScheduled Actionが発動した瞬間にすぐに台数が調整されるわけではないという点がEC2と違うなと感じた部分でした。例えば縮退する場合は b. を契機にMinCapacityを通常より少ない数に設定するかつ、a.のTargetTrackingでCPU使用率がしきい値より低ければタスクを減らしていってくれて結果的にMinCapacityの数までタスク数が減っていくという動きになります。
以下がオートスケーリング導入後のタスク数の変動を表すグラフです。
当初の課題が解決したのか?
最後に前編で上げていた課題が解消されたのかをご紹介できればと思います。再掲になりますが以下のようなスケーラビリティに関する課題感がありました。
ECS化後は、Packerを利用したゴールデンイメージの作成等のプロセスはもちろんなくなり、GitHub Actionsを通して希望する台数を指定するだけというシンプルな作業に置き換わりました。増強作業の時間は1時間以上かかっていたものが、新しいタスクが起動するまでの1 ~ 2分程度で収まるようになりました。 また、Rails / Next.js / Nginx を別のECSサービスとして分離したので部分的なスケールも行えるようになり、コストパフォーマンスが良くなりました!
今後の展望
Webサーバ部分のECS化は完了したのですが、現在バッチサーバのECS化に鋭意取り組んでいます!また、デプロイフローをもっと効率化して早くしたいといった運用の課題もたくさんある状況です! このブログをご覧になって、興味が湧いた方はぜひカジュアル面談等で話を聞きにきていただければと思います!