cronはLinux標準搭載のスケジューラーデーモンです。
ラズパイでcronがしたいと思い立ったが吉日、早速cronの初回設定と、ちょっとばかしの応用テクをご紹介します。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

ラズパイでの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 が生成されるようになります。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

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



まずcronの大元の設定ファイルは
/etc/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 )

        

何やらワサワサと出ておりますが、この内容の下の方の
Job defination がcronにスケジュール登録してあるコマンドのサンプルが4つほど並んでいます。 このcronジョブの作り方は後ほど説明します。

/etc/crontab はcronの設定ファイルの元締めですので、このファイルを直接編集して自分専用のスケジュールを登録しても結果的に動くのですが推奨はされません。 そこでcronの設定自体を操作する方法としては、 crontab コマンドを利用します。

            コマンド: crontab
オプション:
    -u <ユーザ名>:
        設定するcrontabのユーザ名を指定する
        (省略すると現在のデフォルトユーザー名, rootなど)
    -e:
        cron設定ファイルの編集
    -l:
        登録済みのスケジュール一覧を表示
    -r:
        登録されているスケジュールの削除

        

詳しくは
man crontab でご確認ください。

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



まず自分専用のパソコンなどでcronを一人占めして使うなら、
/etc/crontab を直接編集してもなんら差し支えない利用方法だとおもいます。 また /etc/cron.d/ にcrontabスクリプトを置くことでも、定期実行させることができます。
ただし、複数のユーザーが利用する環境でcronを使いたい場合、後々ユーザー管理を考慮すると
/etc/crontab は直接編集はあまり得策ではありません。 そのような場合には crontab -e が推奨されます。

crontab -e を使うと、crontabの設定ファイルが保存される場所が異なり /var/spool/cron/<ユーザー名> に個別に設定が保存されようになり、ユーザーごとの管理はcron側にお任せになります。
なおこの記事では
crontab -e を使うように統一しております。 ご了承ください。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

cronの動作テスト



毎分'Hello cron'をファイルに書き込み

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

$ crontab -l
#...中略
* * * * * echo 'Hello cron' >> /tmp/cron.test.txt

        

この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

        

でスケジュールが削除されました。 なお、
crontab -r では登録していたスケジュールは全て削除されますので、個別に残したいスケジュールがある場合には crontab -e でこまかく編集する必要があります。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

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 でスケジュールを消去しましょう。)


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

実用編〜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

        

として一分間隔で出力が記録されています。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

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



Raspberry Pi OSをパッケージアップデートとかを気が付いた時にやってみた後などはなんだかcronが動かなったりします。
原因が定かではないけれど、普段は動いていたのに唐突に機能が停止してしまったような場合でまずはサービスデーモンの再起動をかけて様子をみましょう。

            $ sudo /etc/init.d/cron restart

        

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


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

まとめ



今回はラズパイ&Dockerの環境でもcronが使えることを確認しました。
例に取り上げたプログラム程度ならわざわざDockerを呼び出すことはないでしょうけれど、使いどころによっては面白い応用ができそうではあります。