EC2インスタンスを定期的に起動・停止する(EventBridgeSchedulerを使って)

# 概要
RDSへアクセスする際によくある構成として、EC2インスタンスの踏み台サーバーを作成してアクセスすることがあると思います。
例に漏れず現場でもそのような構成でRDSにアクセスしているのですが、
コストカット施策の一環で踏み台サーバーの稼働時間を平日日中だけにしたいという要件が降ってきました。

ネット上ではEventBridgeルールによるスケジュール設定が割と溢れているのですが、
せっかくならより高性能なEventBridge Schedulerを使ってやりたいなぁと思い頑張ってみました。
https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/scheduler.html

作った時のハマった点も含めて紹介していこうかと思います。

:::note warn
既にあるEC2インスタンスに対してEventBridgeSchedulerを割り当てる想定で記載していきます。
VPCやサブネット,EC2インスタンスの立て方などに関しては割愛します。
:::

## EventSchedulerで利用するロールの作成
EventSchedulerから起動するために必要なロール、ポリシーを作成します。
`jump-host`というロール名にしています。

```
# 自動起動,停止の権限
data "aws_caller_identity" "self" {}
resource "aws_iam_role" "jump-host" {
  name               = "jump-host"
  assume_role_policy = file("./resources/assume_policy.json")
}
resource "aws_iam_policy" "jump-host" {
  name   = "jump-host"
  policy = templatefile("./resources/policy.json", {
    account-id : data.aws_caller_identity.self.id
  } )
}
resource "aws_iam_role_policy_attachment" "jump-host" {
  role       = aws_iam_role.jump-host.id
  policy_arn = aws_iam_policy.jump-host.arn
}
```

IAMロールに紐づけるassumeポリシーの詳細です。
今回はEventBridge Schedulerから使われる想定のため、Principalに`scheduler.amazonaws.com`を記載してます。

``` ./resources/assume_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": ["scheduler.amazonaws.com"]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

IAMロールに紐づけるポリシーの詳細です。

EC2インスタンスを定期起動・停止する方法はいくらかあると思います。
(SystemsMangaerのオートメーション機能とかもそれっぽい形でできそうなんだということが調べていて知りました)

今回の手順ではEC2に対して直接APIを投げるシンプルな形にしようかと思うので、[StartInstances](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/APIReference/API_StartInstances.html),[StopInstances](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/APIReference/API_StopInstances.html)APIを叩くことにします。
よってEC2インスタンスを起動・停止させたいため以下のようなポリシーを設定します。


``` ./resources/policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:StartInstances",
        "ec2:StopInstances"
      ],
      "Resource": [
        "arn:aws:ec2:*:${account-id}:instance/*",
        "arn:aws:license-manager:*:${account-id}:license-configuration:*"
      ]
    }
  ]
}
```

## EventSchedulerの作成
まずはソースから。

```
resource "aws_scheduler_schedule" "jump-host-stop" {
  name = "jump-host-stop"
  description = "踏み台サーバの停止"
  schedule_expression = "cron(0 21 ? * MON-FRI *)"
  schedule_expression_timezone = "Asia/Tokyo"
  flexible_time_window {
    mode = "OFF"
  }
  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
    role_arn = aws_iam_role.jump-host.arn
    input    = <<EOF
{
  "InstanceIds": ["${aws_instance.jump-host.id}"]
}
EOF
  }
}

resource "aws_scheduler_schedule" "jump-host-start" {
  name = "jump-host-start"
  description = "踏み台サーバーの起動"
  schedule_expression = "cron(0 8 ? * MON-FRI *)"
  schedule_expression_timezone = "Asia/Tokyo"
  flexible_time_window {
    mode = "OFF"
  }
  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
    role_arn = aws_iam_role.jump-host.arn
    input    = <<EOF
{
  "InstanceIds": ["${aws_instance.jump-host.id}"]
}
EOF
  }
}
```

肝となるのが`target`ブロックです。
```
  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
    role_arn = aws_iam_role.jump-host.arn
    input    = <<EOF
{
  "InstanceIds": ["${aws_instance.jump-host.id}"]
}
EOF
  }
```

ターゲットの指定方法はいくつかあるのですが、
より汎用的な指定方法としてarnにユニバーサルターゲットを指定する方法があります。
ここにStartInstances,StopInstancesとパラメーターとしてEC2インスタンスのidを渡してあげれば無事にスケジュール登録完了となります。
https://docs.aws.amazon.com/ja_jp/scheduler/latest/UserGuide/managing-targets-universal.html

書式としてはこんな感じになります。
`arn:aws:scheduler:::aws-sdk:service:apiAction`

で、ここ、私のつまずきポイントだったのですが、
`apiAction`は **キャメルケース**で書く必要があります。
(StartInstancesのドキュメントとか大文字でかかれているので、ここにつまずき小一時間持ってかれました)


# まとめ
EventBridgeSchedulerを利用してEC2インスタンスを定期的に起動・停止する方法についてまとめてみました。
ルールに比べてタイムゾーン指定できたり色々と便利ですね。
少しずつルールから書き換えていきたいと思います。