🚪

CloudWatchアラームとLambda関数を連携してコールドスタンバイ方式のフェールオーバーを実装する

に公開

はじめに

突然ですが、AWSの障害対策と一言で言っても様々なものがありますよね。

パブリックなサービスであればRoute53のルーティングなどを使用すれば楽にフェイルオーバーさせる構成を作ることができるかと思いますが、要件として「インターネットに出ることができない」となるとまた対応も変わってくるかと思います。

ということで今回はそんな環境で使えそうなフォールオーバーの構成を


Amazon CloudWatchアラーム+AWS Lambda


を使って実装してみようと思います。

前提

・VPCに対して二つのサブネットがそれぞれ別のAZに配置されていること。
ちなみに今回はあくまで動作検証なので、外部からの接続が容易なパブリックサブネットでの検証とします。「インターネットに出ることができない」を前提としているにも関わらずなんやねんと思われるかもしれませんがご了承ください。

構成図

だいぶ端折ってはいますが、今回やりたいことをざっくり図にしてみました。
要するにコールドスタンバイの構成でCloudWatchアラームとLambdaを使ってフェールオーバーの仕組みを作ってみようという試みです。

設定

IAMポリシー作成

まずはLambdaに設定するIAMロール用のIAMポリシーを作成していきます。

以下のポリシーを作成し、ロールにアタッチします。

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "DescribeInstancesAll",
			"Effect": "Allow",
			"Action": [
			    "ec2:DescribeInstances",
			    "ec2:DescribeInstanceStatus"
	],
			"Resource": "*"
		},
		{
			"Sid": "StartAndCheckSpecificEC2",
			"Effect": "Allow",
			"Action": 
				"ec2:StartInstances",
			"Resource": "起動したいインスタンスのARNを記入"
		},
		{
			"Sid": "AllowCloudWatchLogs",
			"Effect": "Allow",
			"Action": [
				"logs:CreateLogGroup",
				"logs:CreateLogStream",
				"logs:PutLogEvents"
			],
			"Resource": "*"
		}
	]
}

ARNの記載例は以下になります。

arn:aws:ec2:ap-northeast-1:123456789012:instance/i-0abc123456789def0
※EC2インスタンスの詳細から確認することができます。

Lambda設定

1.先ほど作成したロールを設定して関数を作成します。

import boto3
import json

def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name='ap-northeast-1')  

    # 対象のインスタンスを指定
    instance_id = 'i-0xxxxxxxxxxxxxxxx'  

    # ① 現在の状態を確認
    response = ec2.describe_instance_status(InstanceIds=[instance_id], IncludeAllInstances=True)
    instance_state = response['InstanceStatuses'][0]['InstanceState']['Name']

    if instance_state == 'running':
        print(f"インスタンス {instance_id} はすでに起動中です")
        return {
            'statusCode': 200,
            'body': f"Instance {instance_id} is already running."
        }

    # ② 起動処理
    print(f"インスタンス {instance_id} を起動します")
    ec2.start_instances(InstanceIds=[instance_id])

    # ③ 起動後の確認
    waiter = ec2.get_waiter('instance_running')
    waiter.wait(InstanceIds=[instance_id])
    print(f"インスタンス {instance_id} が正常に起動しました")

    return {
        'statusCode': 200,
        'body': f"Instance {instance_id} started successfully."
    }

今回の処理は以下になります。


①対象インスタンスの状態確認
②対象インスタンスの起動
③対象インスタンスの正常起動確認

2.CloudWatchアラームから関数を呼び出せるようにリソースベースのポリシーをLambdaに設定します。

CloudWatchアラームの作成

今回は検証なので、CPU使用率が50%を超えたらアラームが発報されるように設定しました。

Lambda起動

EC2アクションでインスタンス停止

実際に検証する

インスタンスにSSHで接続し、以下のコマンドを複数回実行し、CPU負荷をかけてアラートを引き起こします。

for i in {1..4}; do yes > /dev/null & done

アラームが発報され、元々起動していたEC2-1が停止し、EC2-2が起動しています。

また、CloudWatchLogsのロググループではLambda関数が問題なく起動していることを確認することができます。

余談

ここで、一度アラームは出たけどうまく動作しなかった例をご紹介します。
Lambda関数がうまく実行されず、以下のようなメッセージが出力されました。

アクション arn:aws:lambda:ap-northeast-1:762233727903:function:EC2InstaceStart の実行に失敗しました。エラーが発生しました:
"CloudWatch Alarms is not authorized to perform: lambda:InvokeFunction on the resource because no resource-based policy allows the lambda:InvokeFunction action"

これはリソースベースのポリシーを設定していないことが原因です。

先の手順でリソースベースのポリシーを追加している手順がありますが、実は後から付け加えました。

最後に

今回はCloudWatchアラームとLambdaを使用して簡単なフェールオーバー構成をためしてみました。
権限周りはあまり実環境で試せていなかったので今回いい勉強になりました。

終わり。

Discussion