[ラズパイ] cronをつかってDockerコンテナのシェルスクリプトを定期実行する


2020/09/03
2021/02/17

cronはLinux標準搭載のスケジューラーデーモンです。

ラズパイでcronがしたいと思い立ったが吉日、早速cronの初回設定と、ちょっとばかしの応用テクをご紹介します。


ラズパイでのcron動作環境の確認

まずはRapberry Pi OS Linuxの環境下でcron環境を有効化するための手順を確認していきましょう。

cronのサービス起動(初回)

cronは何もしないと停止したままなので、ラズパイ起動時に常駐するように設定を起こす必要があります。

まずシスログデーモンにcronを登録します。

以下、
/etc/rsyslog.confの内容は、

            
            $ cat /etc/rsyslog.conf
#...中略
#cron.*             /var/log/cron.log
#...以下略
        
と、cronの行がコメントアウトされているので、コメントアウトを削除します。

            
            $ cat /etc/rsyslog.conf
#...中略
cron.*             /var/log/cron.log
#...以下略
        
変更を反映するため、シスログデーモンを再起動します。

            
            $ sudo /etc/init.d/rsyslog restart
        
次にcronのプログラム設定ファイル/etc/default/cronで、ログレベルを設定する項目がありますが、とにかく全部をログとして出す相当の-L 15を設定します。

            
            $ cat /etc/default/cron
#Cron configuration options
#...中略
EXTRA_OPTS="-L 15"
        
設定変更後はデーモンの再起動します

            
            $ sudo /etc/init.d/cron restart
        
これで/var/log/以下にcron.logが生成されるようになります。

cron設定ファイル(crontab)の確認

            
            $ cat /etc/crontab
#/etc/crontab: system-wide crontab
#Unlike any other crontab you don't have to run the `crontab'
#command to install the new version when you edit this file
#and files in /etc/cron.d. These files also have username fields,
#that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

#Example of job definition:
#.---------------- minute (0 - 59)
#|  .------------- hour (0 - 23)
#|  |  .---------- day of month (1 - 31)
#|  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
#|  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
#|  |  |  |  |
#*  *  *  *  * user-name command to be executed
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
        
            
            コマンド: crontab
オプション:
    -u <ユーザ名>:
        設定するcrontabのユーザ名を指定する
        (省略すると現在のデフォルトユーザー名, rootなど)
    -e:
        cron設定ファイルの編集
    -l:
        登録済みのスケジュール一覧を表示
    -r:
        登録されているスケジュールの削除
        

余談 ~ "/etc/crontab"の直接編集 vs. "crontab -e"

cronの動作テスト

            
            $ crontab -e
#エディタが開くので以下のコマンドを挿入...
* * * * * echo 'Hello cron' >> /tmp/cron.test.txt
#編集後に保存...

$ crontab -l
#...中略
* * * * * echo 'Hello cron' >> /tmp/cron.test.txt
        
            
            $ cat /tmp/cron.test.txt
Hello cron
Hello cron
Hello cron
Hello cron
        
            
            $ crontab -r
$ crontab -l
no crontab for new-user
        

cronからdockerコンテナ内の対象プログラムをスケジュール実行する

ここではラズパイへのDockerのインストール手順は取り上げませんが、そちらを知りたい方は別のブログで以前記載した内容をご参照ください。

練習編 〜 hello worldイメージを使ってみる

通常ホスト側に影響を与えるようなDockerコンテナ内部のプログラムを実行する場合には、Docker側の設定を実行するプログラムに見合った権限で正しく設定する必要があり、特にラズパイ側のデバイスを利用する場合には、特権コンテナで立ち上げることもしばしばです。

Dockerコンテナ内のcronも例外ではなく、コンテナを立ち上げるときに色々と込み入った権限を与えないといけなくなります。考えるのも面倒で
--privileged指定すると、コンテナのプロセスが暴走した場合、CPU使用率がパンパンになり動作不安になる問題もあり、あまり積極的に使いたくない事情もあります。

ということで、ホスト側のOSがLinuxなら、素直にホスト側のcronからDockerコンテナ内のプログラムを実行するようにスケジュールするほうがベターです。

即席利用できる最も軽量な
hello-worldイメージをDocker hubからプルしてみます。

            
            $ docker pull hello-world
$ docker images
REPOSITORY      TAG      SIZE
hello-world     latest   4.85kB
        
毎回cronを実行するたびに、コンテナを生成・起動する(run相当)のも面倒ですので、あらかじめ名前付きのコンテナを生成しておきます。

            
            $ docker create --name='my-hello-world' hello-world
bdfde8721d6bbfe95c200b779ec3d7653e678c63bb03995480e5065893bf44f3
$ docker ps -a
CONTAINER ID    IMAGE        COMMAND    STATUS     NAMES
bdfde8721d6b    hello-world  "/hello"   Created    my-hello-world
        
