はじめに
前回は、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を有効にしたままにするのがよいと思いますが、コスト削減の必要があり、一時的に可用性が低くなることが許容できるのであれば、定期的に無効にするような運用もアリかと思います。




