0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ECS Execで`bin/rails runner`の出力が`EOF`で途切れたので、バックグラウンド実行とファイル出力で回避した話

0
Posted at

はじめに

本番やSTGの調査をするとき、ECS Execでコンテナに入ってbin/rails runnerを叩く場面は多いと思います。私も最近、ECS上で動いているRailsアプリのデータ調査を、AIエージェントにローカルから自動で進めてもらう形で行っていました。

その途中で、aws ecs execute-command経由で実行したbin/rails runnerの出力が、途中でCannot perform start session: EOFというエラーとともに切れてしまう現象にハマりました。軽いechoは普通に返ってくるのに、調査用のスクリプトだけ結果が得られないという状況です。

この記事では、そのときに試して安定した回避策をまとめます。同じような状況の方の参考になれば嬉しいです。

何が起きたか

次のように、ECS Execでbin/rails runnerを直接フォアグラウンドで実行しました。

aws ecs execute-command --interactive \
  --cluster <cluster> --task "$TASK_ARN" --container rails \
  --command "bin/rails runner '...'"

すると、セッションが途中でCannot perform start session: EOFを出して切断され、期待した標準出力が得られないことがありました。単純なechoなどの軽いコマンドは問題なく返ってきます。そのため、コマンドの実行時間や出力量に依存している模様です。

ちなみに、このエラー自体は「ターミナルではない場所からECS Execを実行したとき」にも出るという報告が見つかりました(expectのunbufferでラップするとよい、という話です)。私の場合は事情が少し違い、--interactiveで実行している点や、実行時間・出力量に左右される点から、別の要因も絡んでいそうでした。原因の完全な特定はできていないので、あくまで「こうすると安定した」という実践的な回避策としてお読みください。

回避策:バックグラウンド実行、ファイル出力、結果の読み出し

安定したのは、1回のexecute-commandの中で「バックグラウンドで処理を開始し、少し待ってから結果ファイルを読む」ところまで完結させる方法でした。

1. 診断スクリプトをコンテナ内に書き出す

まず、heredocでスクリプトをコンテナ内に書き出します。引用符のエスケープが複雑になりがちなので、この方が楽でした。

TASK_ARN=$(aws ecs list-tasks --cluster <cluster> --service-name <service> --profile <profile> | jq -r '.taskArns[0]')

aws ecs execute-command --profile <profile> --region ap-northeast-1 \
  --cluster <cluster> --task "$TASK_ARN" --container rails --interactive \
  --command "/bin/sh -c 'cat > /tmp/check.rb << \"EOF\"
# ここにRubyスクリプト本体
File.write(\"/tmp/result.log\", some_result.to_json)
EOF
echo WROTE_SCRIPT'"

スクリプトの結果は標準出力ではなく、File.writeでファイルに書き出すのがポイントです。

2. バックグラウンドで実行して、結果ファイルを読む

次に、setsid nohupでバックグラウンド実行し、数秒待ってから結果ファイルをcatします。

aws ecs execute-command --profile <profile> --region ap-northeast-1 \
  --cluster <cluster> --task "$TASK_ARN" --container rails --interactive \
  --command "/bin/sh -c 'setsid nohup bin/rails runner /tmp/check.rb > /tmp/stdout.log 2>&1 & sleep 15; cat /tmp/result.log; echo ---stdout---; cat /tmp/stdout.log'"

この方法で、期待した結果を取得できました。

ハマりどころと注意点

disownは付けない

最初はバックグラウンド化の定番としてdisownを付けていましたが、コンテナのシェル(busybox系の/bin/shなど)にはdisownが無いことがありました。setsidだけで十分に切り離せたので、disownは付けなくて大丈夫です。

sleepの秒数はRailsのboot時間も考慮する

sleepの秒数は処理量に応じて調整します。DB接続やSentryの初期化など、Railsのboot自体に数秒かかることも見込んでおく必要があります。

終了時のEOFは無害

セッション終了時に出るCannot perform start session: EOFは、この手順では無害でした。コマンド自体の出力は正常に得られているので、これが出ることを失敗の判定条件にしないようにしています。自動化する場合は特に注意したいところです。

後片付けを忘れない

調査用の一時ファイルは、最後に必ず消しておきます。

rm -f /tmp/check.rb /tmp/result.log /tmp/stdout.log

WORKDIRは決め打ちしない

コンテナのWORKDIRが/appとは限りません。私が扱ったアプリでは/llstockでした。DockerfileのWORKDIRを事前に確認しておくのが安全です。

代替策:base64でスクリプトを転送する

heredocの代わりに、ローカルで書いたRubyスクリプトをbase64エンコードして転送する方法もあります。複雑な引用符のネストを避けられます。

B64=$(base64 < local_check.rb | tr -d '\n')
aws ecs execute-command --profile <profile> --region ap-northeast-1 \
  --cluster <cluster> --task "$TASK_ARN" --container rails --interactive \
  --command "/bin/sh -c 'echo $B64 | base64 -d > /tmp/check.rb && cd /llstock && bin/rails runner /tmp/check.rb'"

別の調査では、この方式でフォアグラウンド実行しても、約622KB相当の出力でEOFによる切断は起きず、そのまま結果を取得できました。EOFの発生条件は実行時間や出力量に依存する「模様」としか言えないため、次の順で試すのが良さそうです。

  1. まずbase64転送とフォアグラウンド実行で試す
  2. EOFに遭遇したら、バックグラウンド実行とファイル出力の方式に切り替える

まとめ

ECS Exec経由のbin/rails runnerで出力が途切れたときは、「バックグラウンドで実行し、結果をファイルに書き出して、待ってから読む」という流れにすると安定しました。複雑な引用符はheredocかbase64で回避できます。

原因の特定までは至っていませんが、調査を自動化していると、こうした「出力が取れない」問題は地味に効いてきます。まずは軽い方式から試し、EOFが出たら切り替えるくらいの気持ちで使うのが良さそうです。同じような状況の方の参考になれば嬉しいです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?