ECSのcronジョブのログがCloudWatchに出ない問題 - /proc/1/fd/1リダイレクト
AWS ECSのコンテナ内で、cronを用いて定期実行しているプログラムがあるのですが、そのプログラムのログがCloudWatch Logsに出力されないことがありました。そのことについて調べたことをまとめます。
現象
runner "ExampleBatch.exec"
上記のように、バッチをcron(ruby gemwheneverを使用)で定期実行するよう、ECSで起動しているコンテナ内で設定していました。cronではないメインプロセスのログはCloudWatch Logsに出力されるものの、ExampleBatch内の処理で出力されるはずのログがCloudWatch Logsに出ないという状況でした。
対応方法
runner "ExampleBatch.exec" >> /proc/1/fd/1 2>&1
このように、cronに登録されたバッチ処理の標準出力・標準エラー出力をメインプロセス(/proc/1/fd/1)にリダイレクトすると解決しました。
原因
ECS上のプログラムのログがCloudWatch Logsに出力されるまでの流れは以下です。
ECSのログドライバ(awslogs)は、コンテナのメインプロセス(PID 1)の標準出力・標準エラー出力(stdout/stderr)をdocker logsに送ります。CloudWatch Logsはawslogsログドライバを介して、docker logsの内容を収集し、出力します。
これにより、以下の理由でcronジョブ内の出力はCloudWatch Logsに流れませんでした。
- cronジョブはメインプロセス(
PID 1)ではなく、別プロセスとして起動する。 - cronジョブの出力は、何もリダイレクトを指定しない場合、デフォルトでは
stdoutやstderrをターミナルには表示せずMAILTOに設定された宛先(または/var/spool/mail/{ユーザ名})にメールとして送信しようとする。-
docker logsに送られない
-
ですので、cronジョブのログ出力先をメインプロセスの標準出力にすることによって、CloudWatch Logsにログを出すことができました。
UNIXの出力周りについて整理
UNIX系OSにおける出力の仕様を整理します。
標準出力(stdout)という言葉が出てきました。
標準出力(stdout)とは、プログラムがデータを出力する際のデフォルトの出力先を指します。
通常、stdout の出力先は 端末(ターミナル) ですが、リダイレクト(>, >>)を使用することでファイルや別のプログラムに送ることもできます。
UNIX系OS(Linux, macOSなど)では、プロセスが扱うデータの流れ (ストリーム) は3つ あります。
| 名前 | ファイルディスクリプタ (FD) | 説明 | デフォルトの接続先 |
|---|---|---|---|
| 標準入力 (stdin) | 0 |
プログラムが受け取る入力 | キーボード |
| 標準出力 (stdout) | 1 |
プログラムの通常の出力 | ターミナル |
| 標準エラー出力 (stderr) | 2 |
エラーメッセージの出力 | ターミナル |
このストリームがどこに接続されるかによって、出力の行き先が変わります。
通常、シェル(Bashなど)でコマンドを実行すると、標準出力 (stdout) は自動的にターミナルに接続されるため、結果が見えます。
echo "Hello"
この場合、stdout の「接続先」はターミナルになります。
一方、
cron は対話的なシェルではなく、バックグラウンドで動くプロセスなので、デフォルトの標準出力 (stdout) の接続先がありません。
* * * * * echo "Hello"
cron で echo を実行しても、どこにも出力が接続されていないため、結果が見えません。
ですので、例えば以下のように出力をメインプロセスの標準出力に渡すことができます。
ファイルにリダイレクト
* * * * * echo "Hello" >> /proc/1/fd/1 2>&1
なお、2>&1は、標準エラー出力(stderr, fd 2)を標準出力(stdout, fd 1)にリダイレクトするという意味です。こうすると、標準エラー出力も標準出力に送られます。
-
&1の&は、「1をファイルとしてではなくFDとして扱う」という意味。 -
2>1だとstderrをファイル1に書き込もうとするので間違い。 -
2>&1で、「stderrをstdoutにリダイレクトする」という意味になる。
ということになります。
Discussion