AWSのRDSを自動停止するLambdaを作る記録
- RDSは停止しても7日経つと自動で起動してしまいます。
- このサイトのやり方でRDSを自動停止するLambdaを作る
- 実行権限を作成する
- 自動停止の対象と停止時間を設定できるようにするためにRDSにAutoStopタグを追加します。
- Lambda関数を作成します。
- Lambda関数を実行するトリガーを作成します。
- 失敗したこと
RDSは停止しても7日経つと自動で起動してしまいます。
DB インスタンスは最大 7 日間停止できます。DB インスタンスを手動で起動しないで 7 日間が経過すると、DB インスタンスは自動的に起動します。
一時的に Amazon RDS DB インスタンスを停止する - Amazon Relational Database Service
なので、RDSを監視して自動で停止してほしいです。
案件が動いていない時は停止していてほしいです。ちょいちょい確認して停止するのは面倒くさいです。
そこで、世の中の知識を利用して自動停止できるようにします。
このサイトのやり方でRDSを自動停止するLambdaを作る
実行権限を作成する
Lambdaの実行権限を作成する - ponsuke_tarou’s blog
上記を参考にIAMのポリシーを作成し、ロールを作成します。
ポリシーに設定する内容は以下になります。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "rds:StopDBInstance", "rds:ListTagsForResource" ], "Resource": "arn:aws:rds:*:*:db:*" }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "rds:DescribeDBInstances", "logs:CreateLogGroup", "logs:PutLogEvents" ], "Resource": "*" } ] }
自動停止の対象と停止時間を設定できるようにするためにRDSにAutoStopタグを追加します。
- AWSマネジメントコンソールにある[RDS] > サイドメニューの[データベース] > 対象となるRDSを選択して詳細画面を開きます。
- [タグ]タブ > [追加]ボタンで[タグの追加] ウィンドウを表示します。
- [タグキー]に「AutoStop」と[値]に「自動停止したい時間」を入力します。
- [追加]を選択します。
Lambda関数を作成します。
- AWSのコンソールにある[Lambda] > [関数の作成]ボタンで画面を開きます。
- 必要な項目を入力後に[関数の作成]ボタンで関数を作成します。
- オプション : [一から作成]
- 関数名 : 任意の関数名
- ランタイム : Python3.8
- 実行ロール : 既存のロールを使用する
- 作成したロールを選択します。
関数を実装します。
[関数コード] > [lambda_function.py]に以下のコードを張り付けて[保存]ボタンで保存します。
# -*- coding: utf-8 -*- from __future__ import print_function import sys import json import boto3 import datetime REGION_NAME = "ap-northeast-1" print("Loading function") rds = boto3.client('rds') def get_auto_stop_tag(instance_arn): instance_tags = rds.list_tags_for_resource(ResourceName=instance_arn) tag_list = instance_tags['TagList'] tag = next(iter(filter(lambda tag: tag['Key'] == 'AutoStop' and (tag['Value'] is not None and tag['Value'] != ''), tag_list)), None) return tag def get_auto_stop_time(auto_stop_tag): today = datetime.datetime.now() param_day = today.day auto_stop_val = auto_stop_tag["Value"].split(":") # 設定時刻が8:59以前である場合、GMT変換時に前日にならないよう日付を1日進めておく if int(auto_stop_val[0]) < 9: param_day = param_day + 1 # タグに指定されたGMTでの時間 tag_time = datetime.datetime(today.year, today.month, param_day, int(auto_stop_val[0]), int(auto_stop_val[1])) gmt_time = tag_time + datetime.timedelta(hours=-9) return gmt_time def lambda_handler(event, context): print("Received event: " + json.dumps(event, indent=2)) # インスタンスを取得 instances = rds.describe_db_instances() if len(instances['DBInstances']) > 0: # インスタンスを順番に処理していく for instance in instances['DBInstances']: if instance['DBInstanceStatus'] == 'available': instance_arn = instance['DBInstanceArn'] print(instance_arn + 'は、稼働中です。') tag = get_auto_stop_tag(instance_arn) print('取得したAutoStopタグ:' + str(tag)) if tag: # AutoStopタグが指定されているインスタンスを処理する reference_time = get_auto_stop_time(tag) print('AutoStopタグに指定された時間(GMT)は、' + reference_time.strftime('%Y-%m-%dT%H:%M:%SZ') + 'です。') event_time = datetime.datetime.strptime(event["time"], '%Y-%m-%dT%H:%M:%SZ') reference_time_from = reference_time + datetime.timedelta(minutes=-5) reference_time_to = reference_time + datetime.timedelta(minutes=5) print('処理時間(' + event_time.strftime('%Y-%m-%dT%H:%M:%SZ') + ')が、' + \ reference_time_from.strftime('%Y-%m-%dT%H:%M:%SZ') + 'から' + reference_time_to.strftime('%Y-%m-%dT%H:%M:%SZ') + 'だったら停止します。') # AutoStopタグに指定された時刻の前後5分以内であればインスタンス停止する if reference_time_from <= event_time and event_time <= reference_time_to: rds.stop_db_instance(DBInstanceIdentifier=instance['DBInstanceIdentifier']) print(instance_arn + 'を停止しました。')
実装の参考サイト
AWS Lambda+Python3で複数のRDSを起動停止 | LINKBAL Blog
Lambdaの関数を利用してRDSを長期間停止する - Qiita
DescribeDBInstances - Amazon Relational Database Service
DB instance status - Amazon Relational Database Service
AWS Lambda Pythonをローカル環境で実行 | DevelopersIO
関数を動かしてみます。
- [テスト]ボタンで[テストイベントの設定]ダイアログを表示します。
- [新しいテストイベントの作成]を選択します。
- [イベントテンプレート]で「Hello World」を選択します。
- 引数の欄に「{"time": "2019-12-04T10:02:00Z"}」を入力します(時間はAutoStopタグに指定した時間の近い時間)。
- [イベント名]に任意の名前を設定して[作成]ボタンで保存します。
- [テスト]ボタンで関数を実行します。
- 実行結果とログを確認して、「成功」になるまでソースや設定の見直しをします。
Lambda関数を実行するトリガーを作成します。
CloudWatch EventsをトリガーとしてLambda関数を実行できるようにするためにイベントを登録します。
CloudWatch Eventsにイベントを登録します。
- AWSのコンソールにある[Lambda] > 作成したLambda関数を選択して詳細画面を開きます。
- [Designer]にある[トリガーを追加]ボタンでトリガーの設定画面を開きます。
- プルダウンからCloudWatch Eventsを選択します。
- [ルール]で「新規ルールの作成」を選択し、[ルール名(必須)][ルールの説明(任意)]を入力します。
- [ルールタイプ]で「スケジュール式」を選択し、[スケジュール式]に以下のサイトを参考にスケジュールを設定します。
失敗したこと
CloudWatch Eventsの[スケジュール式]の書き方を間違えた
Cron式で時間に「10-24」と指定したところエラーになりました。24時はだめなんですね。
Parameter ScheduleExpression is not valid. (Service: AmazonCloudWatchEvents; Status Code: 400; Error Code: ValidationException; Request ID: c.....)
Lambda関数でCloudWatch Logsに書き込むための権限がなかった。
Lambda関数を実行するCloudWatch Eventsを登録してからLambda関数の[モニタリング]タブを見てみるとメッセージが表示されていました。
メッセージにある[AWSLambdaBasicExecutionRole]の権限を追加します。
アクセス権限が見つかりません お使いの関数には、Amazon CloudWatch Logs に書き込むためのアクセス許可がありません。ログを閲覧するには、その実行ロールに AWSLambdaBasicExecutionRole の管理ポリシーを追加します。IAM コンソールを開きます。
- AWSマネジメントコンソールにある[IAM] > サイドメニューの[ポリシー] > [AWSLambdaBasicExecutionRole]を検索して詳細画面を表示します。
- [アクセス権] > [JSON]で権限の詳細を表示します。
- [Statement]に記載されている内容以下をコピーします。
- サイドメニューの[ポリシー] > Lambda関数に設定したポリシーを検索して詳細画面を表示します。
- [アクセス権] > [JSON] > [ポリシーの編集]ボタンで編集画面を表示します。
- コピーした内容を[Action]に追記します。
- [ポリシーの確認]ボタン > [変更の保存]ボタンで保存します。
describe_db_instancesを実行する権限がなかった。
Lambda関数を実行したらエラーになりました。ポリシーの[Action]に「rds:DescribeDBInstances」を追加しました。
{ "errorMessage": "An error occurred (AccessDenied) when calling the DescribeDBInstances operation: User: arn:aws:sts::8xxxxxxxxxxx:assumed-role/{ロール名}/{Lambda関数名} is not authorized to perform: rds:DescribeDBInstances", "errorType": "ClientError", "stackTrace": [ " File \"/var/task/lambda_function.py\", line 27, in lambda_handler\n instances = rds.describe_db_instances(Filters=[{\"Name\": \"DBInstanceStatus\", \"Values\": [\"available\"]}])\n", " File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n return self._make_api_call(operation_name, kwargs)\n", " File \"/var/runtime/botocore/client.py\", line 661, in _make_api_call\n raise error_class(parsed_response, operation_name)\n" ] }
「rds:DescribeDBInstances」はリソースを限定できなかった。
以下のように単純に「rds:DescribeDBInstances」を追加したところ同じエラーになりました。
一部のRDSの情報の閲覧だけできるポリシーは作れなかった話 - Qiitaを読んでリソースを限定できないことを知りました。
...省略... "Action": [ "rds:StopDBInstance", "rds:DescribeDBInstances" ], "Resource": "arn:aws:rds:*:*:db:*" ...省略...