🚀

【AWS】Step Functions + EventBridge + Lambdaで、RDS/EC2停止を自動化する

2022/04/21に公開


はじめに

ご覧いただきありがとうございます。阿河です。

AWSを運用する上で「RDSインスタンスを一時的に停止させておきたい。・・でも勝手に自動起動してしまう。いちいち手動で停止させるの面倒だよな・・」ってよくある悩みだと思います。

DBインスタンスは最大7日間停止できますが、7日が経過すると自動起動します。

また必要なときだけ起動するという運用をしている場合、EC2インスタンスをうっかり停止し忘れて無駄なコストが発生する可能性があります。

今回は指定したRDSインスタンス/EC2インスタンスを自動停止させる仕組みを作っていこうと思います。

対象者

  • AWSを運用中
  • 常時稼働が必要ないRDSやEC2リソースを保有している
  • コストを少しでも削減したい
  • プログラミングはほとんど触れたことないけど、この機会に触っていたい
  • 言語はPythonを使用
  • 簡単な自動処理の仕組みを作りたい

概要

(★)がついているところは、手を動かして頂く項目です。

  1. 今回のハンズオン構成
  2. IAMポリシーの作成(★)
  3. IAMロールの作成(★)
  4. VPCエンドポイントの作成(★)
  5. RDS自動停止用のLambda関数を作成(★)
  6. EC2自動停止用のLambda関数を作成(★)
  7. StepFunctionsのステートマシンを作成(★)
  8. EventBridgeでステートマシンを定期実行させる(★)
  9. 検証(★)

事前準備

  • AWSアカウント作成
  • AdministratorAccessを付与したIAMユーザーの作成
  • VPCの作成
  • Public Subnet(Internet Gatewayへのルートを用意)
  • Public SubnetにEC2を作成
  • Private Subnet(local宛てのルートのみ)
  • Private SubnetにRDSを作成

1.今回のハンズオン構成

まずは今回のハンズオンで作成する構成を紹介します。

  • VPC外のLabmdaからAPIを利用して
     - VPC内のPrivate SubnetにあるRDSを停止させる
     - VPC内のPublic SubnetにあるEC2を停止させる
  • VPC外部からPrivate Subnetにアクセスするために、VPCエンドポイントを用意する
  • 2つのLambdaを同時並行で処理するために、StepFunctionsを利用する
  • StepFunctionsで作成したワークフローを、EventBridgeを使って定期実行させる

2. IAMポリシーの作成

では手を動かしましょう!

まずLambdaにRDS/EC2を操作する権限を与える必要があります。
そのためには「RDS/EC2を操作する権限を与えた」IAMロールを作成し、Lambdaがそのロールを使用する必要があります。

IAMロールがピンとこない方は、帽子をイメージしましょう。
帽子(権限をカスタマイズできる)をかぶると、その帽子に備わった特殊能力(権限)が使えます。
もちろん帽子を脱ぐと、特殊能力は使えません。
まず帽子につける特殊能力(権限)自体を作成します。

RDSとEC2それぞれに、ポリシーを作成します。

(マネジメントコンソールでの操作) IAM⇒ポリシー⇒ポリシーの作成⇒JSONタブ

JSONの入力フォームに下記をコピペしましょう。

RDS用ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "rds:StartDBCluster",
                "rds:StopDBCluster",
                "rds:ListTagsForResource",
                "rds:DescribeDBInstances",
                "rds:StopDBInstance",
                "rds:DescribeDBClusters",
                "rds:StartDBInstance",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcs"
            ],
            "Resource": "*"
        }
    ]
}

RDSを操作する権限を付与しました。
ポリシー名は適当な名前でOKです。

次にEC2操作用のポリシーを作成します。

EC2用ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:Describe*",
                "ec2:Start*",
                "ec2:Stop*"
            ],
            "Resource": "*"
        }
    ]
}

EC2の操作権限を与えました。

3. IAMロールの作成

2では帽子(ロール)につける権限の作成を行いました。
今度は帽子本体を作りましょう。

RDS用とEC2用それぞれに作成します。

RDS用ロール

(マネジメントコンソールでの操作) IAM⇒ロール⇒ロールの作成

信頼されたエンティティを選択では「AWSのサービス⇒Lambda」を選択します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

まずLambdaがロールを使用できるようになります。

次にロールに3つの権限を与えます。

  • 2で作成したRDS用のポリシー
  • AWSLambdaVPCAccessExecutionRole
  • AWSStepFunctionsConsoleFullAccess

