Step Functions で EC2 + RDS 自動停止・起動ハンズオン

EventBridge Scheduler + Step Functions で起動順序を制御しながら EC2 → RDS を安全に自動操作する

AWS Step Functions EventBridge Scheduler AWS Lambda Amazon EC2 Amazon RDS マネコン操作 中〜上級 所要時間 90〜120 分 v1.0

📋 概要

このハンズオンでは、AWS Step Functions を使って EC2 と RDS の起動・停止に 順序と状態確認を加えた自動化ワークフローを構築します。「EC2 が running になってから RDS を起動する」「RDS が stopped になってから EC2 を停止する」という依存関係を Step Functions のステートマシンで実現します。EventBridge Scheduler で毎朝・毎夜に自動実行します。

項目内容
所要時間90〜120 分
難易度中〜上級
主要サービスStep Functions、EventBridge Scheduler、Lambda(Python)、EC2、RDS
シンプル版との違い起動完了を確認してから次の処理へ進む。Wait + Choice による状態ポーリング
ℹ️ シンプル版との使い分け

シンプル版(Scheduler + Lambda)は起動リクエストを送るだけで順序確認はしません。本番環境でアプリが「RDS が起動してから EC2 で接続を試みる」など依存関係がある場合はこの Step Functions 版を使ってください。

🏗️ 構成図

起動フロー(朝 8 時)

⏰ EventBridge Scheduler(平日 8:00 Asia/Tokyo)
↓ StartExecution
🟢 handson-start-sfn(Step Functions ステートマシン)
1️⃣ StartEC2(Lambda: handson-sfn-start-ec2)
EC2 起動リクエスト送信
WaitEC2Boot(Wait 15 秒)→ CheckEC2(Lambda)→ IsEC2Running(Choice)
ec2_state === "running" になるまでループ
↓ running 確認
2️⃣ StartRDS(Lambda: handson-sfn-start-rds)
RDS 起動リクエスト送信

停止フロー(夜 22 時)

⏰ EventBridge Scheduler(平日 22:00 Asia/Tokyo)
↓ StartExecution
🔴 handson-stop-sfn(Step Functions ステートマシン)
1️⃣ StopRDS(Lambda: handson-sfn-stop-rds)
RDS 停止リクエスト送信
WaitRDSStop(Wait 30 秒)→ CheckRDS(Lambda)→ IsRDSStopped(Choice)
rds_status === "stopped" になるまでループ
↓ stopped 確認
2️⃣ StopEC2(Lambda: handson-sfn-stop-ec2)
EC2 停止リクエスト送信

作成する Lambda 関数一覧

関数名役割
handson-sfn-start-ec2EC2 に StartInstances を送信
handson-sfn-check-ec2EC2 の現在の状態(State.Name)を返す
handson-sfn-start-rdsRDS に StartDBInstance を送信
handson-sfn-stop-rdsRDS に StopDBInstance を送信
handson-sfn-check-rdsRDS の現在のステータス(DBInstanceStatus)を返す
handson-sfn-stop-ec2EC2 に StopInstances を送信

✅ 前提条件

🔧 ステップ 1: Lambda 関数の作成(6 関数)

まず IAM ポリシーとロールを作成してから、6 つの Lambda 関数を作成します。

1-0. 共通 IAM ロールの作成

手順(シンプル版と同じポリシー内容)
  1. IAM コンソール → ポリシー → 「ポリシーを作成」→ JSON タブに以下を貼り付ける
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:StartInstances", "ec2:StopInstances", "ec2:DescribeInstances",
        "rds:StartDBInstance", "rds:StopDBInstance", "rds:DescribeDBInstances"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}
ポリシー名
handson-sfn-lambda-policy

次に IAM ロールを作成します。

信頼エンティティ
AWS のサービス → Lambda
アタッチするポリシー
handson-sfn-lambda-policy
ロール名
handson-sfn-lambda-role

1-1. handson-sfn-start-ec2

