🐥

EventBridge × Lambdaで作る指定したタイミングでAWSリソースを動かす方法

に公開

概要

EventBridgeとLambdaを使って、AWSリソースを自動起動・停止する仕組みを、記事にしたいと思います。
今回はRDSを例に、平日朝/夕の指定した時間と土日・祝日は起動しないようにしてみました。
設計は他サービス(EC2/ECS/Redshift など)にも応用可能です。

背景と要件

  • 業務時間のみリソースを起動し、時間外は停止したい(コスト最適化)
  • 土日・祝日は実行しない(日本の祝日対応)という要件にも対応可
  • 失敗時のみアラートが欲しい(平常時はノイズを出さない)
  • IaC(Terraform)で一貫管理し、再現性を担保したい
    本記事の最後にTerraformコードを載せてます。

アーキテクチャ概要

  • EventBridge スケジュール
    平日朝/夕の 2 本の cron(JSTの運用時刻をUTCに換算して指定)
  • Lambda 関数(Python 3.13)
    祝日ライブラリ(例: jpholiday)で当日が祝日か判定
    平日かつ非祝日のときのみターゲット操作(DRY_RUN で本操作のオン/オフ切替)
  • 権限
    Lambda 実行ロール + 対象サービスの最小権限(例: RDS の Start/Stop)
  • 監視/通知
    CloudWatch アラーム(Lambda Errors と EventBridge FailedInvocations)
    SNS(メール)へ失敗時のみ通知

実装のポイント

1) スケジュール(EventBridge)

  • 例: 平日 9:50(起動)/ 18:00(停止)
  • Terraform では aws_cloudwatch_event_ruleaws_cloudwatch_event_target を定義
  • EventBridgeのcronはUTC指定のため、JSTの運用時刻をUTCに換算して指定
    9:50 JST → cron(50 0 ? * MON-FRI *)
    18:00 JST → cron(0 9 ? * MON-FRI *)

2) Lambda(祝日判定と環境変数)

  • 祝日判定に jpholiday を使用
  • 環境変数例
    ACTION: start/stop/scale 等の動作切替
    DRY_RUN: true なら実操作せずログのみ
    対象リソース ID(例: RDS_INSTANCE_ID

サンプル(抜粋、RDS 例):

import os, json, logging
from datetime import datetime, timezone, timedelta
import jpholiday, boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)
JST = timezone(timedelta(hours=9))

def lambda_handler(event, context):
    inst = os.environ.get('RDS_INSTANCE_ID')
    action = os.environ.get('ACTION', 'start')
    dry = os.environ.get('DRY_RUN', 'false').lower() == 'true'

    today = datetime.now(JST).date()
    is_weekday = today.weekday() < 5
    is_holiday = jpholiday.is_holiday(today)

    if not (is_weekday and not is_holiday):
        return {"statusCode": 200, "body": json.dumps({"executed": False})}

    if dry:
        logger.info(f"[DRY] {inst} will {action}")
        return {"statusCode": 200, "body": json.dumps({"executed": True, "dry": True})}

    rds = boto3.client('rds')
    if action == 'start':
        rds.start_db_instance(DBInstanceIdentifier=inst)
    elif action == 'stop':
        rds.stop_db_instance(DBInstanceIdentifier=inst)
    else:
        raise ValueError('invalid ACTION')
    return {"statusCode": 200, "body": json.dumps({"executed": True})}

ポイント:

  • ロジック自体はどのサービスにも流用可能(API クライアントと ACTION 分岐を置換)
  • 強制実行が必要なら FORCE_EXECUTE=true 等の環境変数を追加する拡張も有効

3) IAM 最小権限

  • rds:StartDBInstance
    停止中のRDS DBインスタンスを起動する権限(非Aurora向け)。
    ACTION=start のときに必要。

  • rds:StopDBInstance
    稼働中のRDS DBインスタンスを停止する権限(非Aurora向け)。
    ACTION=stop のときに必要。
    補足: RDSは最大7日で自動起動される仕様があります。Auroraは「インスタンス停止」ではなく「クラスタの一時停止/再開」を使います。

  • rds:DescribeDBInstances
    DBインスタンス情報を取得する読み取り権限。
    実行前後の状態確認やログ出力、将来の拡張(待ち合わせ等)で有用なため付与。

4) 監視と通知(失敗時のみ)

  • SNS トピックにメール購読(要メール承認)
    失敗時のみ通知し、日常運用のノイズを削減
  • CloudWatch Metric Alarms
    Lambda: AWS/LambdaErrors > 0
    EventBridge: AWS/EventsFailedInvocations > 0
    treat_missing_data = notBreaching(未実行や週末で誤検知しない)

