(2021-04-19追記)下記内容を追記しました。
- ログの出力方法
- 注意点
人生初の某春のパン祭りに参加中のWebエンジニアの本間です。早くゴールしてご飯に戻りたい...。
さて、弊社のRailsアプリケーションの多くは、Amazon ECS + AWS Fargateの組み合わせで稼働しています。 過去はEC2上で動かしていたのですが、ECS + Fargateに移行したことで様々なメリットがありました。
しかし、ECS + Fargateに移行したことでデメリットもあります。 EC2でRailsを動かしていた時代では、作業用のEC2にsshで接続し、そこでrails consoleを動かして作業、ということが簡単にできていました。 ECS + Fargateに移行してからはこの方法が使えなくなってしまったため、データ修復やジョブリトライの作業を行うのが難しくなりました。 DBに直接接続したり、ECSのRun taskを使ったり、SREチームに特別な環境を用意してもらったりとだましだましやってきたのですが、この部分はEC2自体の方がやりやすかったと思っています。
そのような中、先日 Amazon ECS Exec が発表されました。 詳細は割愛しますが、ECS Execを使うことで 稼働中のDockerコンテナに接続してコマンドを実行する ことができるようになります。 (詳細は クラスメソッドさんの解説記事 が詳しいです)
この機能のリリースを聞いたとき「EC2時代と同じ感覚でrails consoleが使えるのではないか?」という期待が頭に浮かびました。 今回はこれができるかどうか確認するため、ECS Execを有効にした作業用のDockerコンテナを立ち上げて接続し、rails consoleを動かしてみようと思います。
前提として、すでにRailsアプリケーションがECS + Fargateの環境で動かしていることを想定しています。
準備
ECS Execを使うためには、ローカルPCに最新のAWS CLIをインストールする必要があります。 インストールしていない方は 公式ガイド に従い、インストールします。 すでにインストール済みの方も、バージョンが古いと使用できないので最新バージョンにアップデートします。
$ aws --version aws-cli/2.1.31 Python/3.9.2 Darwin/19.6.0 source/x86_64 prompt/off
またAWS CLIのオプションのSession Manager pluginもインストールする必要があります。 インストールしていない方は、公式ガイドに従いインストールしてください。
作業
それではECS Execを使うための設定を行っていきます。
IAM
ECS Execを使うためには、「ECSタスクロール(≠ECSタスク実行ロール)」に ssmmessages 関連のpolicyを追加する必要があります。 下記のpolicyを追加します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel" ], "Resource": "*" } ] }
ECSタスク定義
既存のECSタスク定義をそのまま使っても問題ないのですが、今回は実行するコマンドをtail -f /dev/null
に変えた新しいタスク定義を作りました。
無駄なリソースを使わないようにするためです。
ECSサービス
今回は、ECS Exec専用のECSサービスを作成します。 現在稼働中のコンテナでもECS Execで接続してrails consoleできるはずですが、既存サービスに影響が出てしまう可能性があるため作業専用のコンテナで実行します。
作成はAWS管理コンソールから行いました。 基本的にはすでに動いているECSサービスと同じ設定ですが、以下の設定は既存サービスから変更しています。
- (重要) Fargateのplatform versionが
1.4.0
になっていることを確認。ECS Execは1.4.0でないと利用できないため。 - 新しく作成したタスク定義を使用する。
- Load balancerは「None」を選択。
そしてサービス作成後、追加で行和なければいけない作業があります。
ECS Execを利用するためには、ECSサービスの enableExecuteCommand
を有効にする必要があります。
この設定は、現在(2021年04月01日)、AWS管理コンソール上から設定はできないようです。
AWS CLIからは設定できるため、こちらを使って設定を有効化します。
# enableExecuteCommandはfalse $ aws ecs describe-services \ --cluster $your_ecs_cluster_name \ --services $your_ecs_service_name | jq . { "services": [ { "enableExecuteCommand": false } ] } $ aws ecs update-service \ --cluster $your_ecs_cluster_name \ --service $your_ecs_service_name \ --enable-execute-command # enableExecuteCommandがtrueになった! $ aws ecs describe-services \ --cluster $your_ecs_cluster_name \ --services $your_ecs_service_name | jq . { "services": [ { "enableExecuteCommand": true } ] }
上記の設定後、動いているタスクをstopして、新しいタスクを起動させます。
するとECSタスクの enableExecuteCommand
がtrueになり、 ExecuteCommandAgent
がRUNNINGになります。
$ aws ecs describe-tasks \ --cluster $your_ecs_cluster_name \ --tasks $your_ecs_task_id | jq . { "tasks": [ { "containers": [ { "managedAgents": [ { "lastStartedAt": "2021-04-01T18:28:57.249000+09:00", "name": "ExecuteCommandAgent", "lastStatus": "RUNNING" } ] } ], "enableExecuteCommand": true } ] }
ここまでで準備完了です!タスクIDをメモします。 次にECS Execを使ってコンテナに接続し、rails consoleを動かしてみます。
利用可能チェック
弊社Webエンジニアの杉本より、ECS Exceが使える環境になっているか確認できるツールがあると情報をもらいました。
GitHub - aws-containers/amazon-ecs-exec-checker: 🚀 Pre-flight checks for ECS Exec
上記の設定をしてもつながらなかった場合、このツールを使って何が足りていないかチェックするのが良いと思います。
実行
それではAWS CLIを使って /bin/sh
コマンドを実行することで、稼働中のコンテナに接続してみます。
$ aws ecs execute-command \ --cluster $your_ecs_cluster_name \ --task $your_ecs_task_id \ --container $your_ecs_task_def_container_name \ --command "/bin/sh" --interactive The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-xxxxxxxxxxxxxxx /app $ /app $ echo "Hello world!" Hello world! /app $ date Thu Apr 1 18:49:09 JST 2021
無事接続して、コマンドを実行できるようになったようです。 次にここでrails consoleが動かせるか確認してみます。
/app $ bin/rails c Loading staging environment (Rails 6.1.3) irb(main):001:0> Rails.env => "staging" # 本番は怖いので、stagingで試してます... irb(main):002:0> Time.current => Thu, 01 Apr 2021 18:49:34.230074083 JST +09:00 irb(main):003:0> user = User.find(1000) => ... irb(main):004:0> user.id => 1000 irb(main):005:0> quit /app $ exit Exiting session with sessionId: ecs-execute-command-xxxxxxxxxxxxxxx.
画像で見るとこんな感じです。
すごい!無事rails consoleを立ち上げることができ、アプリケーションのコードを使ってRubyのコードを実行できることも確認できました。 この環境が利用できれば、過去EC2でrails consoleを使って実施していた作業と同等のことができそうです。
ログ出力
コンソールへの入出力をcloudwatch logsに出力できたので、その方法も合わせて解説します。
まず稼働中のdocker imageで下記コマンドが利用可能である必要があります。
script
(util-linux
の一部)cat
(coreutils
の一部)
もし利用できない場合、docker build時に必要なpackageをインストールして、利用できるようにしてください。
次に、ECSタスクロールにcloudwatch logs関連の権限を追加したpolicyをアタッチします。
{ "Version":"2012-10-17", "Statement":[ { "Effect":"Allow", "Action":[ "ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:DescribeLogGroups" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:DescribeLogStreams", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:<AWS_REGION>:<ACCOUNT_ID>:log-group:<LOG_GROUP_NAME>:*" } ] }
上記の設定を行ったのち、ECS Execで接続、コマンド実行、接続終了してみてください。
すると、cloudwatch logsの該当のlog groupに ecs-execute
で始まるログが出力されています。
中身を見るとECS Execで実行中の入出力が保存されていることが確認できると思います。 色変更のためのエスケープシーケンスがあり少し見づらいのですが、実行記録として最低限の役割は果たせているかと思います。
注意点
こちらの方法ですが、下記の注意点があります。
- 稼働中のECSサービスに接続して作業を行った場合、予期せぬ影響が発生する可能性がある。
- ディスクサイズが20GBしかなく、それ以上のファイルを保存することができない。
- ECS Exec終了時に、コンテナ内で出力したファイルが削除されてしまう。
- ECS Exec中にネットワーク接続が切れると再接続できない。
1点目に関して、稼働中のサービスで rails console
を実行するのは危険なので、作業専用のECSサービスを作るのが良いと思います。
2点目、3点目は注意制限になります。
そして4点目、こちらは注意です。 再接続できないため、予定していた作業を最後まで実施できなかったり、作業がどこまで進んだか確認できなくなります。 Session Managerにセッション自体は残っており、こちらを手動で終了させることで強制中断は可能です。(このタイミングでcloudwatch logsにログが出力される) 長時間動かす作業を実行した場合、この問題が発生しやすいと思われるので実行時には注意が必要だと思われます。
この問題の1つの解決策として、EC2を作業用サーバーとして利用する方法も紹介しておりますので、よかったらご覧ください。
まとめ
今回は新しく発表されたAmazon ECS Execを使って、稼働中のコンテナでrails consoleを動かしてみました。 いくつかの設定を加えるだけで簡単にrails consoleを使えることがわかりました。 今後は本番環境でも利用できるようにしていきたいです。
今後の課題としては、まずコンソールの入出力のロギングです。 公式のガイドを見ると、ECS Execで入力したコマンドや出力結果をS3やCloudwatchにログとして保存する例が紹介されています。 この設定を取り入れることができると、作業の履歴が簡単に残せるので是非実施したいです。 (2021-04-19追記、cloudwatch logsへ出力する方法を追記しました)
また、弊社では最近Terraformを使ってインフラをコードで管理しています。 今回、手動でインフラの設定を変更しましたが、この変更をTerraformでもできるようにしたいです。 (2021-04-01現在ですが、 terraformからも設定できる ようになっているみたいです。対応が早い!)
以上になります。 最後までご覧いただきありがとうございました。
ユニファでは一緒に働く仲間を募集しています!よかったらこちらのページもご覧ください。 unifa-e.com