AWS Lambda関数でRDSインスタンスのMulti-AZ有効無効を切り替える
はじめに
前回は、AWS EC2インスタンスのバックアップのため、スナップショットを作成するLambda関数を紹介しました。
今回はRDSインスタンスのMulti-AZの有効、無効(Muti-AZとSingle-AZ)を切り替えるLambda関数を紹介します。
今回のLambda関数は、以下の記事で公開されていた関数を参考にさせていただきました。
(ありがとうございます!)
・boto3でRDSのwaiterを使用するとき注意したいこと – Developers.IO
https://dev.classmethod.jp/cloud/note_when_use_waiter_of_rds_in_boto3/
ユースケース
Lambda関数を紹介する前に、どういった場面でこの関数が必要となるかを考えてみます。
RDSは、Multi-AZを有効にすることで、可用性が高くなります。
万一RDSインスタンスに何らかの障害が発生した場合に、異なるアベイラビリティゾーンで起動しているスタンバイインスタンスにフェイルオーバーすることで、1分~3分程度のダウンタイムでRDBサーバーを自動復旧させることができます。
また、AWSによるメンテナンスやアップグレード、インスタンスタイプの変更の際も、ダウンタイムはフェイルオーバーにかかる時間のみの短時間となります。
とても便利な機能ですが、常に2つのインスタンスを起動していますから、RDSの料金がMulti-AZ無効(Single-AZ)のときの2倍となります。
このため、Multi-AZを有効にする時間帯を限定することで、ある程度料金を削減することができます。
たとえば「休日や夜間は障害発生時の復旧に時間がかかってもよい」ということであれば、
- 平日営業時間帯はMulti-AZを有効
- 夜間や休日はMulti-AZを無効(Single-AZ)
とするような運用方法が考えられます。
逆に「平日の障害発生時はサポート要員がいて人手で復旧できる(このとき、復旧に多少時間がかかってもよい、という前提)。夜間や休日はサポート要員がいないので、自動復旧してほしい」ということであれば、
- 平日営業時間帯はMulti-AZを無効(Single-AZ)
- 夜間や休日はMulti-AZを有効
という運用方法が考えられます。
このような場合に今回のLambda関数を使うと、Multi-AZの有効無効を、曜日や時間を指定してスケジューリングできます。
Lambda関数の仕様・設定
仕様
- RDS DBインスタンスのMulti-AZを無効(または有効)にする。
- RDS DBインスタンスのステータスが「available」以外のときや、すでにMulti-AZが無効(または有効)となっているときは何もしない(ステータスが「available」以外のときは、DBインスタンス情報の変更ができない)。
- Multi-AZを無効、有効にする関数はそれぞれ別に作成する(変数で工夫してひとつにまとめてもよいでしょう)。
- 対象となるDBインスタンス名は、Lambdaの環境変数で指定する。
- Lambda関数のランタイムはPython 3.6
Lambda関数
Multi-AZを有効・無効にする対象となるRDS DBインスタンスと同じリージョンでLambda関数を作成します。
ランタイムは、Python 3.6を指定します。
Multi-AZを無効にするLambda関数
#!/usr/bin/env python # -*- coding: utf-8 -*- # # 指定したRDSインスタンスのMulti-AZを無効にする # Python 3.6 # # Lambda関数の環境変数で以下を設定する。 # # DB_INSTANCE_IDENTIFIER: 対象とするRDSインスタンス名 # import boto3 import collections import time from botocore.client import ClientError import os DB_INSTANCE_IDENTIFIER = os.environ['DB_INSTANCE_IDENTIFIER'] rds = boto3.client('rds', os.environ['AWS_REGION']) def lambda_handler(event, context): disable_multiaz() def disable_multiaz(): description = 'db_instance: ' + DB_INSTANCE_IDENTIFIER db_infos = rds.describe_db_instances( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER )['DBInstances'] if db_infos: db_info = db_infos[0] db_multiaz = db_info['MultiAZ'] db_status = db_info['DBInstanceStatus'] # すでにMulti-AZが無効のときは、何もしない if not db_multiaz: print('Multi-AZ is already False, %s' % (description)) return # ステータスが available 以外のときは、何もしない if db_status != 'available': print('DB Instance is not available, %s' % (description)) return db_instance = _disable_multiaz(DB_INSTANCE_IDENTIFIER, description) print('disable Multi-AZ, %s' % (description)) return # RDSインスタンスのMulti-AZを無効にする # 実行エラーした場合、合計3回までリトライする def _disable_multiaz(db_instance_identifier, description): for i in range(1, 3): try: return rds.modify_db_instance( DBInstanceIdentifier=db_instance_identifier, MultiAZ=False, ApplyImmediately=True) except ClientError as e: print(str(e)) time.sleep(1) raise Exception('Cannot disable Multi-AZ, ' + description) # EOF
Multi-AZを有効にするLambda関数
#!/usr/bin/env python # -*- coding: utf-8 -*- # # 指定したRDSインスタンスのMulti-AZを有効にする。 # Python 3.6 # # Lambda関数の環境変数で以下を設定する。 # # DB_INSTANCE_IDENTIFIER: 対象とするRDSインスタンス名 # import boto3 import collections import time from botocore.client import ClientError import os DB_INSTANCE_IDENTIFIER = os.environ['DB_INSTANCE_IDENTIFIER'] rds = boto3.client('rds', os.environ['AWS_REGION']) def lambda_handler(event, context): enable_multiaz() def enable_multiaz(): description = 'db_instance: ' + DB_INSTANCE_IDENTIFIER db_infos = rds.describe_db_instances( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER )['DBInstances'] if db_infos: db_info = db_infos[0] db_multiaz = db_info['MultiAZ'] db_status = db_info['DBInstanceStatus'] # すでにMulti-AZが有効のときは、何もしない if db_multiaz: print('Multi-AZ is already True, %s' % (description)) return # ステータスが available 以外のときは、何もしない if db_status != 'available': print('DB Instance is not available, %s' % (description)) return db_instance = _enable_multiaz(DB_INSTANCE_IDENTIFIER, description) print('Enable Multi-AZ, %s' % (description)) return # RDSインスタンスのMulti-AZを有効にする # 実行エラーした場合、合計3回までリトライする def _enable_multiaz(db_instance_identifier, description): for i in range(1, 3): try: return rds.modify_db_instance( DBInstanceIdentifier=db_instance_identifier, MultiAZ=True, ApplyImmediately=True) except ClientError as e: print(str(e)) time.sleep(1) raise Exception('Cannot Enable Multi-AZ, ' + description) # EOF
実行ロール
Lambda関数に付与するIAMロールのカスタムポリシーは以下のようにします。
CloudWatch Logsのログ書き出しと、RDSインスタンスの情報取得、変更アクションを付与しています。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:GetLogEvents" ], "Resource": [ "arn:aws:logs:*:*:*" ] }, { "Effect": "Allow", "Action": [ "rds:DescribeDBInstances", "rds:ModifyDBInstance" ], "Resource": [ "*" ] } ] }
環境変数
対象とするRDSインスタンスは、Lambda関数の環境変数で指定します。
- キー: DB_INSTANCE_IDENTIFIER
- 値: <DBインスタンス名>
基本設定
メモリはデフォルトの128MBのままでOKです。
タイムアウトは、リトライをする場合はデフォルトの3秒では短かすぎてエラーすることがあるので、10秒や30秒などに変更します。
定期実行設定
定期的にMulti-AZの無効、有効を切り替えるためにこのLambda関数を実行するには、トリガーとしてCloudWatch Eventを指定して、スケジュールのルールを設定します。
以下は「日本時間の月-金の22時」に関数を実行する設定例です。
「スケジュール式」の時刻はUTCで指定することに注意します。
Multi-AZの有効チェック
設定ミスや、Lambda関数の実行エラーなど、何らかの原因で、必要なときにMulti-AZが有効になっていない可能性もゼロではありません。
念のため、Multi-AZが有効になっていることをチェックし、無効であればAmazon SNSで通知するLambda関数も用意してみました。
ステータスが「available」以外のときも通知します。
Multi-AZの有効無効を切り替えるLambda関数と同様に、対象となるDBインスタンス名は、Lambdaの環境変数で指定します。
SNSトピックは、あらかじめRDSインスタンスと同じリージョンで作成し、通知先(SNSやWebhookなど)のサブスクリプションを設定しておきます。
このときのSNSトピックのARN(arn:aws:sns:ap-northeast-1:~)はのちに必要となるので、メモしておきます。
Lambda関数
Multi-AZを有効・無効にする対象となるRDS DBインスタンスと同じリージョンでLambda関数を作成します。
ランタイムは、Python 3.6を指定します。
#!/usr/bin/env python # -*- coding: utf-8 -*- # # 指定したRDSインスタンスのMulti-AZが有効となっているかチェックする。 # 有効になっていなければ、SNSでアラート通知する。 # Python 3.6 # # 対象とするDBインスタンス名を DB_INSTANCE_IDENTIFIER で指定すること。 # SNSによる通知先トピックのARNを SNS_TARAGET_ARN で指定すること。 # # Lambda関数の環境変数で以下を設定する。 # # DB_INSTANCE_IDENTIFIER: 対象とするRDSインスタンス名 # SNS_TARAGET_ARN: SNSによる通知先トピックのARN # import boto3 import collections import time from botocore.client import ClientError import os DB_INSTANCE_IDENTIFIER = os.environ['DB_INSTANCE_IDENTIFIER'] SNS_TARAGET_ARN = os.environ['SNS_TARAGET_ARN'] rds = boto3.client('rds', os.environ['AWS_REGION']) sns = boto3.resource('sns', os.environ['AWS_REGION']) def lambda_handler(event, context): check_multiaz() def check_multiaz(): topic = sns.Topic(SNS_TARAGET_ARN) if not topic: print('SNS Topic is not exist. ARN: %s' % (SNS_TARAGET_ARN)) return subject = 'DB Instance Status Error' description = 'db_instance: ' + DB_INSTANCE_IDENTIFIER db_infos = rds.describe_db_instances( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER )['DBInstances'] if db_infos: db_info = db_infos[0] db_multiaz = db_info['MultiAZ'] db_status = db_info['DBInstanceStatus'] # ステータスが available 以外のとき if db_status != 'available': message = 'DB Instance is not available, ' + description + ', db_status: ' + db_status print(message) response = topic.publish( Subject = subject, Message = message ) print('response={}'.format(response)) return # Multi-AZが無効のとき if not db_multiaz: message = 'DB Instance is not Multi-AZ, ' + description print(message) response = topic.publish( Subject = subject, Message = message ) print('response={}'.format(response)) return print('DB Instance is available and Multi-AZ. %s' % (description)) return # EOF
実行ロール
Lambda関数に付与するIAMロールのカスタムポリシーは以下のようにします。
CloudWatch Logsのログ書き出しと、RDSインスタンスの情報取得、SNSのPublish(トピックの発行)権限を付与しています。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:GetLogEvents" ], "Resource": [ "arn:aws:logs:*:*:*" ] }, { "Effect": "Allow", "Action": [ "rds:DescribeDBInstances" ], "Resource": [ "*" ] }, { "Effect": "Allow", "Action": [ "sns:Publish" ], "Resource": [ "*" ] } ] }
環境変数
対象とするRDSインスタンスとSNS通知先を、Lambda関数の環境変数で指定します。
- キー: DB_INSTANCE_IDENTIFIER、値: <DBインスタンス名>
- キー: SNS_TARAGET_ARN、値: <SNSによる通知先トピックのARN>
基本設定
メモリはデフォルトの128MBのままでOKです。
タイムアウトは、余裕をもって10秒や30秒などに変更します。
定期実行設定
定期的にMulti-AZをチェックするためにこのLambda関数を実行するには、トリガーとしてCloudWatch Eventを指定して、スケジュールのルールを設定します。
たとえば、平日月-金の8時から22時はMulti-AZを有効、それ以外は無効とする場合、平日月-金の9時にチェックするようにします。
以下は「日本時間の月-金の9時」に関数を実行する設定例です。
「スケジュール式」の時刻はUTCで指定することに注意します。
実行結果
このLambda関数を実行し、RDSインスタンスのMulti-AZが無効、もしくはステータスが「available」以外の場合は、指定したSNSサブスクリプションに通知されます。
サブスクリプションがEメールの場合は、以下のようなメールが届きます。
- Subject
DB Instance Status Error - Message
DB Instance is not Multi-AZ, db_instance: <DBインスタンス名>(Multi-AZが無効の場合)
DB Instance is not available, db_instance: <DBインスタンス名>(ステータスが available 以外の場合)
通知が届いた場合は、とりいそぎ、Multi-AZを手動で有効として、なぜ有効になっていないのか、CloudWatchのLambda関数実行ログなどで調査するとよいでしょう。
おわりに
AWS RDSインスタンスのMulti-AZの有効、無効(Muti-AZとSingle-AZ)を切り替えるLambda関数を紹介しました。
本番運用であれば、Multi-AZを有効にしたままにするのがよいと思いますが、コスト削減の必要があり、一時的に可用性が低くなることが許容できるのであれば、定期的に無効にするような運用もアリかと思います。
(関連記事)
・AWS Lambda関数でEC2のスナップショットを作成する~対象はEBSボリュームのタグで指定
https://inaba-serverdesign.jp/blog/20180330/aws_ec2_create_snapshot_lambda.html