問題解説: 時を司る絡繰りは、新たなる依り代を得るーーー

問題文

今まで使っていたサーバが老朽化してしまったため、新しくサーバのマイグレーションを行うこととなった。
現在はそのマイグレーション前の移行作業中なのだが、cronで動作させていたスクリプトが実行されない。

そのため、新しいサーバでcronが正しく実行されるように設定の変更を行ってほしい。

スタート

旧サーバで実行されているcronに登録されているスクリプトが新サーバで実行されない

ゴール

新サーバにて、全てのcronに登録されているスクリプトが正常終了するようにする

トラブルの概要

Linuxディストリビューションにおいて一般的に利用されているタスクスケジューラである cron の実行時に発生する、ディストリビューション間での差異を利用したトラブルです。

一部のLinuxディストリビューションでは、/etc/cron.hourly/, /etc/cron.monthly/, /etc/cron.weekly/のように、ディレクトリ以下にスクリプトを設置するだけで一定の時間間隔でスクリプト群を実行するディレクトリが用意されています。
このディレクトリを実行している実態は /etc/cron.d に設置されていることが多いです。

[vagrant@node1 ~]$ cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)

[vagrant@node1 ~]$ ls -l /etc/cron.d
合計 8
-rw-r--r--. 1 root root 128  8月  3  2017 0hourly
-rw-------. 1 root root 235  8月  3  2017 sysstat

[vagrant@node1 ~]$ cat /etc/cron.d/0hourly
# Run the hourly jobs
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
01 * * * * root run-parts /etc/cron.hourly

/etc/cron.d/0hourly に、run-parts /etc/cron.hourly というコマンドを実行しています。
この際に実行されている run-parts というコマンドの実装が異なることが、今回の問題におけるキモとなります。

今回の大会の競技時間を考慮した場合、1時間に1回実行されるというシナリオは無用な時間の損失を産んでしまう可能性があったため、疑似的に /etc/cron.d/0minutely というファイルを生成し1分に1回実行されるようにしていました。

run-partsについて

実際に確認してみると、 run-parts というコマンドはディストリビューションによってそもそもファイル形式すら違う事が分かります。

[vagrant@node1 ~]$ cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)

[vagrant@node1 ~]$ which run-parts
/usr/bin/run-parts

[vagrant@node1 ~]$ file /usr/bin/run-parts
/usr/bin/run-parts: Bourne-Again shell script, ASCII text executable



vagrant@node2:~$ cat /etc/debian_version
stretch/sid

vagrant@node2:~$ which run-parts
/bin/run-parts

vagrant@node2:~$ file /bin/run-parts
/bin/run-parts: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=53e52d241c66c42300698ceec057edd3417be4b6, stripped

CentOS においてはBash ベースなシェルスクリプトとして実装されており、Debian においては実行可能なバイナリとして実装されていることが分かります。

CentOS におけるシェルスクリプトでは、以下のような記述がありました。

[vagrant@node1 ~]$ head -n 5 /usr/bin/run-parts
#!/bin/bash
# run-parts - concept taken from Debian

# keep going when something fails
set +e

つまり、元々debianutilsに実装されていたrun-partsをシェルスクリプトで実装したものを、CentOSは利用していると考えられます。

この実装時に差異が発生したのが今回のトラブルの原因となります。

今回の問題におけるトラブル

実際には、以下の差異を利用してトラブルを発生させました。

  • スクリプト名に . を含める (例: crawler.sh)
    • Debianでは . が含まれていても実行されますが、CentOSでは実行されません
  • Shebangがスクリプトに存在しない
    • DebianではShebangが存在しなくても拡張子で判別されますが、CentOSでは拡張子を削除するため記載が必要になります

また、今回は移行時に発生するトラブルとして、以下のトラブルも発生させました。

  • 移行先のディストリビューションにコマンドがあっていない
    • 移行先はDebianだったのですが、 yum update というコマンドを実行させていました
  • cronに登録されているスクリプトが移行先サーバに存在しない
    • WebAPIに対してリクエストを送信するスクリプトが移行元にはあるが移行先には配置していませんでした

採点基準

日本語でこの事象を説明しているブログも多く存在しており、cronという比較的利用者も多いプロダクトであることから、入門編と位置づけて出題しました。
そのため、「どこまで気づいているのか」を採点基準に採点を行いました。

まとめ

トラブルシューティングの基本として、「事象の切り分けを行う」という事があります。
実際にトラブルが発生している事象はどのように実行されていて、どのような箇所で実行されているのかを確かめるのがベターと言われています。

今回の回答を見てみると、「実行権限がなかったので付与しました」「内容が誤っていたため修正しました」などの回答が来ていたのですが、今回で言うなればファイル名を変更しない限り実行されていないため、cronを経由したファイルの実行を確認出来ていなかったのではないかと予測します。
実際にはどのような環境で、どのような方法で目の前のコマンドが実行されているのかを確かめるようにするようにしてみるのが重要だと改めて感じました。

なお、今回の記事中で検証のために利用したVagrantfileはこちらとなります。是非検証してみてください。