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_rule
とaws_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/Lambda
のErrors
> 0
EventBridge:AWS/Events
のFailedInvocations
> 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