これでmy-hello-worldがコンテナとして使えるようにスタンバイ状態になりました。

ラズパイ起動時に生成したコンテナも起動させたい場合には、生成オプションに
--restart=alwaysをしておくべきですが、今回はテストですので動作確認したらすぐイメージごと消すことを想定してますので、restart設定はデフォルトのnoで利用します。

実際にコンテナをアタッチモードで起動してみます。

            
            $ docker start -a my-hello-world
Hello from Docker!
#...以下略
        
これでコンテナが起動状態になり、内部でhelloというプログラムが起動しました。

なお停止させる場合には
docker stop <コンテナ名>です。

では、これをラズパイ側のcronに登録して、毎分出力のログを取るようにスケジュールを登録してみます。

            
            $ crontab -e
#1分毎にdocker startを使用してコンテナ内のプログラムを実行
* * * * * docker start -a my-hello-world >> /tmp/cron.hello-docker.log
        
この設定を保存して、しばらく待ち、ログが書き込まれているかを確認します。

            
            $ cat /tmp/cron.hello-docker.log
Hello from Docker!
#...以下出力が繰り返しログされる
        
Dockerコンテナ内で実行されたプログラムの結果はホスト側のログに記録されます。(終わったらcrontab -rでスケジュールを消去しましょう。)

実用編〜alpineイメージでの利用

とりあえずデモに使用したhello-worldイメージですが、ひたすらhelloプログラムを実行するだけですので実用性がゼロです。

せめて最小限のシェルコマンドが使いたいので、
alpineイメージを利用してdocker execからプログラムを実行してみたいと思います。

上の手順と同様に、まずイメージをプルして、名前付きでコンテナを生成します。

            
            $ docker pull alpine
$ docker images
REPOSITORY    TAG       SIZE
alpine        latest    3.74MB
        
今回のコンテナはインタラクティブモードに指定することでコンテナプロセスの標準入力を開き、擬似tty付きで生成します。そうでないとdocker exec実行前にコンテナが破棄されて、正常に動かないようになってしまいます。

            
            $ docker create -it --init --name my-alpine alpine
$ docker start my-alpine
$ docker ps
CONTAINER ID    IMAGE    COMMAND    STATUS    NAMES
b5f8b73892a6    alpine   "/bin/sh"  Up        my-alpine
        
ちなみですが、docker create + docker startのパターンをdocker runで行いたい場合には、バックグラウンド(デタッチ)モードを付けるだけで上のコマンドと等価になります。

            
            $ docker run -d -it --init --name my-alpine alpine
$ docker ps
CONTAINER ID    IMAGE    COMMAND    STATUS    NAMES
b5f8b73892a6    alpine   "/bin/sh"  Up        my-alpine
        
めでたくmy-alpineコンテナがバックグラウンドで走っている状態になりましたので、docker execを呼び出して、コンテナ内のコマンドを利用することが出来ます。

            
            $ docker exec my-alpine echo 'Hello alpine!'
Hello alpine!
$ docker exec my-alpine date
Thu Sep  3 08:22:44 UTC 2020
        
動作も早くていい感じです。

これをラズパイ側のcronに登録して、毎分出力のログを取るようにスケジュールを登録してみます。

            
            $ crontab -e
#1分毎にdocker execを使用してコンテナ内のプログラムを実行
* * * * * docker exec my-alpine date >> /tmp/cron.alpine-docker.log
        
しばらくしてからログを除くと、

            
            $ cat /tmp/cron.alpine-docker.log
Thu Sep  3 08:28:02 UTC 2020
Thu Sep  3 08:29:01 UTC 2020
Thu Sep  3 08:30:02 UTC 2020
Thu Sep  3 08:31:01 UTC 2020
Thu Sep  3 08:32:02 UTC 2020
Thu Sep  3 08:33:01 UTC 2020
        
として一分間隔で出力が記録されています。


Hotfix ~ ある日突然動かなくなるときに

Raspberry Pi OSをパッケージアップデートとかを気が付いた時にやってみた後などはなんだかcronが動かなったりします。

原因が定かではないけれど、普段は動いていたのに唐突に機能が停止してしまったような場合でまずはサービスデーモンの再起動をかけて様子をみましょう。

            
            $ sudo /etc/init.d/cron restart
        
これで駄目なら、仕方が無いのでラズパイ本体の再起動をかけます。

それでも駄目ならスケジュール登録しているコマンドのどこかにアップデートなどの影響で誤作動を引き起こしている箇所がある可能性が高いですので、
crontab -lで怪しいプログラムを虱潰しに点検していく作業をするしかなさそうです。


まとめ

今回はラズパイ&Dockerの環境でもcronが使えることを確認しました。

例に取り上げたプログラム程度ならわざわざDockerを呼び出すことはないでしょうけれど、使いどころによっては面白い応用ができそうではあります。