ここではRDSの停止権限以外に、LambdaとStepFunctionsの権限も与えています。
3つの権限の付与ができたら、ロールを作成ボタンをクリックする。

EC2用ロール

RDS用ロールと作成方法は同じです。
ただし与える権限を以下に変更します。

  • 2で作成したEC2用のポリシー
  • AWSLambdaVPCAccessExecutionRole
  • AWSStepFunctionsConsoleFullAccess

以上で、権限まわりの準備は完了しました。

4. VPCエンドポイントの作成

VPCエンドポイントは、VPC外のAWSサービスと VPC内のAWSサービスをプライベート接続できるようにするコンポーネントになります。

RDSはプライベートサブネットにあるため、今のままでは外部から接続ができません。
VPCエンドポイントを作成することで、VPC外サービス(Lambda)からVPC内サービス(RDS)に通信ができるようになります。

(マネジメントコンソール上の操作) VPC⇒エンドポイント

まずサービスカテゴリーで「AWSのサービス」を選択します。

サービスの項目では、lambdaを選択します。

サービスのフィルターに「labmda」と入力すると、「com.amazonaws.ap-northeast-1.lambda」(東京リージョンであれば)がフィルタリングされるので、そちらを選択します。

VPCの設定では該当のVPCを選択し、必ず「DNS名を有効化」にチェックを入れておいてください。
AZの設定では、プライベートサブネットを選択しましょう。
VPCエンドポイントポリシーは、「フルアクセス」を選択。

上記の内容で設定を行いましょう。
これでVPC外部のLambdaからAPIを利用して、プライベートサブネットのRDSを停止させるための通信手段を確保しました。

5. RDS自動停止用のLambda関数を作成

(マネジメントコンソール上の操作) Lambda⇒関数⇒関数の作成

まず下記の設定で、関数自体の作成を行います。

  • オプション: 一から作成
  • ランタイム: Python
  • アーキテクチャー: x86_64
  • デフォルトの実行ロール: 基本的な Lambda アクセス権限で新しいロールを作成

設定が終わったら、関数を作成。

作成したLambda関数で、3か所の設定を行う。

ロールの付与/タイムアウト

(マネジメントコンソール上の操作)設定タブ⇒アクセス権限⇒一般設定⇒編集

  • 実行ロールで、3で作成したRDS用ロールを選択。
  • タイムアウトを30秒に変更。

コードの記述

(マネジメントコンソール上の操作)コード⇒コードソース

コードの入力フォームに、下記のコードを入力してください。
DBInstanceIdentifier='~' の部分には、停止したいRDSの識別子を入力してください。

import boto3
import json

def lambda_handler(event,context):
    
    rds = boto3.client('rds')
    db = rds.describe_db_instances(DBInstanceIdentifier='test-rds-stop-db')
    
    response = db['DBInstances']
    for instance in response:
        status = instance['DBInstanceStatus']
        print(status)
        
        if status == 'available':
            
            rds.stop_db_instance(DBInstanceIdentifier='test-rds-stop-db')
            
        else:
            print('RDS already stop')

コードを記述する際は、boto3のAPIリファレンスを参照します。
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rds.html

GoogleClomeなどのブラウザで「boto3」と入力してみましょう。
AWS SDK for Python(Boto3)のページに入り、APIリファレンスを選択。
今回はRDSに関わるAPIなので、サービス一覧からRDSを探して、該当ページに飛びます。

メソッドの一覧が出てくるので、該当しそうなメソッドに当たりをつけます。
今回は「DBインスタンスを停止させたい」ので、Stop_db_instance()の場所に飛びます。

あとはドキュメントにコードの記載例や各パラメータの説明等が載っているので、参照してください。
簡単にコードの説明を補足させて頂きます。

#クライアントの定義
rds = boto3.client('rds')

#RDSの識別子を指定して、指定のRDBインスタンスの情報をJSON形式で取得
db = rds.describe_db_instances(DBInstanceIdentifier='test-rds-stop-db')

#JSON形式の配列から、最初の要素であるDBInstacesの値を抽出。DBインスタンスの情報を取得
response = db['DBInstances']

#要素を回して、DBInstaceStatusの値を取得
for instance in response:
status = instance['DBInstanceStatus']
print(status)

#もし利用可能な状態なら、RDSを停止させる
if status == 'available':
rds.stop_db_instance(DBInstanceIdentifier='test-rds-stop-db')

#もし利用可能な状態でないなら、printだけ実行する
else:
print('RDS already stop')

RDS用のLambda作成は以上です。

試しにRDSを停止できるか確認します。