関数名
handson-sfn-start-ec2
ランタイム / ロール
Python 3.12 / handson-sfn-lambda-role
タイムアウト
30 秒
環境変数: EC2_INSTANCE_ID
i-xxxxxxxxxxxxxxxxx
import boto3, os

def lambda_handler(event, context):
    instance_id = os.environ['EC2_INSTANCE_ID']
    boto3.client('ec2').start_instances(InstanceIds=[instance_id])
    print(f'EC2 {instance_id}: 起動リクエスト送信')
    return {**event, 'ec2_instance_id': instance_id}

1-2. handson-sfn-check-ec2

関数名
handson-sfn-check-ec2
環境変数: EC2_INSTANCE_ID
i-xxxxxxxxxxxxxxxxx
import boto3, os

def lambda_handler(event, context):
    instance_id = event.get('ec2_instance_id', os.environ['EC2_INSTANCE_ID'])
    resp  = boto3.client('ec2').describe_instances(InstanceIds=[instance_id])
    state = resp['Reservations'][0]['Instances'][0]['State']['Name']
    print(f'EC2 {instance_id}: {state}')
    return {**event, 'ec2_state': state}

1-3. handson-sfn-start-rds

関数名
handson-sfn-start-rds
環境変数: RDS_IDENTIFIER
handson-db
import boto3, os

def lambda_handler(event, context):
    db_id = os.environ['RDS_IDENTIFIER']
    boto3.client('rds').start_db_instance(DBInstanceIdentifier=db_id)
    print(f'RDS {db_id}: 起動リクエスト送信')
    return {**event, 'rds_identifier': db_id}

1-4. handson-sfn-stop-rds

関数名
handson-sfn-stop-rds
環境変数: RDS_IDENTIFIER
handson-db
import boto3, os

def lambda_handler(event, context):
    db_id = os.environ['RDS_IDENTIFIER']
    boto3.client('rds').stop_db_instance(DBInstanceIdentifier=db_id)
    print(f'RDS {db_id}: 停止リクエスト送信')
    return {**event, 'rds_identifier': db_id}

1-5. handson-sfn-check-rds

関数名
handson-sfn-check-rds
環境変数: RDS_IDENTIFIER
handson-db
import boto3, os

def lambda_handler(event, context):
    db_id  = event.get('rds_identifier', os.environ['RDS_IDENTIFIER'])
    resp   = boto3.client('rds').describe_db_instances(DBInstanceIdentifier=db_id)
    status = resp['DBInstances'][0]['DBInstanceStatus']
    print(f'RDS {db_id}: {status}')
    return {**event, 'rds_status': status}

1-6. handson-sfn-stop-ec2

関数名
handson-sfn-stop-ec2
環境変数: EC2_INSTANCE_ID
i-xxxxxxxxxxxxxxxxx
import boto3, os

def lambda_handler(event, context):
    instance_id = event.get('ec2_instance_id', os.environ['EC2_INSTANCE_ID'])
    boto3.client('ec2').stop_instances(InstanceIds=[instance_id])
    print(f'EC2 {instance_id}: 停止リクエスト送信')
    return {**event, 'ec2_state': 'stopping'}

🔧 ステップ 2: Step Functions 実行ロールの作成

Step Functions が Lambda を呼び出すための IAM ロールを作成します。

2-1. IAM ポリシーの作成

ポリシー名
handson-sfn-execution-policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:*:function:handson-sfn-*"
    },
    {
      "Effect": "Allow",
      "Action": ["logs:CreateLogDelivery","logs:PutLogEvents","logs:CreateLogGroup"],
      "Resource": "*"
    }
  ]
}

2-2. IAM ロールの作成

信頼エンティティ
AWS のサービス → Step Functions
アタッチするポリシー
handson-sfn-execution-policy
ロール名
handson-sfn-execution-role
ℹ️ Step Functions 用の信頼ポリシー

Step Functions サービスの信頼エンティティは states.amazonaws.com です。ロールを作成する際に「Step Functions」を選択すると自動的に設定されます。

🔧 ステップ 3: 起動ステートマシンの作成

3-1. Step Functions コンソールを開く

