Dockerコンテナ内でcronを実行し実行ログを出力する方法
はじめに
Docker Compose を使って構築している環境にスケジューリングされたジョブ実行の仕組みがほしいという相談をいただきました。
AWS や GCP といったクラウドサービスを利用している場合は CloudWatch Events や Google Scheduler の利用も選択肢に入ります。
しかし今回の環境は Linux サーバに Docker デーモンが用意されているだけでしたので、cron を利用することにしました。
このときに構築した Docker 内で cron を実行する方法と、cron 実行結果を標準出力、標準エラー出力に流して docker logs
コマンドで確認する方法を紹介します。
検証環境
$ uname -moi
arm64 unknown Darwin
$ bash -version | head -n 1
GNU bash, バージョン 5.2.2(1)-release (aarch64-apple-darwin21.6.0)
$ docker --version
Docker version 20.10.17, build 100c701
今回利用した Docker ベースイメージ
今回の検証で利用した Docker のベースイメージは Alpine です。
以下のタグの Alpine イメージを利用しました。
alpine:3.16.2
準備
まずは cron の本体であるcrond
が実行可能な Docker イメージを用意します。
Alpine にはすでにcrond
がインストールされているされているため、インストール作業は不要です。
crond
を実行するコンテナを起動
crond
を起動するコンテナを起動してみます。
起動時には-d
オプションを付与してバックグラウンド ( デーモン ) 起動します。
# 正常に起動できれば、コンテナIDが出力されます。
$ docker run -d alpine:3.16 crond -f
0d16132769db5000466887906837e03ab1ca3bc02a38b307da75424528cc0a76
起動できたようなので、コンテナ ID を指定してコンテナ実行時に出力される標準出力、標準エラー出力を監視します。
# 先程のコンテナIDの先頭6文字だけ(省略形)を指定
$ docker logs -f 0d1613
こちらのコマンドを実行したターミナルウィンドウは閉じずにそのままおいておきます。
後ほど動作確認時に使用します。
crontab
にジョブを登録する
次に crontab
に実行ジョブを登録します。
ここでは簡単なジョブを 2 つ登録します。
- 1 つは正常終了するジョブ。
- 1 つは異常終了するジョブ。
そのために、まずは crond
実行中のコンテナにアタッチします。
# コンテナ内に接続
$ docker exec -it 0d1613 sh
/ #
次に crontab -e
コマンドで crontab をエディタで開きます。
( ちなみに設定ファイルの実体は /var/spool/cron/crontabs/root
です。 )
# crontab -e コマンドを実行すると、crontab設定ファイルをエディタで開きます
/ # crontab -e
ファイルには以下の 2 行を追記します。
*/1 * * * * date 1>>/proc/1/fd/1 2>>/proc/1/fd/2
*/1 * * * * hoge 1>>/proc/1/fd/1 2>>/proc/1/fd/2
1 行目は date
コマンドを実行しています。
2 行目は hoge
という、存在しないコマンドを実行しています。
結果、1 行目の実行結果は正常終了し、2 行目の実行結果は異常終了します。
標準出力は、 プロセス ID = 1 つまり crond
の標準出力と同じファイル記述子に出力します。
標準エラー出力は、やはり プロセス ID = 1 つまり crond
の標準エラー出力と同じファイル記述子に出力します。
動作確認のため、いずれのジョブも 1 分ごとに起動します。
ちなみに、 1>>/proc/1/fd/1 2>>/proc/1/fd/2
という記述の意味については後述します 。
それでは動作確認に進みます。
動作確認
はじめにdocker logs -f
を実行したウィンドウを確認してみましょう。
設定がうまく行っていれば、1 分ごとにスケジューリングしたジョブの実行ログが出力されているはずです。
$ docker logs -f 0d1613
j/bin/ash: hoge: not found
Tue Oct 11 10:52:00 UTC 2022
/bin/ash: hoge: not found
Tue Oct 11 10:53:00 UTC 2022
/bin/ash: hoge: not found
Tue Oct 11 10:54:00 UTC 2022
/bin/ash: hoge: not found
Tue Oct 11 10:55:00 UTC 2022
/bin/ash: hoge: not found
Tue Oct 11 10:56:00 UTC 2022
/bin/ash: hoge: not found
Tue Oct 11 10:57:00 UTC 2022
/bin/ash: hoge: not found
Tue Oct 11 10:58:00 UTC 2022
Tue Oct 11 10:59:00 UTC 2022
/bin/ash: hoge: not found
date
コマンドが実行されたことにより、日時が出力されていることがわかります。
また、 hoge
という存在しないコマンドが実行され、 /bin/ash: hoge: not found
というエラーも出力されていることがわかります。
もし 1>>/proc/1/fd/1 2>>/proc/1/fd/2
という記述がなかったらどうなるの?
crontab に記述されたコマンドが実行されると、標準出力、標準エラー出力の内容はホスト内の sendmail コマンドを使ってメール送信されます。
メール送信されることを確認するために、 crontab を以下のように書き換えます。
# crontabを開く
/ # crontab -e
MAILTO=""
*/1 * * * * date
*/1 * * * * hoge
docker logs -f
実行中のターミナルウィンドウを確認すると、 cronjob 実行結果ログが以下のように変化していることがわかります。
$ docker logs -f
...
sendmail: can't connect to remote host (127.0.0.1): Connection refused
sendmail: can't connect to remote host (127.0.0.1): Connection refused
sendmail: can't connect to remote host (127.0.0.1): Connection refused
sendmail: can't connect to remote host (127.0.0.1): Connection refused
標準出力、標準エラー出力が cronjob から出力された場合、メール送信をしようとしていることがわかります ( ただし、失敗していますね ) 。
正しくメール送信されるような環境が整っていればよいです。
しかし、そうでなければ cronjob が正常終了したのか異常終了したのかわかりません。
Docker の標準出力、標準エラー出力に実行ジョブのログが出力されることでこの問題を解決できます。
ひとこと
はじめに触れたとおり、 Web API として実行基盤を用意して、CloudWatch Events や Google Scheduler で起動する、という仕組みを作ることばかりでした。
そのため、今まで Docker コンテナ内で cronjob を実行したことはほとんどありませんでした。
こういう方法もあるんだな、と勉強になりました。
ディスカッション
コメント一覧
まだ、コメントがありません