こんにちは。WEARリプレイスチームのid:takanamito です。
先日、社内で初めてAWS Fargate上でRailsを動かす環境をつくったので、その事例報告をしようと思います。

WEARでは先日RubyKaigi 2019のスポンサーセッションでお話したように、Ruby on Railsへのシステムリプレイス作業を進めています。

そんな中、手作業で行っている運用を管理画面上でツール化したいという要望が上がってきました。リプレイス作業中であるため、できれば新機能はRailsで実装をしたいところです。しかし管理画面に相当するアプリケーションをデプロイするインフラはまだありませんでした。
WEARリプレイスでは、インフラの構築先としてAWSを採用しています。AWS上で新規にRailsを動かす環境を作るとすると有力な選択肢は次の3つになるかと思います。
リプレイスプロジェクトでは、私たちアプリケーションエンジニアがインフラ構築/運用に参加する事を踏まえ、運用作業をできるだけ少なくしたいという想いがありました。
SREチームとも相談した結果、よりマネージドな環境でコンテナを運用するためFargateでRailsアプリケーションのコンテナを動かす方式を採用することにしました。
運用工数の削減については弊社の塩崎が以前に書いた記事で言及されているので合わせてご参照ください。
今回の記事では実際にRailsを動かしていくにあたって考慮したことについて詳細をお話します。
弊社ではIQONなど既に運用中のRailsアプリケーションがありますが、コンテナは使っておらずEC2インスタンス上にChefで環境を構築しています。
今回、社内で初めてコンテナで動かすRails環境を構築することになったため、以下のような点で新しい仕組みを考える必要がありました。
現在運用中のRailsアプリケーションと、今回つくるFargate環境との比較を以下の表にまとめました。詳細をこの後に書いていきます。
| 非コンテナ(EC2) | Fargate | |
|---|---|---|
| assets配信 | Nginxからstaticに配信 | Railsから直接配信 |
| ログ出力 | ファイルに出力 | CloudWatch Logsに連携 |
| 秘匿情報の注入 | インスタンスの環境変数として渡す | コンテナ内に環境変数として渡す |
本来、新規開発のアプリケーションであればデータベースマイグレーションの仕組みを考える必要がありますが、マイグレーションは既にRails以外の別の方法で行われているため考慮から外しました。
よくあるRailsアプリケーションの構成ではNginxなどのリバースプロキシからassets配信をすると思いますが、今回はRailsから直接assets配信をすることにしました。
assets配信を考える際には以下の項目を考慮しました。
リバースプロキシを使わずにRailsでassets配信をするためCDNの利用も検討しましたが、そもそも社員しか使わないアプリケーションでトラフィックが極めて少なくレスポンスタイムも問題になるほど遅くなかったので導入は避けました。
また同時にasset_syncなどのgemを使ってS3にホスティングすることも考えましたが、Herokuのドキュメントでも触れられているようにThe Twelve-Factor Appの考えに従いこちらも導入は避けました。
The twelve-factor app is completely self-contained and does not rely on runtime injection of a webserver into the execution environment to create a web-facing service.
The Twelve-Factor AppUsing Asset Sync can cause failures. Heroku recommends using a a CDN instead of asset_sync whenever possible.
Please Do Not Use Asset Sync | Heroku Dev Center
実装した当時、FargateではawslogsドライバーのみがサポートされていたためRailsログはログドライバーを通じてCloudWatch Logsに送信されます。
デフォルトのRailsのログは複数行に改行されてしまい、CloudWatch Logs insightsでうまく検索できないためlograge gemを使ってjson形式に変換しています。
# config/environments/production.rbRails.application.configuredo config.lograge.enabled =true config.lograge.formatter =Lograge::Formatters::Json.new config.lograge.custom_options =lambdado |event| exceptions =%w(controller action format authenticity_token) {host: event.payload[:host],timestamp:Time.zone.now,params: event.payload[:params].except(*exceptions),exception: event.payload[:exception],exception_object: event.payload[:exception_object], }end ...end
DBのパスワードなどの秘匿情報をどのようにコンテナ内部に渡すかも課題でした。
例えばRailsのEncrypted Credentialsを使うのも選択肢のひとつですが、弊社では以前からAWS Systems Managerパラメータストアを使った秘匿情報の管理運用をしていたためそれに合わせた仕組みを採用しました。
Fargateプラットフォームバージョン1.3からシークレットを扱えるようになっていますが今回は採用を見送りました。理由は後述します。
具体的にはdocker entrypointに指定しているシェルスクリプトで、パラメータストアから取得した値をコンテナの環境変数に埋めています。
# DockerfileFROM ruby:2.6.3RUN apt-get update -qq && apt-get install -y build-essential nodejs awscli# ...中略WORKDIR /wearCOPY Gemfile /wear/GemfileCOPY Gemfile.lock /wear/Gemfile.lockRUN bundle installCOPY . /wearRUN bundle exec rails assets:precompileENTRYPOINT ["./docker-entrypoint.sh", "bundle", "exec"]
環境変数は次のようにして埋めています。
#!/bin/bashset -eexport DB_USER=$(aws ssm get-parameters --names"/db_user")export DB_PASSWORD=$(aws ssm get-parameters --names"/db_password")export SECRET_KEY_BASE=$(aws ssm get-parameters --names"/secret_key_base")exec"$@"
パラメータストアを使うことで秘匿情報にアクセスできる開発者を限定できる + CloudFormation(CFn)を使ってコードで権限管理が可能なところは利点かと思います。
そのため弊社ではAWSのオーケストレーションツールとしてCFnを使用しています。
Fargateプラットフォームバージョン1.3でサポートされたシークレットの仕組みを採用したかったのですが、今回のインフラ構築をしていた時点ではまだCFnにSecretの機能が反映されていなかったため、APIを通じてパラメータストアから値を取得する方法を選択しています。
開発状況はこのissueで報告されており、現在開発が進行中とのことです。
[ECS] [CloudFormation]: CloudFormation support for Secrets · Issue #97 · aws/containers-roadmap · GitHub
コンテナのメトリクス監視にはDatadogを使用しています。こちらは先述の塩崎の記事で紹介されていたのと同様、サイドカーパターンでコンテナのメトリクスを収集しています。
今回、自分で構築するコンテナデプロイ環境の構築が初めてだったため、かなり色んなハマりポイントにつまずきながらの作業になってしまいました。
Fargateは通常sshできない環境での作業を強いられるためそれによる確認作業の進めにくさは感じました。
特にコンテナ起動で試行錯誤している最中に「環境変数が設定できているか」など、インスタンスに入ってコマンドを叩けば一瞬で終わるはずの作業ができないのはストレスでした。
またAWSマネージドサービスを多用しているため、書いたコードが悪いのか、IAMの権限不足が悪いのかなど原因の切り分けにも苦労する場面が多かったです。
私は最終的にsshが可能なコンテナイメージを用意して確認をしました。これからコンテナデプロイ環境を構築される方は最初に用意しておくと確認作業が捗るかもしれません。
一部ではありますが、今回の環境構築で使用したCFnのymlファイルを置いておきます。
ECSTaskDefinitionRailsApplication:Type:'AWS::ECS::TaskDefinition'Properties:RequiresCompatibilities:-'FARGATE'Cpu:512Memory:1024NetworkMode:'awsvpc'TaskRoleArn:!GetAtt IAMRoleForRailsApplicationTaskRole.Arn # パラメータストアへのアクセス権限を付与ExecutionRoleArn:!GetAtt IAMRoleForRailsApplicationTaskExecution.ArnContainerDefinitions:-Image:!Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryRailsApplication}:latestName:'rails_application'Cpu:256Memory:512Command:-'puma'-'-e'-!GetAtt SSMParameterRailsEnv.ValueEnvironment:-Name:'RAILS_ENV'Value:!GetAtt SSMParameterRailsEnv.ValuePortMappings:-ContainerPort:80HostPort:80Protocol:'tcp'LogConfiguration:LogDriver:'awslogs'Options:awslogs-group:!Ref LogsLogGroupForRailsApplicationawslogs-region:!Ref AWS::Regionawslogs-stream-prefix:'rails_application'-Image: datadog/agent:latestName:'datadog'Cpu:256Memory:512Environment:-Name:'DD_API_KEY'Value:!Ref DatadogAPIKey-Name:'ECS_FARGATE'Value:'true'LogConfiguration:LogDriver:'awslogs'Options:awslogs-group:!Ref LogsLogGroupForRailsApplicationawslogs-region:!Ref AWS::Regionawslogs-stream-prefix:'rails_application-datadog'ECSServiceRailsApplication:Type:'AWS::ECS::Service'DependsOn:-IAMServiceLinkedRoleForECSRailsApplicationServiceProperties:Cluster:!Ref ECSClusterRailsApplicationDesiredCount:2LaunchType:'FARGATE'LoadBalancers:-ContainerName:'rails_application'ContainerPort:80TargetGroupArn:!Ref ElasticLoadBalancingV2TargetGroupExternalRailsApplicationNetworkConfiguration:AwsvpcConfiguration:AssignPublicIp:'DISABLED'SecurityGroups:-!Ref EC2SecurityGroupECSRailsApplicationSubnets:-!Ref EC2SubnetPrivateApplicationAZ1-!Ref EC2SubnetPrivateApplicationAZ2TaskDefinition:!Ref ECSTaskDefinitionRailsApplication
初期の環境構築に今までと一味違った苦戦があったことは事実ですが、EC2インスタンスの管理から解放されるメリットはやはり大きいと感じています。
弊社では今までの仕組みにとらわれず新しい事例を作ることに興味がある方、Webアプリケーションの開発/運用改善に興味がある方を募集しています。興味のある方はぜひ以下のリンクからご応募ください。
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。