手順
  1. AWS コンソールで「Step Functions」を検索して開く
  2. 左メニュー → 「ステートマシン」→「ステートマシンを作成」
  3. 作成方法: 「コードでワークフローを記述する」(ASL を直接入力)
  4. タイプ: 「Standard」(実行履歴が 90 日保存される)

3-2. ASL(Amazon States Language)の入力

以下の JSON をそのまま貼り付けます。Lambda 関数の ARN は 自動取得のため手動置換不要です(Resource に関数名を指定する方法を使います)。

⚠️ ARN を実際の値に置き換えてください

下記 JSON 内の <ACCOUNT_ID> をご自身の AWS アカウント ID(12 桁)に置き換えてください。アカウント ID はコンソール右上のアカウント名をクリックすると確認できます。

{
  "Comment": "EC2 running 確認後に RDS を起動するワークフロー",
  "StartAt": "StartEC2",
  "States": {
    "StartEC2": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:handson-sfn-start-ec2",
      "Next": "WaitEC2Boot",
      "ResultPath": "$.start_ec2_result"
    },
    "WaitEC2Boot": {
      "Type": "Wait",
      "Seconds": 15,
      "Next": "CheckEC2Status"
    },
    "CheckEC2Status": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:handson-sfn-check-ec2",
      "Next": "IsEC2Running",
      "ResultPath": "$"
    },
    "IsEC2Running": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.ec2_state",
          "StringEquals": "running",
          "Next": "StartRDS"
        }
      ],
      "Default": "WaitEC2Boot"
    },
    "StartRDS": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:handson-sfn-start-rds",
      "End": true
    }
  }
}
ステートマシン名
handson-start-sfn
実行ロール
既存のロール → handson-sfn-execution-role

「作成」をクリックします。

ℹ️ ASL の主要なステートタイプ
タイプ説明
TaskLambda などの外部サービスを呼び出す。Resource に ARN を指定
Wait指定秒数だけ待機する。ポーリングの間隔として使用
Choice条件分岐。Variable の値によって次のステートを決定

🔧 ステップ 4: 停止ステートマシンの作成

起動と同じ手順でステートマシンを作成します。

⚠️ <ACCOUNT_ID> を置き換えてください
{
  "Comment": "RDS stopped 確認後に EC2 を停止するワークフロー",
  "StartAt": "StopRDS",
  "States": {
    "StopRDS": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:handson-sfn-stop-rds",
      "Next": "WaitRDSStop",
      "ResultPath": "$.stop_rds_result"
    },
    "WaitRDSStop": {
      "Type": "Wait",
      "Seconds": 30,
      "Next": "CheckRDSStatus"
    },
    "CheckRDSStatus": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:handson-sfn-check-rds",
      "Next": "IsRDSStopped",
      "ResultPath": "$"
    },
    "IsRDSStopped": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.rds_status",
          "StringEquals": "stopped",
          "Next": "StopEC2"
        }
      ],
      "Default": "WaitRDSStop"
    },
    "StopEC2": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:function:handson-sfn-stop-ec2",
      "End": true
    }
  }
}
ステートマシン名
handson-stop-sfn
実行ロール
handson-sfn-execution-role
ℹ️ RDS の停止にかかる時間

RDS の停止処理は通常 3〜10 分かかります。WaitRDSStop で 30 秒待機してから状態確認するループが繰り返されます。実際の実行では CheckRDSStatus → IsRDSStopped → WaitRDSStop のループが 5〜15 回程度繰り返されます。

🔧 ステップ 5: EventBridge Scheduler の設定

Step Functions を定時実行するための Scheduler を 2 つ作成します。まず Scheduler 用の IAM ロールが必要です。

5-1. Scheduler 用 IAM ロールの作成

ポリシー内容
states:StartExecution(handson-start-sfn と handson-stop-sfn)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "states:StartExecution",
      "Resource": [
        "arn:aws:states:ap-northeast-1:*:stateMachine:handson-start-sfn",
        "arn:aws:states:ap-northeast-1:*:stateMachine:handson-stop-sfn"
      ]
    }
  ]
}
ポリシー名
handson-scheduler-sfn-policy

