Lambdaを使ってALBに特定の時間でメンテナンスページを表示させる

ALBでWebページを公開している時に、特定のメンテナンス時間を設けたい場合があります。
例えば、月次第1日曜日 4:00~6:00をメンテナンス時間としてサーバの再起動をしたり、パッチ適用を行ったりということがしたいのですが、ALBには特定の時間でメンテナンスページを表示するような機能がありません。

機能がないのであればLambdaで実装してみようと思い、作成してみました。
多少の制限がありますが、簡単なメンテナンスページを表示する程度であれば十分に要望を満たすことができると思います。

使用例

このページのLambdaを使用することで以下のようなことが実現できます。
  • 特定の時間に自動でALBからメンテナンスページの表示(Webページ閉局)
  • 特定の時間に自動でALBから通常のWebページの表示(Webページ開局)
特定の時刻で閉局・開局させる場合は、Amazon EventBridgeの作成が必要となります。
※AWS EventBridgeの解説は、本ページでは省略いたします。
 作成方法は、こちらをご確認下さい。

構成図

本ページにて紹介するLambdaは、ALBのリスナーにHTMLの固定レスポンスを応答するルールを追加します。
優先度を最も高い1として全アクセスを対象とするルールとなるため、ALBは必ずメンテナンスページを返答するような形になります。

boto3関数は、ELBの「create_rule」を使用します。
ただし、ルールの優先度1は基本的に使われていることがほとんどなので、ルール追加の前に既存ルールの優先度を1つずつずらしてあげる必要があります。
そのために使用するのが、ELBの「describe_rules」と「set_rule_priorities」です。
既存のルールを参照し、1つずらした優先度で設定を行うことができます。

また、そのルールが開局中なのか閉局中なのかがわかるように「add_tags」にてMaintenanceModeタグを設定しています。
このタグがActiveになっている場合は、閉局中となります。
「describe_tags」にてタグ状態のチェック処理も入っており、閉局中に再度実行しても何も処理を行わずスキップするようにしています。
また、閉局をしたら開局も必要となります。
メンテナンスページから通常のWebアクセスが可能となるように、閉局のために作ったルールを削除します。
メンテナンスページを固定でレスポンスするルールを削除することで、通常のアクセスに応答するルールにアクセスが流れるようになります。
boto3関数は、ELBの「delete_rule」を使用します。
また、「describe_tags」にてタグ状態のチェックして、閉局中の場合のみ開局処理をするように設定してあります。

Lambdaソースコード

以下は、メンテナンスページ表示用のソースコードです。
メンテナンスページを実装したいリスナーARNと表示するメンテナンスページのHTMLを入力すれば使用可能です。
import boto3
import json

elb_client = boto3.client('elbv2')
def lambda_handler(event, context):
    listener_arn = ''
    rule_list = []
    MainteFlag = 0
    
    #ALBリスナーのタグ検索
    response = elb_client.describe_tags(ResourceArns = [listener_arn])
    
    #MaintenanceModeタグの確認 → なし or Active以外の場合はメンテナンス有効化処理フラグ設定 
    if not "MaintenanceMode" in [tag_key['Key'] for tag_key in response["TagDescriptions"][0]["Tags"]]:
        MainteFlag = 1
    for tag in response["TagDescriptions"][0]["Tags"]:
        if tag["Key"] == "MaintenanceMode":
            if tag["Value"] != "Active":
                MainteFlag = 1
    
    #メンテナンスモード有効化処理            
    if MainteFlag == 1:
        #リスナールールのプライオリティチェック
        response = elb_client.describe_rules(ListenerArn=listener_arn)
        for rule in response["Rules"]:
            if rule["Priority"] != "default":
                rule_dict = {}
                rule_dict["RuleArn"] = rule["RuleArn"]
                rule_dict["Priority"] = int(rule["Priority"])
                rule_list.append(rule_dict)
        rule_list = sorted(rule_list, key=lambda x: x.get('Priority'))
        
        #現状のルールをプライオリティ2以降に設定
        for i in range(len(rule_list)):
            rule_list[i]["Priority"] = i+2
        elb_client.set_rule_priorities(RulePriorities=rule_list)
        
        #プライオリティ1でメンテナンスページの表示ルール作成
        elb_client.create_rule(
            ListenerArn = listener_arn,
            Conditions = [
                {
                    'Field': 'path-pattern',
                    'PathPatternConfig': {
                        'Values': [
                            '/*',
                        ]
                    }
                },
            ],
            Priority = 1,
            Actions = [
                {
                    'Type': 'fixed-response',
                    'FixedResponseConfig': {
                        'ContentType': 'text/html',
                        'StatusCode': '200',
                        'MessageBody': """"""
                    }
                }
            ]
        )
        
        #MaintenanceModeタグをActiveで設定
        elb_client.add_tags(
            ResourceArns = [listener_arn],
            Tags = [
                {
                    'Key': 'MaintenanceMode',
                    'Value': 'Active'
                }
            ]
        )
    
    return 0
