サイトアイコン 協栄情報ブログ

EventBridgeとLambdaを使用してRDS自動停止を作成する


この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので十分ご注意ください。

はじめに

以前RDSを停止するのを忘れていたため、RDS自動停止機能を実装しました。
EventBridgeとLambda関数(Boto3)を使って指定時間に起動中のRDSリソース(RDSインスタンス,RDSクラスター)自動停止機能を構築していきます。

Lambdaとは

AWS Lambda は、サーバーをプロビジョニングまたは管理せずにコードを実行できるようにするコンピューティングサービスです。Lambda は可用性の高いコンピューティングインフラストラクチャでコードを実行し、コンピューティングリソースに関するすべての管理を行います。これには、サーバーおよびオペレーティングシステムのメンテナンス、容量のプロビジョニングおよび自動スケーリング、さらにログ記録などが含まれます。Lambda を使用すると、実質どのようなタイプのアプリケーションやバックエンドサービスに対してもコードを実行できます。必要なのは、Lambda がサポートするいずれかの言語でコードを指定することだけです。

引用:AWS Lambda

Boto3とは

AWS SDK for Python (Boto3) を使用すると、AWS の使用を迅速に開始できます。Boto3 を使用することで、Python のアプリケーション、ライブラリ、スクリプトを AWS の各種サービス(Amazon S3、Amazon EC2、Amazon DynamoDB など)と容易に統合できます。

引用:AWS SDK for Python (Boto3)

EventBridgeとは

EventBridge は、イベントを使用してアプリケーションコンポーネントを接続するサーバーレスサービスです。これにより、スケーラブルなイベント駆動型アプリケーションを簡単に構築できます。これを使用して、自社開発アプリケーション、AWS サービス、サードパーティソフトウェアなどのソースから組織全体のコンシューマアプリケーションにイベントをルーティングできます。EventBridge では、イベントの取り込み、フィルタリング、変換、配信をシンプルかつ一貫性のある方法で行うことができるため、新しいアプリケーションをすばやく構築できます。

引用:Amazon EventBridge

RDSクラスターとは

あるマルチ AZ DB クラスターの配置は、2 つの読み取り可能なスタンバイ DB インスタンスを持つ Amazon RDS の高可用性の配置モードです。マルチ AZ DB クラスターには、同じAWSのリージョンに 3 つの別々のアベイラビリティーゾーンに 1 つのライター DB インスタンスと 2 つのリーダー DB インスタンスがあります。マルチ AZ DB クラスターは、マルチ AZ DB インスタンスの配置と比較して、高可用性、読み取りワークロードの容量の増加、および書き込みレイテンシーの低減を提供します。

引用:マルチ AZ DB クラスター配置

RDSインスタンスとは

DB インスタンスはクラウドで実行される独立したデータベース環境です。これは、Amazon RDS の基本的な構成要素です。DB インスタンスには、ユーザーが作成した複数のデータベースを含めることができ、スタンドアロンデータベースインスタンスにアクセスする場合と同じクライアントツールやアプリケーションを使用してアクセスできます。

引用:Amazon RDS DB インスタンス

LambdaでRDS自動停止を作成

構成図

Lambdaの作成

Lambda関数の全体のソースコードです。

# coding:utf-8

import json
import boto3

def rds_db_instance_all_region_stop(region):
    """
    regionを受け取り、全リージョンにあるRDSインスタンスを停止させ、、
    停止していないなら現在の状態を出力する。
    """
    rds = boto3.client('rds', region_name=region)

    all_db_instance = rds.describe_db_instances().get('DBInstances')

    for each_db_instance in all_db_instance:

        db_instance_identifier = each_db_instance['DBInstanceIdentifier']

        db_instance_status = each_db_instance['DBInstanceStatus']

        if db_instance_status == "available":
            rds.stop_db_instance(DBInstanceIdentifier=db_instance_identifier)
            print(f"RDS DBInstanceを停止しました。 InstanceID:{db_instance_identifier}")
        else:
            print(f"{db_instance_identifier} のインスタンスの状態は {db_instance_status} です")

def rds_db_cluster_all_region_stop(region):
    """
    regionを受け取り、全リージョンにあるRDSクラスターを停止させ、、
    停止していないなら現在の状態を出力する。
    """
    rds = boto3.client('rds', region_name=region)

    all_db_cluster = rds.describe_db_clusters().get('DBClusters')

    for each_db_cluster in all_db_cluster:

        db_cluster_identifier = each_db_cluster['DBClusterIdentifier']

        db_cluster_status = each_db_cluster['Status']

        if db_cluster_status == "available":
            print(f"RDS DBClusterを停止しました。 ClusterID:{db_cluster_identifier}")
            rds.stop_db_cluster(DBClusterIdentifier=db_cluster_identifier)
        else:
            print(f"{db_cluster_identifier} インスタンスの状態は{db_cluster_status} です")

def lambda_handler(event, context):

    ec2 = boto3.client('ec2')
    regions = list(map(lambda x: x['RegionName'], ec2.describe_regions()['Regions']))

    for region in regions:

        rds_db_cluster_all_region_stop(region)

        rds_db_instance_all_region_stop(region)

    print("-------Proccessing complete!----------")

関数def rds_db_instance_all_region_stopの説明

初めにdef rds_db_instance_all_region_stopの説明をしたいと思います。
regionを受け取り、全リージョンにあるインスタンスを停止させ、
停止してないなら現在の状態を出力しています。
関数のソースコードの下に詳しく書いてありますので参照ください。