IAM ロールを作成します。

信頼エンティティ
AWS のサービス → EventBridge Scheduler(scheduler.amazonaws.com)
ロール名
handson-scheduler-sfn-role
⚠️ 信頼エンティティの選択に注意

「EventBridge」ではなく 「EventBridge Scheduler」 を選択してください。信頼ポリシーのプリンシパルが scheduler.amazonaws.com になっていることを確認してください。コンソールに「Scheduler」が一覧にない場合は「カスタム信頼ポリシー」で直接入力します。

5-2. 起動スケジューラーの作成

手順
  1. EventBridge コンソール → Scheduler → 「スケジュールを作成」
スケジュール名
handson-sfn-start-schedule
Cron 式
0 8 ? * MON-FRI *
タイムゾーン
Asia/Tokyo
ターゲット
テンプレートされたターゲット → Step Functions StartExecution
ステートマシン
handson-start-sfn
入力 JSON(ペイロード)
{}
実行ロール
既存のロール → handson-scheduler-sfn-role

5-3. 停止スケジューラーの作成

スケジュール名
handson-sfn-stop-schedule
Cron 式
0 22 ? * MON-FRI *
タイムゾーン
Asia/Tokyo
ステートマシン
handson-stop-sfn

🔧 ステップ 6: 動作確認

6-1. 停止ステートマシンを手動実行

手順
  1. Step Functions コンソール → handson-stop-sfn を開く
  2. 「実行の開始」 ボタンをクリック
  3. 入力 JSON: {} のまま → 「実行の開始」
  4. 実行詳細画面でフロー図が表示される → ステートが緑色に変わりながら進む様子を観察

6-2. 実行フロー図の確認

✅ 期待される画面

Step Functions の実行画面にステートマシンのフロー図が表示されます。現在実行中のステートが青く、完了したステートが緑に変わります。WaitRDSStop → CheckRDSStatus → IsRDSStopped のループが RDS が停止するまで繰り返されます(3〜10 分程度)。

6-3. 入出力の確認

手順
  1. 実行画面の各ステートをクリック → 右側パネルに「入力」と「出力」が表示される
  2. CheckRDSStatus ステートの「出力」に rds_status フィールドの値が表示される(例: "stopping""stopped"
  3. IsRDSStopped ステートの遷移先が "stopped" になった時点で StopEC2 へ進む様子を確認する

6-4. 起動ステートマシンを手動実行

EC2 と RDS が停止したことを確認してから、handson-start-sfn を同様に手動実行します。「EC2 が running になってから RDS が起動リクエストを受け取る」フローを確認してください。

6-5. 実行履歴の確認

手順
  1. Step Functions コンソール → ステートマシン → 「実行」タブ
  2. 過去の実行一覧が表示される。ステータスが「成功」であれば OK
  3. 各実行をクリックして「イベント履歴」タブ → 各ステートの実行時刻・入出力・所要時間が確認できる

🧹 クリーンアップ

削除手順(逆順で実施)

  1. EventBridge Scheduler の削除
    EventBridge → Scheduler → handson-sfn-start-schedule と handson-sfn-stop-schedule を削除
  2. Step Functions ステートマシンの削除
    Step Functions → handson-start-sfn と handson-stop-sfn を選択 → 「削除」
  3. Lambda 関数の削除(6 関数)
    Lambda → handson-sfn-start-ec2, check-ec2, start-rds, stop-rds, check-rds, stop-ec2 を削除
  4. IAM ロール・ポリシーの削除
    IAM → ロール: handson-sfn-execution-role, handson-sfn-lambda-role, handson-scheduler-sfn-role を削除
    IAM → ポリシー: handson-sfn-execution-policy, handson-sfn-lambda-policy, handson-scheduler-sfn-policy を削除
✅ 削除確認

Step Functions の「ステートマシン」一覧から handson-start-sfn と handson-stop-sfn が消えていれば完了です。Lambda 関数 6 つ、Scheduler 2 つ、IAM リソースの削除も確認してください。