以下は、メンテナンスページ解除用のソースコードです。
import boto3

elb_client = boto3.client('elbv2')
def lambda_handler(event, context):
    listener_arn = ''
    
    response = elb_client.describe_tags(ResourceArns=[listener_arn])
    for tag in response["TagDescriptions"][0]["Tags"]:
        if tag["Key"] == "MaintenanceMode":
            if tag["Value"] == "Active":
                response = elb_client.describe_rules(ListenerArn=listener_arn)
                for rule in response["Rules"]:
                    if rule["Priority"] == "1":
                        elb_client.delete_rule(RuleArn=rule["RuleArn"])
                        elb_client.add_tags(
                            ResourceArns=[listener_arn],
                            Tags=[
                                {
                                    'Key': 'MaintenanceMode',
                                    'Value': 'Inactive'
                                }
                            ]
                        )
    return 0

▼基本設定

ALBリスナーARN*
レスポンスHTML*

権限(IAMロール設定)

Lambdaに付与するIAMロールに「elasticloadbalancing:DescribeRules」、「elasticloadbalancing:CreateRule」、「elasticloadbalancing:DeleteRule」、「elasticloadbalancing:SetRulePriorities」、「elasticloadbalancing:DescribeTags」、「elasticloadbalancing:AddTags」の権限を付与してください。

解説

紹介した閉局Lambdaのフローは以下の通りです。
  1. ALBリスナーに付与されているタグ検索
  2. タグ情報をもとに閉局処理の実行有無を判定
  3. ルールの優先度整理
  4. メンテナンスページ表示ルール追加
  5. 閉局中タグ設定

今回の処理での肝は、開局処理でルール削除を実施するのでメンテナンスページを表示するルールを削除できるようにしなくてはいけない点です。
他に必要なルールを削除してしまっては大変ですから・・・。
色々と方法を考えましたが、結果としてタグでメンテナンス中かどうかの情報を持たせることにしました。
リスナーのタグに「MaintenanceMode」タグを設定し、「Active」ならメンテナンス中。
「Inactive」なら稼働中を示します。

閉局処理は稼働中の場合のみ、メンテナンスページ表示のルールを優先度1で挿入します。
メンテナンス中だった場合は、何もせずに処理をスキップさせます。

また紹介した開局Lambdaのフローは以下の通りです。
  1. ALBリスナーに付与されているタグ検索
  2. タグ情報をもとに開局処理の実行有無を判定
  3. メンテナンス中であれば、優先度1のルールを削除
メンテナンス中ではあれば、優先度1にメンテナンスページ表示のルールがあるはずなので、そのルールを削除します。
逆にメンテナンス中でないのに実行された場合は、何もせずに実行を終了させます。

使い方

今回紹介したソースコードのLambdaを作成し、AWS EventBridgeで指定した日時で実行するように設定してください。