💰
EventBridgeSchedulerでEC2とRDSを停止する
背景
検証環境やバッチ処理のみを実行しているリソースの夜間停止するために、EventBridge Schedulerを使用することにしました。
個人開発の成果物とかもこれで管理できたらコストカットできてとても良いですね〜。
作成物の概要
- 平日のAM08:00に起動
- 平日のPM23:59に停止
- 実装はTerraform
- 対象リソースはEC2とRDS
- タグを付与して対象のインスタンスを特定する
EC2実装
IAM作成
resource "aws_iam_role" "this" {
name = "eventbridge-scheduler-role"
assume_role_policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "scheduler.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy" "this" {
name = "ec2-start-stop-policy"
role = aws_iam_role.this.name
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": "*"
}
]
})
}
対象のEC2はタグで抽出、dataで一括で取得する
data "aws_instances" "this" {
filter {
name = "tag:Target"
values = ["true"]
}
}
EventBrigdeSchedulerを作成
resource "aws_scheduler_schedule_group" "this" {
name = "ec2-off-hours-shutdown"
}
resource "aws_scheduler_schedule" "start" {
name = "ec2-start"
group_name = aws_scheduler_schedule_group.this.name
schedule_expression_timezone = "Asia/Tokyo"
schedule_expression = "cron(0 8 ? * MON-FRI *)"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
role_arn = aws_iam_role.this.arn
# 複数のインスタンスIDを指定出来るので、同じ時間に停止するなら1つのスケジューラーのみで対応できる
input = jsonencode({
"InstanceIds": ${jsonencode(instance_ids)}
})
retry_policy {
maximum_event_age_in_seconds = 3600
}
}
}
resource "aws_scheduler_schedule" "stop" {
name = "ec2-stop"
group_name = aws_scheduler_schedule_group.this.name
schedule_expression_timezone = "Asia/Tokyo"
schedule_expression = "cron(59 23 ? * MON-FRI *)"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
role_arn = aws_iam_role.this.arn
input = jsonencode({
"InstanceIds": ${jsonencode(instance_ids)}
})
}
}
RDS実装
IAMをTerraformで作成
resource "aws_iam_role" "this" {
name = "eventbridge-scheduler-role"
assume_role_policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "scheduler.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy" "this" {
name = "ec2-start-stop-policy"
role = aws_iam_role.this.name
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"rds:StartDBInstance",
"rds:StopDBInstance"
],
"Resource": [
"*"
],
"Effect": "Allow"
}
]
})
}
対象のEC2はタグで抽出、dataで一括で取得する
data "aws_db_instances" "this" {
tags = {
Target = "true"
}
}
EventBrigdeSchedulerをTerraformで作成
resource "aws_scheduler_schedule_group" "this" {
name = "rds-off-hours-shutdown"
}
resource "aws_scheduler_schedule" "start" {
# RDSの再開は対象を1つのリソースしか選択できないため、dataで取得した対象の数だけループしてそれぞれ作成する
count = length(data.aws_db_instances.this.instance_identifiers)
name = "rds-start-for-${data.aws_db_instances.this.instance_identifiers[count.index]}"
group_name = aws_scheduler_schedule_group.this.name
schedule_expression_timezone = "Asia/Tokyo"
schedule_expression = "cron(0 8 ? * MON-FRI *)"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:rds:startDBInstance"
role_arn = aws_iam_role.this.arn
input = jsonencode({
"DbInstanceIdentifier": "${data.aws_db_instances.this.instance_identifiers[count.index]}"
})
retry_policy {
maximum_event_age_in_seconds = 3600
}
}
}
resource "aws_scheduler_schedule" "stop" {
count = length(data.aws_db_instances.this.instance_identifiers)
name = "rds-stop-for-${data.aws_db_instances.this.instance_identifiers[count.index]}"
group_name = aws_scheduler_schedule_group.this.name
schedule_expression_timezone = "Asia/Tokyo"
schedule_expression = "cron(59 23 ? * MON-FRI *)"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:rds:stopDBInstance"
role_arn = aws_iam_role.this.arn
input = jsonencode({
"DbInstanceIdentifier": "${data.aws_db_instances.this.instance_identifiers[count.index]}"
})
}
}
確認方法
CloudTrailで、イベントソースとイベント名で絞り込んで実行ログを確認
- EC2
- イベントソース
- ec2.amazonaws.com
- イベント名
- StartInstances
- StopInstances
- イベントソース
- RDS
- イベントソース
- rds.amazonaws.com
- イベント名
- StartDBInstance
- StopDBInstance
- イベントソース
課題
dataを使って対象を取得しているため、RDSのようにインスタンスごとにスケジューラーを作成すると、apply以降に作成されたインスタンスが対象から漏れてしまいます。
現状では、GitHub Actionsのcron機能を使用してplanを実行し、差分があればSlack通知を行うようにしていますが、より良い方法がないかと考えています。
Discussion