運用 Tips とベストプラクティス

  • 本番切替前に DRY_RUN=true でロジック検証(祝日/平日テスト)
  • タイムゾーンは常に明示(JST を UTC 変換して cron 設定)
  • 7 日間停止制限など、サービス固有の仕様に注意(例: RDS の自動再起動)
  • ログ出力を構造化(アラート時のトラブルシュートを容易に)

まとめ

今回は、EventBridge × Lambda による「平日・非祝日だけ実行」パターンの記事を書きましたが、
好きなタイミングでAWSリソースを動かしたいor停止したい場合に応用していただけたらと思います。

いきなり本番リソースに影響を与えるのが運用ルール的に厳しい場合が多々あると思いますが、その時はDRY_RUNを活用し、安全に検証してみてください。

また、CloudWatch アラームと SNS を組み合わせることで、失敗時のみを確実に検知しつつ、日常運用の負荷を抑えられます。RDS に限らず、EC2/ECS/Redshift/Glue などへも同様に適用可能です。

※以下、Terraformコード

Lambda関数 - RDS起動用

resource "aws_lambda_function" "rds_start" {
  filename         = data.archive_file.rds_scheduler_zip.output_path
  function_name    = "rds-start-scheduler"
  role            = aws_iam_role.rds_scheduler_lambda_role.arn
  handler         = "lambda_function.lambda_handler"
  runtime         = "python3.13"
  timeout         = 60

  source_code_hash = data.archive_file.rds_scheduler_zip.output_base64sha256

  environment {
    variables = {
      RDS_INSTANCE_ID = aws_db_instance.mydb.identifier
      ACTION         = "start"
      DRY_RUN        = "false"  # 初期はテストモード
    }
  }

  tags = {
    Name = "RDS Start Scheduler"
  }
}

Lambda関数 - RDS停止用

resource "aws_lambda_function" "rds_stop" {
  filename         = data.archive_file.rds_scheduler_zip.output_path
  function_name    = "rds-stop-scheduler"
  role            = aws_iam_role.rds_scheduler_lambda_role.arn
  handler         = "lambda_function.lambda_handler"
  runtime         = "python3.13"
  timeout         = 60

  source_code_hash = data.archive_file.rds_scheduler_zip.output_base64sha256

  environment {
    variables = {
      RDS_INSTANCE_ID = aws_db_instance.mydb.identifier
      ACTION         = "stop"
      DRY_RUN        = "false"  # 初期はテストモード
    }
  }

  tags = {
    Name = "RDS Stop Scheduler"
  }
}

EventBridge ルール - RDS起動(平日9:50 JST)

resource "aws_cloudwatch_event_rule" "rds_start_schedule" {
  name                = "rds-start-schedule"
  description         = "RDS起動スケジュール - 平日9:50 JST"
  schedule_expression = "cron(50 0 ? * MON-FRI *)"  # UTC 0:50 = JST 9:50

  tags = {
    Name = "RDS Start Schedule"
  }
}

resource "aws_cloudwatch_event_target" "rds_start_target" {
  rule      = aws_cloudwatch_event_rule.rds_start_schedule.name
  target_id = "RDSStartLambdaTarget"
  arn       = aws_lambda_function.rds_start.arn
}

resource "aws_lambda_permission" "allow_eventbridge_start" {
  statement_id  = "AllowExecutionFromEventBridge"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.rds_start.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.rds_start_schedule.arn
}

EventBridge ルール - RDS停止(平日18:00 JST)

resource "aws_cloudwatch_event_rule" "rds_stop_schedule" {
  name                = "rds-stop-schedule"
  description         = "RDS停止スケジュール - 平日18:00 JST"
  schedule_expression = "cron(0 9 ? * MON-FRI *)"   # UTC 9:00 = JST 18:00

  tags = {
    Name = "RDS Stop Schedule"
  }
}

resource "aws_cloudwatch_event_target" "rds_stop_target" {
  rule      = aws_cloudwatch_event_rule.rds_stop_schedule.name
  target_id = "RDSStopLambdaTarget"
  arn       = aws_lambda_function.rds_stop.arn
}

resource "aws_lambda_permission" "allow_eventbridge_stop" {
  statement_id  = "AllowExecutionFromEventBridge"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.rds_stop.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.rds_stop_schedule.arn
}

Discussion