AWS Fargate を使って開発ブランチの Rails アプリケーションをデプロイする

みんなのウェディングのインフラエンジニア横山です。
今回は開発ブランチのデプロイにAWS Fargateを利用し始めた話について書きます。

開発ブランチデプロイとは、開発中のトピックブランチを社内のディレクターやデザイナーに確認してもらうために、開発サーバにデプロイすることを指します。

弊社では永らく以下の記事でお伝えしたように、単一サーバへの複数の開発ブランチデプロイを行なってきました。
開発ブランチをデプロイする

今回、こちらの手法を刷新して、デプロイ先としてFargateを利用するように変更しました。

これにより、以下のようなメリットがあります。

  • サーバのリソース制約から解放されるため、同時に何個でもデプロイできる。
  • ブランチ単位でタスクが起動するので、利用頻度に応じて柔軟に課金されるようになる。

どのように実現しているのか

ここからは実際の仕組みについてお話しします。
まず、概略図は以下の通りです。

以下、順番に説明していきます。

  1. Slackのスラッシュコマンドで /deploy wedding development ブランチ名 を実行
    スラッシュコマンドが実行されるとSlackからAmazon API GatewayにPOSTリクエストが行われます。
    こちらのスラッシュコマンドについては以下の記事を確認ください。
    インフラエンジニアがSlack スラッシュコマンド + API Gateway + Lambdaでサーバーレスボットを作った話
    Amazon API Gatewayはスラッシュコマンドの引数(wedding development ブランチ名)をLambdaに送ります。

  2. LambdaでFargate上へのタスク実行をトリガー
    Amazon API Gatewayから送られてきたスラッシュコマンドの引数から、ブランチ名を取り出し、環境変数BRANCHにブランチ名を設定しタスクの実行をトリガーします。
    LambdaはPythonで実装しているので具体的には以下のメソッドを呼んでいます。
    https://boto3.readthedocs.io/en/latest/reference/services/ecs.html#ECS.Client.run_task

  3. コンテナの起動スクリプトの中でgit cloneを実行
    環境変数BRANCHに指定したブランチがgit cloneされます。

  4. コンテナの起動スクリプトの中でRoute53にDNSレコードを登録
    ブランチ名を元にした、ブランチ名.devel.example.comといった形式のDNSレコードを登録します。
    起動スクリプトはPythonで実装しているので具体的には以下のメソッドを呼んでいます。
    https://boto3.readthedocs.io/en/latest/reference/services/route53.html#Route53.Client.change_resource_record_sets

以上のような流れでデプロイがされています。

こんなところどうなってるの?

すでに同じ名前のブランチがデプロイされている時、2重起動しないの?

コンテナの起動スクリプトの中で、古い方のタスクを落とすようにしています。
また、DNSレコードについてはUPSERTで新しいタスクのIPに上書き更新してるので、別途削除する必要はありません。

起動はいいけど削除はどうやってるの?

別のLambdaで定期削除しています。
タスクの起動時刻を確認して、24時間以上たったタスクを削除するようにしています。
現状は24時間を限度としていますが、このあたりは利用状況に合わせて変更していく予定です。

つまづいたところ

ECSサービスだと環境変数の上書きができない

当初はタスクではなく、サービスとして起動することを目指しました。
これは、以下のAmazon ECSサービスディスカバリが東京リージョン対応した暁にはDNS登録を自動化したかったためです。
Amazon ECS サービスディスカバリ
しかし、検証を進めていく中で、サービスだと実行時に環境変数の追加ができないことが判明しました。
環境変数でブランチ名を渡すことを想定していたので、これには困りました。 環境変数で渡せないとなると、CodeBuildを利用して、docker buildをブランチごとに行い、ブランチを組み込んだイメージを作成する必要があります。
しかし、この方法だと以下のようなデメリットがあります。

  • docker buildに時間がかかる
  • CodeBuildの仕様上、並列で複数ブランチのデプロイ作業を進められない。

ということで上記でお伝えした、タスクとして起動することを選択しました。

inotify上限に引っかかりRailsが起動しない

はじめてFargate上でタスクを実行した時、Railsが以下のエラーで起動しない問題に突き当たりました。

FATAL: Listen error: unable to monitor directories for changes.
Visit https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers for info on how to fix this.

エラーに解決法のURLが記載されているので、そちらのページにある通りに、inotifyの監視可能ファイル数の上限を増加させようとしました。
https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers

しかし、inotifyの監視可能ファイル数の上限変更のためには、コンテナをprivileged: trueで動かす必要がありました。
Fargateではコンテナをprivileged: trueで動かせないという制限があったためinotifyの監視可能ファイル数の上限は変更できませんでした。 https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html

privileged タイプ: ブール値

必須: いいえ

このパラメーターが true のとき、コンテナには、ホストコンテナインスタンスに対する昇格されたアクセス権限 (root ユーザーと同様) が付与されます。

このパラメータは、Docker Remote API の コンテナを作成する セクションの Privileged と docker run の –privileged オプションにマッピングされます。

注記

このパラメータは、Fargate 起動タイプを使用する Windows コンテナまたはタスクではサポートされていません。

もうお手上げかと思ったところ、アプリケーションのdevelopment.rbで以下の通り、設定していることを見つけました。

config.file_watcher = ActiveSupport::EventedFileUpdateChecker

そこで、以下のようにunless ENV[“DISABLEFILEWATCHER”]をつけ、コンテナの起動スクリプト内でRails起動時に同名の環境変数を渡すことにしました。

config.file_watcher = ActiveSupport::EventedFileUpdateChecker unless ENV["DISABLE_FILE_WATCHER"]

これにより、Fargate上ではfile_watcherが設定されなくなり、無事にRailsが起動できるようになりました。

まとめ

AWS Fargateを利用することで、とても簡単かつ効率的に開発ブランチのデプロイを行うことができました。 機会がありましたら、皆さんもこちらの記事を参考に是非作ってみてください!