EventBridge Schedulerを使ったEC2の定期起動/停止をTerraformで実装する

この記事は terraform Advent Calendar 2024 の21日目の記事です。

今回はEventBridge Schedulerを使ってEC2インスタンスを定刻で停止・開始する設定を
Terraformで実装してみたいと思います。

業務上でもよくお客様から「開発環境のとあるインスタンスは業務時間内しか使わないから、使う時間帯だけ起動しておき、それ以外の時間は停止してコスト削減したい」というご要望をよくいただきます。

手動で毎日対応するのは辛いので自動化しましょう + 設定をテンプレート化しましょうということでさくっと必要なコードを書いてみました。
皆さんの参考になれば幸いです。

## 必要な記述
- Terraformのprovider設定
- IAMロールとポリシーの設定
- EC2インスタンスの設定
- インスタンス停止用EventBridge Schedulerの設定
- インスタンス開始用EventBridge Schedulerの設定

## 記述内容

### Terraformのprovider設定

東京リージョンを使いますよという設定です。
ここはTerraformの基本ですね。

```HCL
provider "aws" {
  region = "ap-northeast-1"
}
```

### IAMロールとポリシーの設定

今回は EventBridge Scheduler がEC2インスタンスを起動・停止するAPIを実行します。
APIの実行権限を与える必要があるため、IAMロールを作成し
ec2:StartInstances と ec2:StopInstances を許可するポリシーを紐づけます。

```HCL
## Create IAM Role for EventBridge Scheduler
resource "aws_iam_role" "eventbridge_scheduler_role" {
  name = "eventbridge-scheduler-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Service = "scheduler.amazonaws.com"
        },
        Action = "sts:AssumeRole"
      }
    ]
  })
}

## Create IAM Policy for EventBridge Scheduler
resource "aws_iam_policy" "ec2_operation_policy" {
  name = "ec2-operation-policy"
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Action = [
          "ec2:StartInstances",
          "ec2:StopInstances"
        ],
        Resource = "*"
      }
    ]
  })
}

## Attach Policy to IAM Role
resource "aws_iam_role_policy_attachment" "ec2_operation_policy_attachment" {
  role       = aws_iam_role.eventbridge_scheduler_role.id
  policy_arn = aws_iam_policy.ec2_operation_policy.arn
}
```

### EC2インスタンスの設定

EventBridge Scheduler によって操作されるEC2インスタンスです。
ひとまずインスタンスを作成できる最低限の設定を書いています。

```HCL
## Create EC2 Instance
resource "aws_instance" "example" {
  ami           = "ami-0ab02459752898a60"
  instance_type = "t3.micro"

  tags = {
    Name = "example-ec2-instance"
  }
}
```

### インスタンス停止用EventBridge Schedulerの設定

EventBridge Scheduler の target.arn で ec2:stopInstances を実行するように指定します。
当然実行するために対象のインスタンスのIDを特定の書式で渡す必要があるので、jsonencodeというTerraformの関数を使い

```json
{"InstanceIds":["i-0d6256f6f0883e873"]}
```

という json を input として指定しています。

schedule_expressionの部分はcron形式でSchedulerが実行される時間を指定します。
schedule_expression_timezone でタイムゾーンをJSTにしているため日本時間で指定が可能です。
(実行時間は皆さんの環境に合わせて任意のものに変更してください)

```HCL
## Create Scheduler Schedule for Stop EC2 Instance
resource "aws_scheduler_schedule" "stop_ec2_instance" {
  name = "stop-ec2-instance"

  schedule_expression          = "cron(0 19 * * ? *)"
  schedule_expression_timezone = "Asia/Tokyo"

  flexible_time_window {
    mode = "OFF"
  }

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
    role_arn = aws_iam_role.eventbridge_scheduler_role.arn

    input = jsonencode({
      InstanceIds = [
        aws_instance.example.id
      ]
    })
  }
}
```

### インスタンス開始用EventBridge Schedulerの設定

インスタンス停止用の設定とほぼ同じです。
違いと言えば target.arn がec2:startInstancesに変わっているくらいでしょうか。
(こちらも実行時間は皆さんの環境に合わせて任意のものに変更してください)

```HCL
## Scheduler Schedule for Run EC2 Instance
resource "aws_scheduler_schedule" "start_ec2_instance" {
  name = "start-ec2-instance"

  schedule_expression          = "cron(0 10 * * ? *)"
  schedule_expression_timezone = "Asia/Tokyo"

  flexible_time_window {
    mode = "OFF"
  }

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
    role_arn = aws_iam_role.eventbridge_scheduler_role.arn

    input = jsonencode({
      InstanceIds = [
        aws_instance.example.id
      ]
    })
  }
}
```

## まとめ
後はTerraformのお作法に従って init → plan → apply で反映されます。
コード自体はそれほど難しくなく気軽に実装できますね。
EC2等のインスタンスを使わない時間帯はこまめに停止しておきましょう。
皆さんもコスト削減の一つの試みとして是非試してみてください～。
以上です(完)