CloudWatchイベントでスケジュールされたECSタスクをAWS SDK for Javaで直接起動する

## TL; DR
CloudWatchイベントでスケジュールされたECSタスクを、AWS SDK for Javaを利用して直接起動する機会があったので、備忘録がてら知見としてまとめます。


## 環境
- AWS CLI: `2.1.16`
- Kotlin: `1.3.50`
- AWS SDK for Java: `2.8.7`


## AWS CLI
やること自体はとても単純ですので、簡単にAWS CLIで動作確認していきます。

まず、[list-targets-by-rule][list-targets-by-rule]でCloudWatchイベントでスケジュールされたECSタスクのパラメータを取得します。

```bash
$ aws events list-targets-by-rule --rule "batch-schedule"
```

レスポンスを確認すると、`Arn`にクラスター名、`EcsParameters`以下にECSタスクの起動に必要なパラメータが色々設定されているのが分かります。

```json
{
    "Targets": [
        {
            "Id": "batch-schedule",
            "Arn": "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/{クラスター名}",
            "RoleArn": "arn:aws:iam::{アカウントID}:role/{ロール名}",
            "EcsParameters": {
                "TaskDefinitionArn": "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/{ファミリー名}:{リビジョン番号}",
                "TaskCount": 1,
                "LaunchType": "FARGATE",
                "NetworkConfiguration": {
                    "awsvpcConfiguration": {
                        "Subnets": [
                            "{サブネットID}"
                        ],
                        "SecurityGroups": [
                            "{セキュリティグループID}"
                        ],
                        "AssignPublicIp": "DISABLED"
                    }
                },
                "PlatformVersion": "1.3.0"
            }
        }
    ]
}
```

後は上記で取得したパラメータを使って[run-task][run-task]を実行するだけです。

```bash
$ aws ecs run-task \
--cluster "arn:aws:ecs:ap-northeast-1:{アカウントID}:cluster/{クラスター名}" \
--task-definition "arn:aws:ecs:ap-northeast-1:{アカウントID}:task-definition/{ファミリー名}:{リビジョン番号}" \
--launch-type "FARGATE" \
--network-configuration "awsvpcConfiguration={subnets=[{サブネットID}],securityGroups=[{セキュリティグループID}],assignPublicIp=DISABLED}"
```


## AWS SDK for Java
上記と同じことをAWS SDKで実行すると下記のようになります。

```text:build.gradle.kts
dependencies {
    ...

    implementation("software.amazon.awssdk:ecs")
    implementation("software.amazon.awssdk:cloudwatchevents")
}
```

```kotlin
import software.amazon.awssdk.services.cloudwatchevents.CloudWatchEventsClient
import software.amazon.awssdk.services.cloudwatchevents.model.ListTargetsByRuleRequest
import software.amazon.awssdk.services.ecs.EcsClient
import software.amazon.awssdk.services.ecs.model.AwsVpcConfiguration
import software.amazon.awssdk.services.ecs.model.NetworkConfiguration
import software.amazon.awssdk.services.ecs.model.RunTaskRequest
import software.amazon.awssdk.services.ecs.model.RunTaskResponse


fun runTask(): RunTaskResponse {
    // CloudWatchイベントでスケジュールされたECSタスクのパラメータを取得
    val cloudWatchEvents = CloudWatchEventsClient.builder().build()
    val listTargetsByRuleRequest = ListTargetsByRuleRequest.builder()
        .rule("batch-schedule")
        .build()
    val listTargetsByRuleResponse = cloudWatchEvents.listTargetsByRule(listTargetsByRuleRequest)
    val eventRule = listTargetsByRuleResponse.targets().first()
    val ecsParameters = eventRule.ecsParameters()

    // CloudWatch Eventsのパッケージで定義されたNetworkConfigurationを元に
    // ECSのパッケージで定義されたNetworkConfigurationを作成する
    val networkConfiguration = ecsParameters.networkConfiguration().awsvpcConfiguration().let {
        NetworkConfiguration.builder().awsvpcConfiguration(
            AwsVpcConfiguration.builder()
                .subnets(it.subnets())
                .securityGroups(it.securityGroups())
                .assignPublicIp(it.assignPublicIp().name)
                .build()
        ).build()
    }

    // ECSタスクを実行する
    val ecs = EcsClient.builder().build()
    val runTaskRequest = RunTaskRequest.builder()
        .cluster(eventRule.arn())
        .taskDefinition(ecsParameters.taskDefinitionArn())
        .launchType(ecsParameters.launchType().name)
        .networkConfiguration(networkConfiguration)
        .build()
    return ecs.runTask(runTaskRequest)
}
```

NetworkConfigurationの詰め替えが若干面倒ではありますが、ListTargetsByRuleで取得したパラメータを元にRunTaskを実行するだけなのは特に変わらないですね。


## まとめ
実際はClientをシングルトンにしたり色々やると思いますが、記事にするにあたって諸々簡略化しました。
AWS CLIもAWS SDKも結局はAWS APIをコールしてるだけなので、AWS APIにもっと慣れ親しんだ方が理解が早そうだなぁと思う今日この頃です。


[list-targets-by-rule]:https://docs.aws.amazon.com/cli/latest/reference/events/list-targets-by-rule.html
[run-task]:https://docs.aws.amazon.com/cli/latest/reference/ecs/run-task.html