はじめに
Docker Compose を使って構築している環境にスケジューリングされたジョブ実行の仕組みがほしいという相談をいただきました。
AWS や GCP といったクラウドサービスを利用している場合は CloudWatch Events や Google Scheduler の利用も選択肢に入ります。 しかし今回の環境は Linux サーバに Docker デーモンが用意されているだけでしたので、cron を利用することにしました。
このときに構築した Docker 内で cron を実行する方法と、cron 実行結果を標準出力、標準エラー出力に流して docker logs コマンドで確認する方法を紹介します。
検証環境
| |
今回利用した Docker ベースイメージ
今回の検証で利用した Docker のベースイメージは Alpine です。
以下のタグの Alpine イメージを利用しました。
alpine:3.16.2
準備
まずは cron の本体であるcrondが実行可能な Docker イメージを用意します。
Alpine にはすでにcrondがインストールされているされているため、インストール作業は不要です。
crond を実行するコンテナを起動
crond を起動するコンテナを起動してみます。
起動時には-d オプションを付与してバックグラウンド ( デーモン ) 起動します。
| |
起動できたようなので、コンテナ ID を指定してコンテナ実行時に出力される標準出力、標準エラー出力を監視します。
| |
こちらのコマンドを実行したターミナルウィンドウは閉じずにそのままおいておきます。 後ほど動作確認時に使用します。
crontab にジョブを登録する
次に crontab に実行ジョブを登録します。
ここでは簡単なジョブを 2 つ登録します。
- 1 つは正常終了するジョブ。
- 1 つは異常終了するジョブ。
そのために、まずは crond 実行中のコンテナにアタッチします。
| |
次に crontab -e コマンドで crontab をエディタで開きます。
( ちなみに設定ファイルの実体は /var/spool/cron/crontabs/root です。 )
| |
ファイルには以下の 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 分ごとにスケジューリングしたジョブの実行ログが出力されているはずです。
| |
date コマンドが実行されたことにより、日時が出力されていることがわかります。
また、 hoge という存在しないコマンドが実行され、 /bin/ash: hoge: not found というエラーも出力されていることがわかります。
もし 1>>/proc/1/fd/1 2>>/proc/1/fd/2 という記述がなかったらどうなるの?
crontab に記述されたコマンドが実行されると、標準出力、標準エラー出力の内容はホスト内の sendmail コマンドを使ってメール送信されます。
メール送信されることを確認するために、 crontab を以下のように書き換えます。
| |
docker logs -f 実行中のターミナルウィンドウを確認すると、 cronjob 実行結果ログが以下のように変化していることがわかります。
| |
標準出力、標準エラー出力が cronjob から出力された場合、メール送信をしようとしていることがわかります ( ただし、失敗していますね ) 。
正しくメール送信されるような環境が整っていればよいです。 しかし、そうでなければ cronjob が正常終了したのか異常終了したのかわかりません。
Docker の標準出力、標準エラー出力に実行ジョブのログが出力されることでこの問題を解決できます。
ひとこと
はじめに触れたとおり、 Web API として実行基盤を用意して、CloudWatch Events や Google Scheduler で起動する、という仕組みを作ることばかりでした。 そのため、今まで Docker コンテナ内で cronjob を実行したことはほとんどありませんでした。
こういう方法もあるんだな、と勉強になりました。