(マネジメントコンソール上の操作) コード⇒テスト⇒テストイベント
新しいイベントを作成。hello-worldテンプレートをそのまま使う。

作成したテストイベントでテストを実行する。
Testボタンをクリック。

RDSのページに飛ぶと、指定したRDSインスタンスの停止が始まっていることが分かります。

6.EC2自動停止用のLambda関数を作成

次はEC2自動停止用のLambda関数を作成します。
基本的にRDS用のLambda関数と設定フローは同じなので、下記手順で設定を行ってください。

  • 関数の作成
  • EC2用のロールを設定
  • タイムアウトの設定
  • コードの記述

コードは下記をコピペしてください。
EC2のインスタンスIDは、停止させたいEC2のインスタンスIDに変更してください。

以下はAWSの公式ドキュメントを参考に作成したサンプルコードです。

import boto3

instances = ['**EC2のインスタンスID**']
client = boto3.client('ec2')


def lambda_handler(event,context):
    
   client.stop_instances(InstanceIds=instances)

EC2停止用のLambda作成は以上です。

試しにTestボタンをクリックして、Lambdaを実行してみましょう。
EC2の停止が始まれば、設定内容に間違いはありません。

7.StepFunctionsのステートマシンを作成

次はStepFunctionsの設定です。
このサービスを使うと・・・

複数のサービスを

  • 同期実行させる
  • 並列実行させる
  • 条件分岐させる

一言でいうと「複数のAWSサービスを、パズルのように組み合わせる」ことで、処理のタイミングをコントロールすることが可能です。

今回実現したいことは

  • ステートマシンを実行
  • 「RDS用のLambda」「EC2用のLabmda」の2つが同時並行で実行を行われるようにする

言葉で説明されても分かりづらいと思うので、設定を行ってみましょう。

(マネジメントコンソール上の操作) StepFunctions⇒ステートマシン⇒ステートマシンの作成

作成方法: ワークフローを視覚的に設計
タイプ: 標準

ワークフローの設計画面が表示されます。

GUIで視覚的にワークフローを作成できます。

今回は並列処理を実行したいので、フロー⇒Parallelを選択します。
Parallelを中央のフォームにドラッグします。


ワークフローの形が変わります。

次にアクションから AWS LambdaのInvokeを選択します。
Invokeを「ここに状態をドロップ」の2か所にドロップします。

最後にそれぞれのLambda Invokeに、紐づけるLambda関数を選択します。

状態名: わかりやすい名前
APIパラメータ: 紐づけるLambda関数

実際に行われるアクションが定義された。
これでGUIでの定義作業は完了です。
「次へ」を選択。

GUIの操作に合わせて、コードが生成されていることが分かる。

あとはデフォルトの設定でOKである。

ステートマシンが作成されたら、実行の開始ボタンをクリック。

設定したワークフロー通りに、RDS/EC2の停止処理が同時に開始されます。
今回はシンプルなLambdaの組み合わせですが、様々なAWSサービスをワークフローに組み込むことができます。

8.EventBridgeでステートマシンを定期実行させる

(マネジメントコンソール上の操作) EventBridge⇒ルールを作成

イベントバス: default
パターン定義: 「スケジュール」を選択。
今回はテストのために15分ごとにステートマシンが実行されるように設定します。
実行ペースは環境によって決めてください。

ターゲットとして、⑦で作成したステートマシンを選択。

上記の設定で、EventBridgeルールを作成します。
ルール作成後に、StepFunctionsのステートマシンが15分ごとに定期実行されるようになります。

9.検証

最後に「RDS停止/EC2停止」が15分ごとに定期実行されるかを確認しましょう。
私の環境では、12時43分から15分ごとに定期処理が実行されました。


12:43
EventBridgeを作成すると、すぐにEC2とRDSの停止が始まる。
StepFunctionsの画面でも、実行履歴が確認できます。

テストのために、停止したEC2を再度起動させました。


12:58
ステートマシンが実行される。

EC2が停止する。

RDSは停止中なので、停止処理は起こらない。
CloudWatchのログを見ると、RDS用のLambda関数が実行されていることが分かる。
分岐処理として'RDS already stop’が表示されています。

次のテストのために、RDSを再起動させる


13:13
ステートマシンが実行される。
⇒RDSが停止する。
⇒EC2は停止中なので、停止処理は起こらない。


さいごに

以上でRDS/EC2の自動停止処理の実装は完了です。
Lambdaの処理を変えたり、他のAWSサービスを組み合わせることで、日々のルーティン業務を少しでも自動化できたらいいですね。

お疲れ様でした!!

MEGAZONE株式会社 Tech Blog

Discussion