Multi-AZに対応した高可用Cronサーバを構築する
はじめに
ジョブスケジューリングを簡易的な仕組みで構築する場合、まず候補に上がるのはEC2上のLinuxでcronを利用したものだと思います。特別なミドルウェアの追加インストールはいらないし、使い慣れているし、スクリプトと組み合わせればだいたい何でも出来ちゃいますし。しかし単体のEC2上でcronを動かすだけでは、可用性が確保出来ません。AWSにおいてAZ障害まで考慮するのであれば、Multi-AZに冗長化されたシステムを構成し、可用性を確保する必要があります。
で、単純に複数のAZに分散してEC2を構築し、crontabを共有するだけでは、ジョブが二重に実行されてしまいます。アクティブ/スタンバイに動作するような仕組みを考慮しなくてはいけません。
そこで今回は、クラスタ構成がサポートされている最新のcrond(cronie)を使って、Multi-AZに対応した高可用Cronサーバを構築します!
構成
AWSの東京リージョンで、2つのAZにそれぞれEC2を構築します。OSはAmazon Linux(2015.03)です。
2台のEC2のうち、AZ-aにあるほうをCronマスタ、AZ-cにあるほうをCronスレーブとします。各ユーザが設定するcrontab(/var/spool/cron)は、lsyncdによってCronマスタからCronスレーブに同期されます。
クラスタ構成がサポートされているcrondでは、/var/spool/cron/.cron.hostnameファイルによってアクティブとスタンバイの動作を切り替えます。.cron.hostnameファイルに自ホスト名が記述されている場合のみ、アクティブとして/var/spool/cron/下のcrontabファイルを実行します。.cron.hostnameファイルが無い場合、あるいは自ホスト名が記述されていない場合は、スタンバイとして/var/spool/cron/下のcrontabファイルを実行しません。
なお、このアクティブ/スタンバイは/var/spool/cron/下のcrontabファイルに対してのみ行われ、/etc/crontabや/etc/cron.*下についてはそれぞれのホスト個別に実行されます。この仕様を使って、/etc/crontabによって定義されたping.shによってアクティブとスタンバイを切り替えます。
正常動作時はAZ-aにあるCronマスタがアクティブになります。Cronスレーブは.cron.hostnameが存在しないのでスタンバイとなります。
Cronマスタが停止した場合、/etc/crontabによって起動されたping.shがCronマスタの停止を検知し、/var/spool/cron/.cron.hostnameを作成し、アクティブとなります。今回は仕組みを簡素化するためにCronスレーブからCronマスタへのcrontabの同期は行っておりませんが、必要あれば相互に同期することももちろん可能です。
Cronスレーブが停止した場合は、特に動作に変化はありません。Cronスレーブが復帰したら、また/var/spool/cron下が同期されます。
やってみる
下準備
今回の仕組みを使うためには、それぞれのEC2でホスト名が明確になっている必要があるので、以下のように設定しておきます。なおCronマスタが「cron-a」、Cronスレーブが「cron-c」です。
$ sudo vi /etc/sysconfig/network HOSTNAME=cron-a
最新版のCronieをインストールする
この作業はCronマスタ、Cronスレーブともに行います。
Amazon LinuxにインストールされているCron(cronie)は1.4.4です。
$ sudo rpm -qa | grep cron cronie-1.4.4-12.6.amzn1.x86_64
しかし、このバージョンではクラスタ構成がサポートされておらず、-cオプションが使えません。
$ crond -c crond: 無効なオプション -- 'c' usage: crond [-n] [-p] [-s] [-i] [-m <mail command>] [-x [ext,sch,proc,pars,load,misc,test,bit]]
そこで、最新のcronieをインストールします。まずはインストールに必要なパッケージをインストールします。
$ sudo yum install automake gcc $ sudo yum install git
gitリポジトリをcloneし、autoreconfを実行します。
$ git clone https://git.fedorahosted.org/git/cronie.git $ cd cronie $ autoreconf configure.ac:7: error: required file './config.guess' not found configure.ac:7: 'automake --add-missing' can install 'config.guess' configure.ac:7: error: required file './config.sub' not found configure.ac:7: 'automake --add-missing' can install 'config.sub' configure.ac:5: error: required file './install-sh' not found configure.ac:5: 'automake --add-missing' can install 'install-sh' configure.ac:5: error: required file './missing' not found configure.ac:5: 'automake --add-missing' can install 'missing' anacron/Makefile.am: error: required file './depcomp' not found anacron/Makefile.am: 'automake --add-missing' can install 'depcomp' autoreconf: automake failed with exit status: 1
エラーが出てしまいました。automakeを手動で実行してmissingを解消し、再度autoreconfします。
$ automake --add-missing configure.ac:7: installing './config.guess' configure.ac:7: installing './config.sub' configure.ac:5: installing './install-sh' configure.ac:5: installing './missing' anacron/Makefile.am: installing './depcomp' $ autoreconf
今度はエラーが出ませんでした。ではmakeしてmake installします!
$ ./configure --sysconfdir=/etc --localstatedir=/var $ make $ sudo make install
無事インストールされたことを確認します!
$ ls -alF /usr/local/bin -rwxr-xr-x 1 root root 131912 5月 8 07:10 crontab* $ ls -alF /usr/local/sbin -rwxr-xr-x 1 root root 198853 5月 8 07:10 crond*
しかしこのままだとうまく動きません。/usr/bin/crontabと同様に、/usr/local/bin/crontabにスティッキービットを設定します。
$ sudo chmod u+s /usr/local/bin/crontab $ ls -alF /usr/local/bin 合計 140 -rwsr-xr-x 1 root root 131912 5月 8 08:46 crontab*
さて、起動スクリプトを書き換えて、新しくインストールしたcrondが使われるように変更します。
$ sudo vi /etc/init.d/crond ### /usr/sbin/crondから変更 ### exec=/usr/local/sbin/crond # Source function library. . /etc/rc.d/init.d/functions ### functionsを読み込んだ後にPATHを設定 ### PATH="/usr/local/bin:/usr/local/sbin":$PATH
更に、crondの起動時に-cオプションを付与し、クラスタ構成サポートモードで起動するようにします。
$ sudo vi /etc/sysconfig/crond CRONDARGS=-c
では起動します。
$ sudo service crond restart Stopping crond: [ OK ] Starting crond: [ OK ] $ ps aux | grep crond root 15615 0.0 0.0 108420 668 ? Ss 07:36 0:00 crond -c
ちゃんとcrondが-cオプション付きで起動しましたね!
lsyncdのセットアップ
次に、/var/spool/cronを同期させるためのlsyncdをセットアップします。この作業はCronマスタだけで行います。
事前にec2-userでCronマスタからCronスレーブにssh接続できるように、鍵ファイルの設置などを済ませておきます。
/var/spool/cronはデフォルトではrootのみ参照可能な権限になっていますので、ec2-userをrootグループに所属させ、/var/spool/cronのパーミッションを変更します。
$ usermod -G root ec2-user $ chmod 770 /var/spool/cron $ ls -alF /var/spool/cron drwxrwx--- 2 root root 4096 5月 8 08:54 ./
この時点で一体手動でrsyncを実行し、ちゃんと動くことを確認しておきましょう。
$ sudo -s # rsync -avn -e "/usr/bin/ssh -l ec2-user -i /home/ec2-user/.ssh/id_rsa" /var/spool/cron/ 172.31.200.10:/var/spool/cron/
ではlsyncdをインストールし、設定を行います。
$ sudo yum install --enablerepo=epel lsyncd $ sudo vi /etc/lsyncd.conf settings{ logfile = "/var/log/lsyncd/lsyncd.log", statusFile = "/tmp/lsyncd.stat", statusInterval = 1, nodaemon = false, } sync { default.rsyncssh, source = "/var/spool/cron/", host = "172.31.200.10", targetdir = "/var/spool/cron/", rsync = { rsh = "/usr/bin/ssh -l ec2-user -i /home/ec2-user/.ssh/id_rsa", _extra = { "-rlOt", } }, }
lsyncdを起動します。
$ sudo service lsyncd start Starting lsyncd: [ OK ]
それでは同期を確認します。まずはec2-userでcrontabを作成できるように、/etc/cron.allowにec2-userと追記します。
$ sudo -s # echo ec2-user >> /etc/cron.allow # exit
ではec2-userでcrontabを作成しましょう。
$ crontab -e */1 * * * * date >> /home/ec2-user/date.txt $ ls -alF /var/spool/cron/ -rw------- 1 ec2-user ec2-user 25 5月 8 08:54 ec2-user
しばらく待ってからCronスレーブで確認すると、ちゃんとファイルが同期されています!
$ ls -alF /var/spool/cron/ -rw------- 1 ec2-user ec2-user 25 5月 8 08:54 ec2-user
監視スクリプトを仕込む
それでは監視スクリプトをCronマスタとCronスレーブ両方に仕込みます。
まずはCronマスタから。Cronスレーブに対してPingを実行し、応答が無ければcrontab -nコマンドによって.cron.hostnameファイルを作成します。
$ sudo -s # vi /root/ping.sh #!/bin/sh ping 172.31.200.10 -c 1 -i 1 > /dev/null 2>&1 UP=$? if [ $UP -ne 0 ]; then /usr/local/bin/crontab -n fi
実行権限をつけておきましょう。
# chmod 755 ping.sh
次にCronスレーブ。こちらはCronマスタと違い、Cronマスタに対してPingを実行し応答が無ければcrontab -nコマンドによって.cron.hostnameファイルを作成し、Cronマスタが起動している時には/var/spool/cron/.cron.hostnameを削除します。
$ sudo -s # vi /root/ping.sh #!/bin/sh ping 172.31.100.10 -c 1 -i 1 > /dev/null 2>&1 UP=$? if [ $UP -ne 0 ]; then /usr/local/bin/crontab -n else rm /var/spool/cron/.cron.hostname fi
こちらも同じく実行権限を付与します。
# chmod 755 ping.sh
Cronマスタ、Cronスレーブともに、定期的に監視スクリプトが動くように、/etc/crontabに設定します。
# vi /etc/crontab */5 * * * * root /root/ping.sh
初期設定として、Cronマスタで/var/spool/cron/.cron.hostnameを作成しておきます。
$ sudo /usr/local/bin/crontab -n $ ls -alF /var/spool/cron -rw------- 1 root root 7 5月 8 10:37 .cron.hostname -rw------- 1 ec2-user ec2-user 44 5月 8 11:05 ec2-user
動作確認
通常時、Cronマスタには.cron.hostnameがあり、Cronスレーブには.cron.hostnameがありません。
■Cronマスタ $ ls -alF /var/spool/cron -rw------- 1 root root 7 5月 8 10:37 .cron.hostname -rw------- 1 ec2-user ec2-user 44 5月 8 11:05 ec2-user $ sudo cat /var/spool/cron/.cron.hostname cron-a ■Cronスレーブ $ ls -alF /var/spool/cron -rw------- 1 ec2-user ec2-user 44 5月 8 11:05 ec2-user
また、Cronマスタでのみ、/var/spool/cron/ec2-userが実行されています。
$ cat /home/ec2-user/date.txt Fri May 8 11:16:01 UTC 2015
ここでCronマスタをStopしてみます。するとCronスレーブで.cron.hostnameが作成されます
■Cronスレーブ $ ls -alF /var/spool/cron/ -rw------- 1 root root 7 5月 8 11:17 .cron.hostname -rw------- 1 ec2-user ec2-user 44 5月 8 11:05 ec2-user $ sudo cat /var/spool/cron/.cron.hostname cron-c
そしてCronスレーブで/var/spool/cron/ec2-userが実行され始めます。
■Cronスレーブ $ cat /home/ec2-user/date.txt Fri May 8 11:18:01 UTC 2015 Fri May 8 11:19:01 UTC 2015
再度CronマスタをStartすると、Cronスレーブの.cron.hostnameが削除され、/var/spool/cron/ec2-userが実行されなくなります。
これでMulti-AZに対応した高可用性Cronサーバが出来上がりました!
さいごに
まぁ、ここまでやるならLVS + Keepalivedにしようとか、crontabはGlusterFSで同期しちゃおうとか、商用ジョブスケジューリングソフトウェアを使おうとか、いろいろあるとは思うのですが、Cronの冗長化が手軽にできるのは素敵だと思いました!