EventBridgeからEventBridge Schedulerに移行してみる

## はじめに

この記事は、[ミロゴス Advent Calendar 2022](https://qiita.com/advent-calendar/2022/milogos) １１日目の記事です。

先月の11/10にEventBridge Scheduler（以降、Scheduler）が発表されました。
[Introducing Amazon EventBridge Scheduler](https://aws.amazon.com/jp/blogs/compute/introducing-amazon-eventbridge-scheduler/)

これまで社内システムにてStep Functionsをcronで指定した時刻に実行するために、EventBridge Rule（以降、Rule）を使っていました。
今回、発表されたSchedulerはまさにこのユースケースにはまる内容でした。

本記事ではRuleをSchedulerに変える際に、現状実施していることは変えずに移行できるのか確認していきます。

なお、呼ばれるStep Functionsのワークフローは以前に[Step Functions Localでモック](https://qiita.com/taskforce_Hisui/items/397ed38f0cdae94b0941#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)していたものです。このステートマシンはルールからパラメータを受けとっており、条件分岐やデータストアから引っ張ってくる条件などに使っています。

## 前提

### Amazon EventBridge Schedulerに関して

すでに様々なブログで紹介記事を見ますが、詳細かつ日本語ではclassmethodさんの記事がわかりやすかったです。
- [EventBridge SchedulerとEvent Bridge Ruleの違いをCloudFormationから学ぶ](https://dev.classmethod.jp/articles/eventbridge-scheduler-and-event-bridge-rule-difference/)

EventBridgeではイベント駆動のために他サービスからのイベントパターンの設定やSaaSとしての利用など広く利用されていました。
それ以外にCloudWatch Eventsから持ってきたcronなどの時間指定イベントがあるなど、ごちゃ混ぜなイメージを私は持っていました。
今回のSchedulerの登場によって時間指定のイベントは別管理になるため、整理されそうな所感を持っています。

また、Step Functionsに渡すパラメータや時間ごとにルールを作っていると当たり前のようにルール数が増えていきます。
ルール数のデフォルト上限である300件から500件へ上限緩和申請をしているのですが、先日確認した際にまた上限に近づいてきており、急いで再申請していました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/e906b1be-20f8-c90a-06f7-9d616cf844d8.png)

Schedulerではデフォルトで100万件となっており、これだけでも移行を検討する材料になるかと思います。

## 移行する対象

まず、移行する対象のシステムの概要です。

- Lambdaからルールを作成します。
    - 本記事では調査が前提のため、ローカル環境から実行します。
- AWS SDKを使います。言語はPythonを使うためboto3でリソースを作成します。
    - [Ruleのboto3リファレンス](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/events.html)
    - [EventBridgeSchedulerのboto3リファレンス](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/scheduler.html)
- Step Functionsには入力パラメータとして整形されたJSONを渡します。
- 起動させるStep FunctionsやEventBridgeのRoleを環境変数で設定します。
    - TARGET_ARN
        - ステートマシンのARN
    - EVENTS_ROLE_ARN
        - Ruleがステートマシンを実行するためのIAM RoleのARN
    - SCHEDULER_ROLE_ARN
        - Schedulerがステートマシンを実行するためのIAM RoleのARN

```python:app.py
import json
import os
import boto3

if __name__ == "__main__":
    # Rule
    events_client = boto3.client("events")
    events_client.put_rule(
        Name="hoge_rule",
        ScheduleExpression="cron(30 8 29 11 ? 2023)",
        State="DISABLED",
    )
    events_client.put_targets(
        Rule="hoge_rule",
        Targets=[
            {
                "Id": "hoge_target",
                "Arn": os.environ["TARGET_ARN"],
                "RoleArn": os.environ["EVENTS_ROLE_ARN"],
                "Input": json.dumps({"hoge": "test"}),
            }
        ],
    )

    # Scheduler
    scheduler_client = boto3.client("scheduler")
    scheduler_client.create_schedule(
        Name="hoge_rule",
        ScheduleExpressionTimezone="Asia/Tokyo",
        ScheduleExpression="cron(30 17 29 11 ? 2023)",
        Target={
            "Arn": os.environ["TARGET_ARN"],
            "RoleArn": os.environ["SCHEDULER_ROLE_ARN"],
            "Input": json.dumps({"hoge": "test"}),
        },
        State="DISABLED",
        # スケジュールを分散する必要がないためOFF
        FlexibleTimeWindow={"Mode": "OFF"},
    )
```

Ruleではルールの作成後にターゲットをアタッチするような流れになります。
Targets引数は配列の形のため、1イベントで複数の対象を設定できます。

SchedulerはScheduleの作成時にまとめて実行時間と対象を設定します。
Ruleとは異なり、対象は単一になります。

また、FlexibleTimeWindowは使わない場合でも、設定は必要になります。
FlexibleTimeWindowを指定すると、指定時間内で対象を起動し分散させることができます。
今回は指定の時間に実行させたいので、OFFにしています。

ScheduleExpressionTimezoneはオプションのパラメータです。
RuleのcronではUTCでの時間設定でした。
SchedulerではScheduleExpressionTimezoneを指定することで、タイムゾーンを指定できます。
ここでは+0900のAsia/Tokyoを指定したため、cronの時間も+9時間しています。

### 実行時の注意

> botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the CreateSchedule operation: The execution role you provide must allow AWS EventBridge Scheduler to assume the role.

Ruleと同じRoleを使ってしまうと、Schedulerの実行で上記のようなエラーになります。
IAM RoleのTrusted EntityはRuleとSchedulerとで異なるため注意が必要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/4526f58b-0aef-d2a7-badc-83b11553d1b0.png)

なお、2022/11/30時点ではAWSコンソールでIAM Roleを作成する際、AWS ServiceからSchedulerが見つからなかったため、Custom trust policyを選んでJSONで直接指定するのが良さそうです。

Trusted Entityはそれぞれ下記になります。

- EventBridge
    - `events.amazonaws.com`
```json:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "events.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```
- Scheduler
    - `scheduler.amazonaws.com`
```json
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "Statement1",
			"Effect": "Allow",
			"Principal": {
			    "Service": "scheduler.amazonaws.com"
			},
			"Action": "sts:AssumeRole"
		}
	]
}
```

なお、PolicyはステートマシンのStartExecution権限のみにしています。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "states:StartExecution"
            ],
            "Resource": "arn:aws:states:ap-northeast-1:00000000000:stateMachine:*",
            "Effect": "Allow"
        }
    ]
}
```

### AWSコンソール上での確認

||EventBridge|EventBridge Scheduler|
| --- | --- | --- |
| cronの見え方 |![スクリーンショット 2022-11-29 17.28.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/df56f1f9-cb6c-5505-3c38-a0d32a1c6659.png) |![スクリーンショット 2022-11-30 13.29.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/3042cdda-9b82-91a8-a678-9d2fd4689e98.png)|
| ターゲットの見え方 |![スクリーンショット 2022-11-29 17.28.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/44ee8739-41f6-55f9-25ee-5f04b5789cba.png) |![スクリーンショット 2022-11-30 13.24.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/5574ec51-42ff-89db-f7c4-cd482d0ade5f.png) |

Schedulerのcronの時間は見やすくなっているので、[AWS独特のcron式](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-create-rule-schedule.html#eb-cron-expressions)でも見間違えの可能性が下がりそうです。

## 実行時のエラー

> botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the CreateSchedule operation: The schedule expression cron(30 0 1 1 ? 2022), with StartDate null and EndDate null, is invalid.

CreateScheduleする際に実行時間がcronの指定時間を過ぎていた場合、上記のようなエラーが出力されました。
StartDateとEndDateのパラメータがSchedulerでは追加されたため、無効な時間設定になっていないかを確認するようになっているようです。
Schedulerを設定する以上は有効な時間をcronで設定しているものという仕様のようですね。

## 所感

RuleからSchedulerへの移行の確認として、同じことができるのか簡単に触ってみました。
細かい部分での違いはあるものの時間による処理の開始にRuleを使っているのならば、問題なくSchedulerに移行ができそうでした。
同じようなユースケースの方がSchedulerに移行する一助になれば嬉しいです。