Perl Hackers Hub

第25回cron周りのベストプラクティス(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはsongmuさんこと松木雅幸さんで、テーマはcronです。

なお本稿のサンプルコードは、本誌サポートサイトから入手できます。

cronとは?

cronは指定日時にジョブの自動実行を行うジョブスケジューラです。UNIX系のOSであれば実装の違いこそあれ、ほぼ標準でインストールされています。

作業自動化や、タスクを自動実行したいなどといった場合にcronは避けては通れません。Perlでバッチ処理を書く際などに多くの人が活用していると思いますが、ベストプラクティスがわからず恐る恐る使っている人も多いのではないでしょうか。

本稿では、cron活用におけるベストプラクティスについてお話します。

cronの使いどころ

cronの使い途は、主に次の3つが考えられます。

  • a.アプリケーションのジョブの実行
  • b.システムに関わるジョブの実行
  • c.監視用途

a.はアプリケーションで定期的に実行したいジョブの実行です。たとえばランキングの更新処理などの用途です。b.はログのローテートやバックアップなどの用途です。c.の用途は少し意外に感じられるかもしれませんが、毎分確実に実行されることを利用して、簡易な死活監視などに利用するといった用途があります。

cronの長所と短所

長所

長所としては何より枯れて安定していることが挙げられるでしょう。とにかくcrond(cron実行デーモン)はまず落ちることがなく、確実に指定したジョブを実行してくれるという安心感があります。当たり前に感じるかもしれませんが、これだけ安定したデーモンを書くのが難しいことは、常駐プログラムを少しでも書いたことがある人にはわかることでしょう。

また、何よりデファクトスタンダードであり、多くの人が慣れた形式でジョブ設定を書くことができることも長所です。

短所

短所としてはやはり、設定方法が馴染みづらく落とし穴も多いことです。本稿はその解消の一助となることを目的としています。

crontabの指定

cronの設定にはcrontabcron table形式と呼ばれるテキストファイルが用いられ、その編集にはcrontabコマンドが使われます。

crontabはユーザごとに設定されます。crontabコマンドで編集したcrontabは、Red Hat系Linuxの場合、/var/spool/cron/[username]に保存されます。ちなみに/etc/crontabというファイルもあるのですが、これはあまり直接編集しないほうがよいので、本稿では取り上げません。

crontabの一般的な指定方法

ひとまず気軽に設定する分には、crontab -eを打てば編集画面が立ち上がります。

5 15 14 3 * perl -E 'print "Hello"'

たとえば上記のように設定すると、3月14日15時5分にコマンドが実行されます。コマンドは任意のものを実行でき、パイプでつないでほかの処理に渡すこともできます。

コマンドの標準出力とエラー出力はまとめられ、ユーザのメールボックスに送られます。crontabの中で$MAILTO環境変数を指定することで送り先を変更できます。出力をメールで送りたくない場合はパイプやリダイレクトで出力先を変更することもできます。最終的な出力がなかった場合にはメールは送られません。

$MAILTOを指定する例
MAILTO="songmu@example.com"
5 15 14 3 - perl -E 'print "Hello"'
パイプ経由でsyslogに投げる例
5 15 14 3 * perl -E 'print "Hello"' | logger -t hello

ところで、crontab -eでのcrontabの直接編集は実は好ましくありません。crontab -rによるcrontab全消去のオペレーションミスがたびたび話題になります。実はcrontab <filename>とファイル指定することで、その内容をcrontabに反映できます。そして、このファイルをリポジトリ管理するのがベストプラクティスです。これについてはあとで取り上げます。

cron指定

crontabは1行1ジョブ指定になります。左からスペース区切りで、分、時、日、月、曜日、実行コマンドの指定となっています。先の例で説明すると次のようになります。

分 時 日 月 曜日 実行コマンド
5 15 14 3 * perl -E 'print "Hello"'

曜日は0~7の指定が可能になっており、0と7が日曜日です。上記で曜日に指定されている*は後述する全範囲指定です。

各フィールドには単なる数字だけでなく、次の記号を利用できます。これにより、バラエティに富んだ指定が可能になります。

  • -:範囲指定
  • *:全範囲指定
  • /:インターバル指定
  • ,:区切り指定

範囲指定

-は範囲指定を表します。たとえば分指定で1-10であれば、1~10分までの各分にジョブが実行されます。

全範囲指定

*はそのフィールドが取り得る全範囲を指定したものと同じです。たとえば、分指定に*が使われた場合は、0-59と同様の意味となります。

インターバル指定

「3分おき」といった指定は/を用いて行います。左に範囲、右にインターバルを指定します。たとえば分指定に*/3という指定をした場合は、0分、3分、6分……といった具合にジョブが実行されます。

全範囲指定と組み合わせて使われることが多いですが、12-27/5のような指定もできます。この場合は12分から27分の間に5分おきに実行されます。つまり、12分、17分、22分、27分に実行されます。ただ、この指定の方法が使われているのはあまり見かけたことはなく馴染みも薄いので、濫用は控えましょう。

区切り指定

,で区切ることで複数の実行時間や範囲を指定できます。たとえば分指定で1,3,5,8などと指定すれば、1分、3分、5分、8分にジョブが実行されます。

,はこれまでの指定方法と組み合わせて使えます。たとえば0-10,20-45/2,17,48は、0~10分までは毎分、20~45分までは2分おき、そのほか17分と48分に実行といった具合です。

指定の中で重複があった場合でも、ジョブは一度だけ実行されます。たとえば分指定で1-10,5-15という指定がされた場合、5~10分の間は重複して指定されていることになりますが、この場合でもジョブは各分1回だけ実行されます。

これもまた、ほかの人がわかりづらい複雑な指定をしてしまわないように注意が必要でしょう。

そのほかの指定

数字を使う代わりに、曜日の指定にsun、mon……月の指定にjan、feb……が使えます。また、毎時を指定する@hourlyといった特殊な指定方法もあったりと、細かい指定方法がほかにもあるのですが、ここでは細かく述べません。詳しくはCRONTAB(5)をご覧ください。

crontab指定のハマりどころ

crontabの指定には、ハマりどころや注意すべき点がいくつかあります。

実行ディレクトリの問題

各cronのジョブが実行される際のカレントディレクトリは、ユーザのホームディレクトリになります。実行コマンドの指定は絶対パスを使ったり、事前に実行ディレクトリにcdするなどしないといけません。

環境変数

cronの各ジョブ実行時には、環境変数は最低限しかセットされていません。特に$PATHに/usr/bin:/binしか指定されていないため、セットしたはずのコマンドにパスが通っておらずコマンドが実行できないといったミスがありがちです。

環境変数の指定をcrontabの中に書くことが可能ですが、シェル内での指定と違い右辺の環境変数が展開されない点に注意が必要です。たとえば$PATH/usr/local/binを追加する場合の間違った例と正しい例は次のようになります。

間違った例
PATH=/usr/local/bin:$PATH
正しい例
PATH=/usr/local/bin:/usr/bin:/bin

足りない環境変数類を補うためや、先述した実行ディレクトリへのcdをするために、各cronジョブの実行においてはランチャーシェルを噛ませるのが一つのセオリーとなっています。これはのちほど説明します。

うっかり毎分指定

あまりcrontabの設定に馴染みのない人が、たとえば毎朝5時ちょうどにバックアップを走らせたいという場合に、次のような間違った指定をしてしまうことがあります。

意図した正しい設定はこれ
0 5 * * * /path/to/backup.sh
次のように間違えてしまう
* 5 * * * /path/to/backup.sh

「そんなことしないよ」と思われるかもしれませんが、筆者はけっこうな頻度でこのミスにお目にかかっているので、起こってしまいやすい間違いだととらえています。

逆に毎分実行したいジョブもあるかと思いますので、その場合は次のように明示的に1分おきのインターバル指定をするのがベタープラクティスだと考えています。

*/1 * * * * /path/to/monitor.sh

%のエスケープ

crontabの指定において、%は改行を意味するメタキャラクタです。もし%を記述したい場合は、\%というように\でのエスケープが必要です。

曜日指定の罠

曜日指定と日付指定が同時に指定された場合、片方の条件を満たした場合にジョブが実行されます。たとえば次のように指定した場合、

0 0 13 * 5 /path/to/jewison

13日の金曜日に実行されることを期待してしまいますが、残念ながら実際には毎月13日、そして毎週金曜日にジョブが実行されます。直感に反する動きなので、同時指定は避けたほうがよいでしょう。実際に13日の金曜日に処理を実行させたい場合は、ジョブ内で自前でその分岐を書く必要があります。

0分ちょうど指定を避ける

1時間ごとに行うような定期的な処理は、つい毎時0分に設定してしまいたくなりますが、これは避けたほうがよいでしょう。ちょうどの時間はほかの人によってもジョブが設定されがちなので、0分になると急にサーバ負荷が上がってしまう危険性があります。

特に何らかのクローラを動かしたり、外部APIへの問い合わせを行うようなプログラムを走らせる場合は、先方のサーバの負荷を考え、絶対に0分指定はしないようにしましょう。


いろいろハマりどころはありますが、これらは次回(2)で取り上げるcrontabのテストを行うことで未然に防ぐことができます。

<続きの(2)こちらから。>

おすすめ記事

記事・ニュース一覧