Help us understand the problem. What is going on with this article?

細かすぎて伝わらない anacron の挙動

More than 5 years have passed since last update.

対象は CentOS 6 に付属の cronie-anacron-1.4.4。

開始時間についての問題

/etc/anacrontab がRHEL 6/CentOS 6系のデフォルトのまま以下のようになっていて、

/etc/anacrontab
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22

#period in days   delay in minutes   job-identifier   command
1   5   cron.daily      nice run-parts /etc/cron.daily
7   25  cron.weekly     nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly        nice run-parts /etc/cron.monthly

anacron 自体の起動も、デフォルトのまま /etc/cron.d/0hourly/etc/cron.hourly/0anacron によって毎時1分に起動されているとする。

このとき、上記の cron.weekly ジョブの開始時間の範囲は、何時何分から何時何分までになるか?

誤った回答

まず遅延時間を考えると、

  • デフォルトの最短遅延が6分、最長遅延は上記の RANDOM_DELAY で45分
  • cron.weekly の固定の遅延が25分
  • あわせると、cron.weekly の取り得る遅延は31分〜70分

で、

  • ジョブが実行される時間の範囲は、START_HOURS_RANGE により3時から22時の間
  • anacron 自体は、毎時1分に起動する

ことから、ジョブが実行できる状況なら 3:32〜4:11 の間のどこかで起動する……

というのは誤りである。

正しい回答

anacron のソースで START_HOURS_RANGE のチェックを行っている箇所を見てみると、以下のようになっている。

main.c
time_t jobtime = start_sec + job_array[j]->delay * 60;

t = localtime(&jobtime);
if (range_start != -1 && range_stop != -1 &&
    (t->tm_hour < range_start || t->tm_hour >= range_stop))
{
    /* ジョブをスキップする処理 */

この if にマッチした場合 START_HOURS_RANGE の範囲外としてジョブがスキップされるが、この基準となる時間は anacron の起動時間(ソース中の start_sec)ではなく、それにジョブごとの遅延時間(ソース中の delay)を加えた時間になっている。定義からすると当然のことで、遅延も考慮に入れた上で、ジョブが実際に動作する時間で判定しているわけだ。

なので、最初の問題の場合、2:01 の anacron の実行時に遅延が59分以上になったジョブは開始時間が 3:00 以降になるため、スキップはされず、そのまま 2:01 の回の anacron によって実行される。この場合は 3:00〜3:11 のどこかで起動することになる。

つまり、この設定での cron.weekly のジョブが起動する時刻は、

  • 3:00〜3:11 (2:01の回の anacron で起動された場合)、または
  • 3:32〜4:11 (3:01の回の anacron で起動された場合)

のどこか、というのが正解になる。

派生する問題

デフォルトの設定の場合、cron.daily ジョブの遅延時間は11分〜50分なので、2:01 の回の anacron で起動されることはなく、必ず 3:01 の回で起動する(3:12〜3:51 の間)。

しかし、anacron の動作として

  1. /var/spool/anacron/ 以下にあるジョブ名と同じ名前のファイルを読み、その中に書かれている前回実行日付から、ジョブを実行対象とするかどうかを判定する。たとえば週次ジョブなら、 /var/spool/anacron/cron.weekly ファイルに記載の前回実行日から7日経過しているかを判定する。
  2. 所定の日数が経過していて実行対象と判定されたジョブについて、この前回実行日ファイルをロックする。
  3. この後で、前述の START_HOURS_RANGE のチェックを行う。実行対象外と判定されたジョブについても、anacron 自体の完了まで前回実行日ファイルはアンロックされない。

という順序になっているため、前述のように 2:01 の回の anacron が 3:01 以降まで cron.weekly の起動待ちになっている場合、実際には起動されない cron.daily の前回実行日ファイルも一緒にロックされたままになっている。

この状態で 3:01 の回の anacron が cron.daily を起動しようとすると、上の 2. のロックに失敗し、ログに

Job 'cron.daily' locked by another anacron - skipping

と出力して起動をスキップしてしまう。結果、cron.daily ジョブは次回の起動タイミング 4:12〜4:51 まで起動が遅れることになる。

anacron で日次ジョブを実行しているが、なぜか特定の曜日だけたまに起動時間が1時間くらい遅くなる……のようなことがあったら、おそらくこういう挙動が原因と思われる。

その他

anacron の起動メカニズムや挙動については、以下がわかりやすい。

http://www.reqtc.com/column/anacron1.html

ただ、

「遅延時間」=共通設定にてランダム決定した遅延時間+
         ジョブ個別設定にてランダム決定した遅延時間

とあるが、ジョブ個別設定の方の遅延時間は、ランダムではなくそのまま加算だと思われる。

あと、Red Hatの公式ドキュメント等には

https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/ch-Automating_System_Tasks.html

RANDOM_DELAY — ジョブごとに指定されている delay in minutes 変数に追加される最大の時間分数。
最短の遅延時間は、デフォルトで 6 分に設定されています。

とあるが、6分に調整するロジックがソースのどこにも見つからない。ソース上は、

readtab.c
int i = random();
double x = 0;
x = (double) i / (double) RAND_MAX * (double) (atoi(value));
random_number = (int)x;
Debug(("Randomized delay set: %d", random_number));

のように 0 から RANDOM_DELAY までの値をとるようになっていて(valueRANDOM_DELAY の設定値)、これ以降も何か調整している形跡がない。最低6分なら、デフォルト設定では cron.daily の遅延は必ず11分以上になるはずだが、手元の cron ログでは

/var/log/cron:Feb  9 03:01:01 myhost anacron[25024]: Will run job `cron.daily' in 9 min.

というのがあるので、マニュアルの方が間違っているんじゃないかと思う。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away