プライベートサブネットにあるEC2でRoute 53 のフェイルオーバー機能を利用したウォームスタンバイ構成を試してみた
こんにちは、いまだに昨年末のRIZIN vs. BELLATOR 全面対抗戦の気分が抜けないAWS事業本部のニシヤマです。 入場から試合内容も最高な大晦日でした。
はじめに
EC2インスタンスを冗長化する際にELBを利用して冗長化することがあると思います。今回はELBを利用せずRoute 53のヘルスチェック機能を利用してプライベートサブネットのEC2インスタンスのウォームスタンバイ構成を試してみました。プライマリEC2インスタンスに障害が発生した場合、EC2インスタンスを参照するCNAMEレコードがセカンダリEC2インスタンスへ自動的に切り替わる構成になります。
・Route 53 ヘルスチェッカーは VPC の外にあります。IP アドレスを使用して VPC 内のエンドポイントの正常性をチェックするには、VPC 内のインスタンスにパブリック IP アドレスを割り当てる必要があります。
・また、CloudWatch メトリクスを作成し、アラームをメトリクスに関連付けて、アラームのデータストリームに基づくヘルスチェックを作成することもできます。例えば、EC2 の StatusCheckFailed メトリクスのステータスをチェックする CloudWatch メトリクスを作成、このメトリクスにアラームを追加、さらにアラームのデータストリームに基づいたヘルスチェックを作成することが可能であり、バーチャルプライベートクラウド (VPC) 内にプライベート IP アドレスのみがあるインスタンスをチェックします。CloudWatch コンソールを使用して CloudWatch メトリクスおよびアラームを作成する方法について詳細は、Amazon CloudWatch ユーザーガイドを参照してください。
上記のドキュメントの通り、Route 53のヘルスチェック機能でエンドポイントの正常性チェックを利用する場合はパブリック IP アドレスが必要になります。 今回はプライベートサブネットのEC2を利用するので後者のCloudWatch メトリクスを利用してフェイルオーバーさせます。
構成図を見てもらった方がわかりやすいと思います。
まずは構成図にあるようなVPC環境と、EC2インスタンスを用意した状態から開始します。フェイルオーバーさせるEC2は同一AZでも問題ないですが障害を想定している場合2つのAZにプライベートサブネットを作成し、各サブネットに1台づつEC2を配置するようにします。
ではやっていきます
やってみる
CloudWatch アラートの作成
それではまずはプライマリEC2の障害判断に利用するCloudWatch アラートを設定していきます。
CloudWatchの画面でアラートの作成
をクリックします。
メトリクスの選択ではプライマリEC2のStatusCheckFailedを選択しメトリクスの選択
をクリックします。
期間を1分に変更して、StatusCheckFailedは失敗時に1
になるため閾値を0.99以上に変更して次へ
をクリックします。
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html
インスタンスが過去 1 分間にインスタンスのステータスチェックとシステムステータスチェックの両方に合格したかどうかを報告します。
このメトリクスは 0 (合格) または 1 (失敗) となります。
デフォルトでは、このメトリクスは無料で 1 分の頻度で利用できます。
単位: カウント
アクションの設定ではアラート時に通知を行うSNSトピックを指定して次へ
をクリックします。
次にアラート名を入力し次へ
をクリックします。
最後に確認を行ってアラートの作成
をクリックします。
以上でCloudWatch アラートの作成が完了しました。次にこちらのCloudWatch アラートを利用するヘルスチェックを作成します。
ヘルスチェックの作成
Route53のコンソールでヘルスチェックを選択し、ヘルスチェックの作成
をクリックします。
ヘルスチェックの作成画面では以下の情報を入力し次へ
をクリックします。
- ヘルスチェックの名前:任意の値を入力
- モニタリングの対象:CloudWatch アラームの状態
- CloudWatch リージョン:VPCを構築したリージョン
- CloudWatch アラーム:先ほど作成したCloudWatch アラート
- アラームが不足状態にある場合:「ステータスは異常です」を選択
次にヘルスチェックが異常に変化した際にアラームによる通知を行うかの選択が可能です。こちらのアラートはバージニアリージョンに作成されます。今回はCloudWatch アラートで通知を受けるのでいいえのままでヘルスチェックの作成
をクリックします。
アラートが作成されました。少し待つと現在のステータスも確認が可能です。
次にプライベートホステッドゾーンをVPCに紐づけてEC2インスタンス用のCNAMEレコードを設定します。
プライベートホステッドゾーン、CNAMEレコードの作成
Route53のコンソールでホストゾーンを選択し、ホストゾーンの作成
をクリックします。
ホストゾーンの作成では以下の情報を入力しホストゾーンの作成
をクリックします。
- ドメイン名:任意のドメイン名
- タイプ:プライベートホストゾーン
- ホストゾーンに関連づけるVPCのリージョン:VPCを構築したリージョン
- ホストゾーンに関連づけるVPCのVPC ID:フェイルオーバーさせるEC2の構築しているVPC ID
作成されたホストゾーンでレコードを作成
をクリックします。
レコードを作成では以下の情報を入力しレコードを作成
をクリックします。
プライマリレコード
- レコード名:任意のレコード名
- 値:プライマリEC2のプライベート IPv4 アドレス「10.0.11.176」
- TTL:任意のTTL
- ルーティングポリシー:フェイルオーバー
- フェイルオーバーレコードタイプ:プライマリ
- ヘルスチェック ID:先ほど作成したヘルスチェックのID
- レコード ID:レコードを一意に区別するための名前
セカンダリレコード
- レコード名:任意のコード名(プライマリレコードと同じ値)
- 値:セカンダリEC2のプライベート IPv4 アドレス「10.0.12.19」
- TTL:任意のTTL
- ルーティングポリシー:フェイルオーバー
- フェイルオーバーレコードタイプ:セカンダリ
- ヘルスチェック ID:空白
- レコード ID:レコードを一意に区別するための名前
この時にセカンダリレコードにヘルスチェック IDを登録してしまわないように注意してください。私は最初ヘルスチェック IDを入力してしまいうまく動作しませんでした。
レコードが2つ作成されていることを確認しました。プライマリ側のレコードのみにヘルスチェック IDが入っています。
動作確認
それでは準備ができたので動作確認用EC2から確認してみます。先ほど指定したレコード名を名前解決するとプライマリEC2のIPアドレスが返って来ることを確認します。
ec2-user@ip-10-0-1-16 ~]$ dig +short winner.rizinvsbellator.local 10.0.11.176
それではプライマリEC2のCloudWatch アラートをアラート状態にします。今回メトリクスにStatusCheckFailedを利用しているため、プライマリEC2にログインし以下のブログを参考に障害状態を再現します。
プライベートサブネットのEC2には踏み台サーバ経由でのアクセスや、NATゲートウェイを利用してセッションマネージャーを利用してアクセスしてください。
sh-4.2$ ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP group default qlen 1000 link/ether 06:df:1c:24:4a:1b brd ff:ff:ff:ff:ff:ff inet 10.0.11.176/24 brd 10.0.11.255 scope global dynamic eth0 valid_lft 2905sec preferred_lft 2905sec inet6 fe80::4df:1cff:fe24:4a1b/64 scope link valid_lft forever preferred_lft forever sh-4.2$ sudo ifdown eth0
しばらくするとCloudWatch アラートをアラート状態になっています。
ヘルスチェックの画面からも障害状態になっていることを確認できました。
では、もう一度名前解決をしてみるとIPアドレスがセカンダリEC2のIPアドレスになっていることが確認できました!障害状態にしたプライマリEC2はマネジメントコンソールより再起動することで復旧するので復旧後も確認してみます。
[ec2-user@ip-10-0-1-16 ~]$ dig +short winner.rizinvsbellator.local 10.0.11.176 〜〜〜〜〜障害発生後〜〜〜〜〜 [ec2-user@ip-10-0-1-16 ~]$ dig +short winner.rizinvsbellator.local 10.0.12.19 〜〜〜〜〜マネジメントコンソールよりプライマリEC2再起動後〜〜〜〜〜 ec2-user@ip-10-0-1-16 ~]$ dig +short winner.rizinvsbellator.local 10.0.11.176
以上です!
まとめ
いかがでしたでしょうか。 通常EC2を冗長化をする際はELBを利用して負荷分散することが多いかと思いますが、EFSなどファイル共有をしている状態で同時に書き込みが許可できない場合には有効ではないかと思います。障害発生〜CloudWatch アラートの状態変化、DNSレコードがフェイルオーバーまでに少し時間がかかりますが障害発生時に自動でEC2のフェイルオーバーしてほしい時にはこのような構成をとることで対処が可能かと思います。
参考
- https://dev.classmethod.jp/articles/route53-how-to-set-up-failover-routing/
- https://dev.classmethod.jp/articles/route53-internalelb-dns-failover/
おまけ
上記でDNSレコードの名前解決でフェイルオーバーしていることを確認できましたが、今回フェイルオーバー対象のEC2にApacheをインストールし、以下のindex.htmlを配置しておきました。
プライマリEC2のindex.html
sh-4.2$ cat /var/www/html/index.html Bellator Win
セカンダリEC2のindex.html
sh-4.2$ cat /var/www/html/index.html RIZIN Win
このindex.htmlに動作確認用EC2から定期的にアクセスさせてみたいと思います。以下のコマンドでタイムアウトを1秒にしたcurlコマンドで5秒ごとにindex.htmlにアクセスをした状態でプライマリEC2に障害発生させてみたいと思います。
while true do echo -n `date "+%Y/%m/%d %H:%M:%S"` echo -n " " curl --max-time 1 winner.rizinvsbellator.local sleep 5 done
結果は以下の様になりました。障害発生から6〜7分程度かかりましたが問題なく切り替わりました。プライマリEC2復旧時は体感ですが4〜5分程度で切り戻しされていました。
2023/01/29 12:12:54 Bellator Win 2023/01/29 12:12:59 Bellator Win 2023/01/29 12:13:04 curl: (28) Connection timed out after 1001 milliseconds 2023/01/29 12:13:10 curl: (28) Connection timed out after 1001 milliseconds 〜〜省略〜〜 2023/01/29 12:19:16 curl: (28) Connection timed out after 1001 milliseconds 2023/01/29 12:19:22 curl: (28) Connection timed out after 1001 milliseconds 2023/01/29 12:19:28 RIZIN Win 2023/01/29 12:19:33 RIZIN Win
昨年末のRIZIN vs. BELLATOR 対抗戦ではBELLATOR側全勝という結果になりましたが、次回の対抗戦が実現した場合は上記の結果のように是非RIZIN側にも勝利を勝ち取ってほしいですね。