プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

cronで定期実行を設定する

cronでコマンドを定期実行したい

ラズパイで運用している室内温湿度観測システムなのですが、何かしらのErrorが発生して停止しているのか、原因ははっきりしないのですがPythonスクリプトが停止していることがあります。
以前、「温度計のErrorでセンサーが認識しなくなる」という現象もあって、このときはいったんラズパイの電源を完全にoffしないと回復しなかったのですが、最近週1ぐらいの頻度でPythonが停止していて、しかし再度Pythonスクリプトを起動するとなんの問題もなく起動する、という別現象が発生しています。

原因はわからないのですがラズパイを再起動すれば回復するので
「温湿度の最終データを別のPythonスクリプトで確認し、停止していたらラズパイを再起動する」というのを作ってみました。
ここで、定期実行のためにcronを設定しましたが、なかなか苦労したのでここにまとめておこうと思います。

cronの設定

cronでは決まった日時での繰り返し、または決まった日時間隔の繰り返しが設定できます。

$sudo crontab -e

を実行しcronの設定ファイルを開きます。
初回は編集に使うeditorを聞かれるのでvimなどを選択します。

ここでは1日に一回で十分と考え、以下のように毎日18時31分に実行するようにします。
これをファイルに追記します。

31 18 * * * sudo /usr/bin/python3 /home/raspberrypi3p/cron.py

日時間隔の繰り返しの場合は以下のように*/5と記載し、この場合5分ごとになります。

*/5 * * * * /usr/bin/python3 /home/raspberrypi3p/cron.py

*になっている部分は順番に 「分 時 日 月 曜日」で*ワイルドカードです。

また、起動時に一回だけ実行したい場合は@rebootを使います。起動からwaitを入れたい場合はsleep 15;のようにします(ここでは15秒後)。
起動時には各種プロセスが順番に立ち上がるのですが、デーモンの立ち上がりを待ってからでないと上手く実行されない場合があるようでwaitを入れたほうがいい場合があります。

@reboot /usr/bin/python3 /home/raspberrypi3p/cron.py
@reboot sleep 15; /usr/bin/python3 /home/raspberrypi3p/cron.py

Pythonの場合は上記のようになりますが、ここで Python/usr/bin/python3のように絶対パスで記載する必要があります。
Python絶対パス

$ which python3

で取得できます。
また、Pythonスクリプト/home/user名/cron.pyのように絶対パスで指定するようにします。

shellスクリプトの場合は以下のようになります。

*/5 * * * * sh /home/raspberrypi3p/cron.sh

上記sudo crontab -eコマンドまたは以下のコマンドでこのファイルの内容が確認できます。

$ sudo crontab -l   //確認のみ

cronを起動します。

$ sudo systemctl start cron
or
$ sudo systemctl enable cron

以下コマンドで起動できているかどうか状態確認できます。
activeまたはinactiveと表示されます。
また、cronの動作Logも表示されます。

$ sudo systemctl status cron
→ Active: active (running)
→ Active: inactive (dead)

停止させたいときは以下のようにします。

$ sudo systemctl stop cron

トラブルシューティング

これで意図通りに定期実行されていることが確認されればOKですが、上手くいかないことがあります。
まずは、cronのLogを有効化します。

rsyslog.conf 中の cron ログは # とコメントアウトされているため、コメントアウトを外します。

$ sudo vim /etc/rsyslog.conf
>> 
# cron.*                         /var/log/cron.log
↓
cron.*                         /var/log/cron.log

再起動して変更を反映させます。

$ sudo systemctl restart rsyslog

以下コマンドでcronのLogを確認します。

$ sudo vim /var/log/cron.log
>> Jan 16 18:25:03 raspberrypi3p CRON[17105]: (CRON) error (grandchild #17106 failed with exit status 1)

このようなerrorが出力されていれば何らかのErrorがでてうまく動いていないということになります。

crontab -eの中身を以下のように/tmp/test.logに出力するようにします。
これでPythonprint()やErrorの内容がテキストファイルに出力されるようになります(標準出力と標準エラーを出力)。
https://yongjinkim.com/linux%E3%81%A7%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E5%AE%9F%E8%A1%8C%E3%83%AD%E3%82%B0%EF%BC%88cron%E3%82%82%EF%BC%89%E3%82%92%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AB%E5%87%BA%E5%8A%9B/
デバッグ用に実行頻度を多くしておきます(ここでは2分ごと)。

*/2 * * * * /usr/bin/python3 /home/raspberrypi3p/cron.py >> /tmp/test.log 2>&1

しかし、そもそもこのLogが出てこないようならcron自体の設定がおかしく、cronが動いていないということになります。

出てきた内容はここではPythonの例外でした。
serviceAccountKey.jsonというFirebaseにアクセスするためのキーが見つからない、というものでした。

Traceback (most recent call last):
  File "/home/raspberrypi3p/cron.py", line 48, in <module>
    get_last_data()
  File "/home/raspberrypi3p/cron.py", line 24, in get_last_data
    cred = credentials.Certificate('serviceAccountKey.json')
  File "/usr/local/lib/python3.7/dist-packages/firebase_admin/credentials.py", line 83, in __init__
    with open(cert) as json_file:
FileNotFoundError: [Errno 2] No such file or directory: 'serviceAccountKey.json'

しかし、cronを使わず直接

python3 cron.py

のように起動した場合は問題なく終了します。
さらに、デバッグ用に

inport os
print(os.getcwd())
>> /root

を加えると、実行されているフォルダが分かります。

一方、手動で起動すると

raspberrypi3p@raspberrypi3p:~ $ sudo python3 cron.py
>> /home/raspberrypi3p

となり、実行されているカレントフォルダが違うことが分かりました。
このため、/home/raspberrypi3pに設置されているserviceAccountKey.jsonが見つからないということが起きていることが分かりました。

まとめ

cronで自動実行の仕組みを構築しました。
cron自体の記事はたくさんあるのですが、躓きポイントが多いと思いました。
一度出来てしまえば簡単なのですが、最初の設定は結構苦労します。
上手く動かないときは
・crontab -e の記法が間違っていないか
Python等を実行している場合はLogを出力するようにしてprint()やErrorなどの中身を確認する
・実行権限sudo等の問題は無いか
・cronを使わないで実行したとき問題は無いか
等を確認することで解決できると思います。