AWS ECSを使ったバッチサーバ環境を試してみる


# はじめに
バッチの実行環境を作る場合、いろんな選択肢があります。

1. ec2を立ててその上でcronを動かす。
2. lambdaとcloudWatchEventの組み合わせ。一つのバッチに対して一つのlambdaがひもづくみたいな環境を作る。
3. AWS batchを使う。
4. ECSのサービス起動しているコンテナ内にcronを定義して実行
5. ECSのScheduleTaskを使う。

それぞれのメリデメは以下の記事にまとまっており、大変参考になりました。
この記事でも書いてある通り、ECSのScheduleTaskを使うのがバッチ処理にはすごく向いていると思い、試してみました。
http://tech.connehito.com/entry/2017/09/13/171914


# サンプルとして使うAPIサーバ
以下のapiを叩くと、slackに通知が行くような簡単なAPIを作りました。
コードはこちら => [github](https://github.com/pokotyan/ecs-batch)

このサーバーのデプロイとスケジュール実行にECSを使っていきます。

```terminal
curl ***.***.***.***/api/hoge
```
<img width="686" alt="スクリーンショット 2019-06-16 10.47.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/b9aa644a-c40b-805b-9f5a-23c86d25ad56.png">

```terminal
curl ***.***.***.***/api/sample
```
<img width="688" alt="スクリーンショット 2019-06-16 10.48.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/151f7d46-b6c5-f676-9465-c1f35bba9410.png">


# 事前準備
*ecs-cliのインストール*
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_CLI_installation.html

*ecs-cliで利用するアカウントの設定*
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cmd-ecs-cli-configure-profile.html

*あとで使うため、awsコンソール上でキーペアを作成*
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/get-set-up-for-amazon-ecs.html#create-a-key-pair

# クラスターの作成

```terminal
$ ecs-cli configure --region ap-northeast-1 --cluster batch
INFO[0000] Saved ECS CLI cluster configuration default.
$
```

# クラスタの起動
`--keypair` には事前準備で作成しておいたキーペアのキーペア名を入れます

```terminal
$ ecs-cli up --keypair ecs1 --capability-iam --instance-type t2.micro
INFO[0000] Using recommended Amazon Linux 2 AMI with ECS Agent 1.29.0 and Docker version 18.06.1-ce
INFO[0000] Created cluster                               cluster=batch region=ap-northeast-1
INFO[0001] Waiting for your cluster resources to be created...
INFO[0001] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0061] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0122] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
VPC created: vpc-06a96d66a28a309d5
Security Group created: sg-06963801d57d9b397
Subnet created: subnet-002a9338da5410dcf
Subnet created: subnet-0cf2a1265a7b2dae4
Cluster creation succeeded.
$
```

# docker-compose.ymlを使ってタスク定義を作成、起動する
※docker-compose.ymlがあるdirに移動してから実行

```
$ ecs-cli compose up
INFO[0000] Using ECS task definition                     TaskDefinition="ecs-batch:4"
INFO[0001] Starting container...                         container=2cd79d8c-9374-4a9f-a956-453246a0f079/app
INFO[0001] Describe ECS container status                 container=2cd79d8c-9374-4a9f-a956-453246a0f079/app desiredStatus=RUNNING lastStatus=PENDING taskDefinition="ecs-batch:4"
INFO[0013] Started container...                          container=2cd79d8c-9374-4a9f-a956-453246a0f079/app desiredStatus=RUNNING lastStatus=RUNNING taskDefinition="ecs-batch:4"
$
```


# APIサーバ自体のデプロイは完了
ここまでの手順で

```terminal
curl ***.***.***.***/api/hoge
curl ***.***.***.***/api/sample
```

を実行するとslackに通知が飛ぶサーバーを構築することはできました。
これをECSのScheduleTaskから実行できるようにしていきます。



# curlするだけのdockerを作る。
このcurlするだけのdockerイメージは `docker hub` か `ECR` にpushしておきます。

```docker
FROM alpine:3.7
RUN apk update && apk add curl
CMD curl --help
```

このイメージを何に使うかというと、このcurlをただ実行するだけのコンテナをECSのタスク定義として登録します。そしてこのイメージの `CMD` を上書きする形でECSのScheduleTaskを登録していくイメージです。

ScheduleTaskを定義する際、コンテナのコマンドの上書きができます。
`curl ***.***.***.***/api/hoge`, `curl ***.***.***.***/api/sample` にコンテナのコマンドを上書き、それらをスケジュール登録していきます。


# curl --helpを実行するタスク定義を作る
新しいタスク定義の作成 をクリック

<img width="1440" alt="スクリーンショット 2019-06-15 6.40.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/c679331e-a8ff-64f7-7ce0-702c98aeee8d.png">

EC2を選んで 次のステップ をクリック。（ただcurlを実行するだけで常に起動している必要がないのでFARGATEの方がいい気がする。）
<img width="1437" alt="スクリーンショット 2019-06-15 6.41.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/223b880c-13eb-fca8-3132-f130acc503ad.png">

タスク定義名: curl
<img width="1440" alt="スクリーンショット 2019-06-16 11.51.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/e13b521c-b10c-9a4a-5eed-ec1fb12abbc6.png">


コンテナの追加 を押す

<img width="1431" alt="スクリーンショット 2019-06-16 11.52.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/e7b9e29c-3d1c-430e-3439-cddedb63ff04.png">

コンテナ名: curl
イメージ: docker hubかECRのイメージを入力
メモリ制限: 128（埋めないと追加ボタン押せなかった）
追加をクリック
<img width="1118" alt="スクリーンショット_2019-06-16_11_54_04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/a552bd8e-86b9-2ec4-b7ec-068b854970ce.png">




作成 をクリック
<img width="1440" alt="スクリーンショット 2019-06-15 6.48.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/4df054b0-82c3-b189-0733-913f3b7f99f6.png">

ただ `curl --help` を実行するだけのタスク定義が作成される
<img width="1440" alt="スクリーンショット 2019-06-16 11.58.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/e1fa8477-536f-3183-97cc-b072f7d65d30.png">


# ECSのScheduleTaskを登録していく

クラスター => タスクのスケジューリング => 作成
<img width="1439" alt="スクリーンショット 2019-06-15 6.54.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/38eb8379-a493-3212-1ccc-7c25683e0541.png">

スケジュールルールの名前: sample
スケジュールルールの説明: sample API
スケジュール間隔は1分おき。※等間隔実行だけでなく、cron形式でも書くことができます。
<img width="1440" alt="スクリーンショット 2019-06-16 12.01.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/1e3d73bd-3f6e-a66b-290b-c9a36e36a89e.png">


ターゲットID: sample
タスク定義: 事前に作成しておいた `curl` を選択
<img width="1437" alt="スクリーンショット 2019-06-16 12.03.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/8946ddf5-3763-518f-eed7-8e74f8b2d27c.png">

コンテナの上書き。コマンドをsampleAPIを叩くcurlに変更して作成をクリック
<img width="1440" alt="スクリーンショット_2019-06-16_12_05_19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/0af6d922-bcb9-1983-13c2-c966b137935b.png">
注意するところして、[コマンドはカンマ区切りの文字列である必要があります。](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs_run_task_ec2.html)

> コマンドのオーバーライドの場合: [Command override] に、送信するコマンドオーバーライドを入力します。コンテナ定義で ENTRYPOINT が指定されていない場合は、引用符のない文字列のカンマ区切りリストである必要があります。例:


# バッチの実行確認
上と同じ感じで `curl ***.***.***.***/api/hoge` の方のScheduleTaskも登録します。
※hogeの方は5分間隔で作成しました。
<img width="1193" alt="スクリーンショット 2019-06-16 12.18.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/3e0a35f1-79e1-be0e-ccbc-28dabc693ea6.png">

slackを確認するとこの二つのバッチが指定した間隔通りに定期実行されていることがわかります。

<img width="683" alt="スクリーンショット 2019-06-16 12.16.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/9be969a1-ebff-a7b5-bdbd-504f8784c423.png">


# 最後に
ECSのタスクのスケジューリングの機能を使えば、awsの画面上からバッチの一覧を確認できますし、特定のバッチの有効/無効も画面上でぽちぽちするだけで完結します。

また、スケジュール系のバッチサーバはドメインロジックに深く関わってくる、落ちたらやばいサーバーとなることがほとんどだと思います。
node.jsで作ったサーバー(expressとか)などの場合、pm2を用いて常時サーバーが起動していることの担保をしたりしますが、たまにpm2自体が音もなく死んでいることがありました。

ECSを使う場合、サービスを定義すれば常時何個のタスク(コンテナの集合体)を起動しておくかを定義できるため、pm2からも解放される気がしてます。