概要
シングルEC2で動いているCronバッチサーバを冗長化したいが、サーバーレス化する時間がない・・・
そんな時に既存のアプリ資産やCron資産を流用できるよう、EC2でマルチAZ Cronクラスタを構築したという話です。
環境
マルチAZ EC2(2台)
Linux(CentOS6,7かAmazonLinux,AmazonLinux2を想定)
Cronのクラスタ機能
最近のCron(cronie)には実はアクティブ-スタンバイクラスタを実現するための機能が備わっています。
今回はその機能を使います。
ですがこれはあくまで、2台あるサーバのどっちでCronを動かすか、という判断をするための機能があるだけで、
- ハートビート通信
- フェールオーバ制御
- クラスタ仮想IP
といったHAクラスタに付きまとう種々の機能は無いので、このへんを作っていきます。
ただ、年1くらいで発生するAZ障害を考慮すると、普通のHAクラスタと異なり、ハートビート通信が途切れることを前提に考える必要があります。なので基本EC2間の通信に依存した判定は行わず、それぞれが独立して動く必要があります。
なお、Cronクラスタ機能については詳しいサイト様があるので詳細は割愛します。(ありがとうございます!)
構成
- CronクラスタのためにCron領域(/var/spool/cron)をEC2間で同期、もしくは共有する必要があるので今回はEFSで共有化します。
- ハートビート通信を行います。今回はping疎通と、AZ障害を考慮してインターネット疎通も行います。(AZ障害時はEC2間でping通信ができなくなるので、自身がインターネットに出れるか?を正常or異常の判断に使おうという考えです)
- クラスタ仮想IP(EIP)をフェールオーバ時に移動させます。そのため、各EC2にはローカルIPを2つ振ります。1つは自サーバ専用のEIP用、2つ目はクラスタ仮想EIP用です。これにより、アクティブ機がインターネットに出る際のIPを常に固定できます。外部接続先があるシステムでは、これは重要な要件です。
ハートビート通信
今回実装するハートビート通信のロジックを簡単に書くと以下です。
※クラスタペアの相手サーバのことをピアサーバと呼ぶことにします。
1.ピアサーバのローカルIPへPing
↓
2.ピアサーバのグローバルIPへPing
↓
3.1も2も失敗した場合、インターネットに出れるかをチェック(googleなど信頼性が高い公開サイト、念のため2か所使う)
↓
4.3がNGだった場合、自サーバ側のAZ障害と判断してスタンバイに降格する。
↓
5.3が問題なかった場合、ピアサーバ側の障害と判断して自サーバがアクティブに昇格する。
フェールオーバ処理
フェールオーバ処理は以下の流れで行います。
スタンバイからアクティブへの昇格を例にします。上の構成図を見ながらだと分かりやすいと思います。
1.ピアサーバのプライマリローカルIPへ、ピアサーバ専用のEIPをアタッチします(EIPをセカンダリローカルIPからプライマリへ移動)
この時点でクラスタ仮想IPは一時的に無くなります。
2.自サーバのプライマリローカルIPへ、クラスタ仮想EIPをアタッチします。
3.自サーバのセカンダリローカルIPへ、自サーバ専用EIPをアタッチします。
4.最後にcrontab -n <自サーバ名>
と実行し、Cron実行サーバを切り替えて完了です。
なぜこんなEIP付け替えをわざわざやるのか?と思われるかもしれません。
それは、EC2のルーティング設定が、外部発信時に常にプライマリローカルIP(のNIC)を使うようになっているためです。
なので使いたいEIPは常にプライマリ側にアタッチしておく必要があるのです。
両サーバで同じバッチが同時に動いてしまうことはないの?
可能性としてはゼロではないと思います。
ただcronieの機能は、/var/spool/cron/.cron.hostname
の内容を見て実行サーバを判断するというものです。
なのでここをEFS共有しておけば両サーバが同一ファイルをチェックすることになるので理論上、両サーバがアクティブとなる(同じバッチが同時に動く)ことは無いといえます。
サンプルコード
GitHubに上げておきます。
サンプルコードでは、上に書いたハートビート処理やフェールオーバ処理、他にも信頼性を上げるための細かいチェック処理を入れています。
Cronで5秒間隔くらいで実行するイメージでいます。
よろしければ参考にしてください。
シェルの書き方が拙いところはご容赦ください・・・。
まとめ
クラスタ制御をゼロから開発するのは思ったより苦労しました。
皆様のお役に立てることを祈ってます。
でもとっととサーバーレスへ移行しましょう!(台無し