def rds_db_instance_all_region_stop(region):

    rds = boto3.client('rds', region_name=region)

    all_db_instance = rds.describe_db_instances().get('DBInstances')

    for each_db_instance in all_db_instance:

        db_instance_identifier = each_db_instance['DBInstanceIdentifier']

        db_instance_status = each_db_instance['DBInstanceStatus']

        if db_instance_status == "available":
            rds.stop_db_instance(DBInstanceIdentifier=db_instance_identifier)
            print(f"RDS DBInstanceを停止しました。 InstanceID:{db_instance_identifier}")
        else:
            print(f"{db_instance_identifier} のインスタンスの状態は {db_instance_status} です")

関数rds_db_cluster_all_region_stopの説明

次にrds_db_cluster_all_region_stopの説明をしたいと思います。
こちらも同様にregionを受け取り、全リージョンにあるクラスターを停止させ、
停止してないなら現在の状態を出力しています。
同様に関数のソースコードの下に詳しく書いてありますので参照ください。

def rds_db_cluster_all_region_stop(region):

    rds = boto3.client('rds', region_name=region)

    all_db_cluster = rds.describe_db_clusters().get('DBClusters')

    for each_db_cluster in all_db_cluster:

        db_cluster_identifier = each_db_cluster['DBClusterIdentifier']

        db_cluster_status = each_db_cluster['Status']

        if db_cluster_status == "available":
            print(f"RDS DBClusterを停止しました。 ClusterID:{db_cluster_identifier}")
            rds.stop_db_cluster(DBClusterIdentifier=db_cluster_identifier)
        else:
            print(f"{db_cluster_identifier} インスタンスの状態は{db_cluster_status} です")

関数lambda_handlerの説明

最後にlambda_handlerの説明をしたいと思います。
lambda_handlerは少し特殊なので公式から引用したいと思います。

Lambda 関数ハンドラーは、イベントを処理する関数コード内のメソッドです。関数が呼び出されると、Lambda はハンドラーメソッドを実行します。ハンドラーによってレスポンスが終了するか、レスポンスが返ったら、別のイベントを処理できるようになります。

引用:Python の Lambda 関数ハンドラー

def lambda_handler(event, context):

    ec2 = boto3.client('ec2')
    regions = list(map(lambda x: x['RegionName'], ec2.describe_regions()['Regions']))

    for region in regions:
        rds_db_cluster_all_region_stop(region)

        rds_db_instance_all_region_stop(region)

    print("-------Proccessing complete!----------")

引用:Python の Lambda 関数ハンドラー 命名

引数のeventは

イベントオブジェクト です。イベントは、処理する Lambda 関数のデータを含む JSON 形式のドキュメントです。 Lambda ランタイム は、イベントをオブジェクトに変換し、それを関数コードに渡します。これは通常 Python dict タイプです。また list、str、int、float、または NoneType タイプを使用できます。
イベントオブジェクトには、呼び出し元のサービスからの情報が含まれます。関数を呼び出すときは、イベントの構造とコンテンツを決定します。AWS のサービスで関数を呼び出す場合、そのイベントはサービスによって定義されます。

contextは

2番目の引数は コンテキストオブジェクト です。コンテキストオブジェクトは、ランタイムに Lambda によって関数に渡されます。このオブジェクトは、呼び出し、関数、およびランタイム環境に関する情報を示すメソッドおよびプロパティを提供します。

引用:Python の Lambda 関数ハンドラー 仕組み

EventBridgeの作成

次にEventBridgeを作成していきます。

※日本時間と9時間ずれているので13に設定しています。
cron式は毎日22時に実行されるように設定しています。

cron式にはワイルドカードがあり、説明は以下となっています。

  • ワイルドカード [,] (カンマ) には追加の値が含まれます。月フィールドの、「JAN,FEB,MAR」は、1 月、2 月、3 月を含みます。
  • ワイルドカード [-] (ダッシュ) は範囲を指定します。日フィールドの、「1-15」は、指定した月の 1 日から 15 日を含みます。
  • ワイルドカード [] (アスタリスク) にはフィールドのすべての値が含まれます。時間フィールドの、 にはすべての時間が含まれています。[*] を日および曜日フィールドの両方に使用することはできません。一方に使用する場合は、もう一方に [?] を使用する必要があります。
  • ワイルドカード [/] (スラッシュ) で増分を指定します。分フィールドで、「1/10」と入力して、その時間の最初の分から始めて、10 分毎を指定できます (11 分、21 分、31 分など)。
  • ? (疑問符) ワイルドカードは任意を意味します。[日] フィールドに 7 と入力し、7 日が何曜日であってもかまわない場合、[曜日] フィールドに ? を入力できます。
  • Day-of-month フィールドまたは Day-of-week フィールドの、ワイルドカード L は月または週の最終日を指定します。
  • Day-of-month フィールドのワイルドカード W は、平日を指定します。Day-of-month フィールドで、3W は月の 3 日目に最も近い平日を指定します。

引用:ルールのスケジュール式 cron式

挙動の確認

挙動の確認のためRDSクラスターとRDSインスタンスを起動しておきます。

Lambda関数を実行します。

Lambda関数実行後、しっかりRDSインスタンスとクラスターが停止しているかを確認します。

しっかり停止しています。

次はEventBridgeがしっかり機能しているか確認します。
※テストのためCron式を19:40に指定しています
上と同様にRDSとクラスターを起動しておきます。
EventBridgeで指定した時刻になるとしっかり停止されることを確認します。

CloudTrailでも指定時間に停止ログが確認できました。

まとめ

Lambda関数とEventBridgeで指定時間で自動実行できる便利なサービスというのを確認しました。
Lambda関数を作成時にboto3の知識とpythonの理解が深まりました。
また機会があればLambda関数とEventBridgeを使って何か作ろうと思います。

モバイルバージョンを終了