AWS Fargate の「タスクのスケジューリング」でバッチ用コンテナを定時実行する

## 概要
* AWS Fargate には、スケジュールルールに従ってタスクを実行できる機能がある。
* スケジュールルールは次の２通りある。どちらもスケジュールルールに従って毎回新しいコンテナが起動して終了する。
 * 固定された間隔で実行
 * Cron式
* この「タスクのスケジューリング」機能を使って、これまで EC2 上の crond で起動して実行していたバッチ処理を、コンテナ化して AWS Fargate に移行してみる。

## 今回 Fargate に移行するバッチの概要
* Elasticsearch の インデックスを１日１回削除するバッチ。
 * サーバのアクセスログを Fluentd で Elasticsearch に送ってKibanaで可視化している。１日分のアクセスログを１つのインデックスに格納していて、30日前のインデックスファイルを削除するバッチを１日1回実行している。

* バッチは bash スクリプトでこんな感じで書いている。

```bash
#!/bin/bash
ESDOMAIN="hogehoge.ap-northeast-1.es.amazonaws.com"
INDEXNAME=(
    "fastly-accesslog-"
    "varnish-accesslog-"
    "nginx-accesslog-"
)
DATA=`date -d '30 days ago' +"%Y-%m-%d"`
for INDEX in ${INDEXNAME[@]}; do
  echo "Delete: ${INDEX}${DATA}"
  curl -s -X DELETE https://${ESDOMAIN}/${INDEX}${DATA}
  echo -e "\n"
done
echo "Execution Result"
curl -s -X GET ${ESDOMAIN}/_aliases?pretty | grep -v '}' | sort
```

## バッチ処理を行うコンテナの Dockerfile 作成

```
FROM alpine:latest
RUN apk --no-cache add tzdata && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && apk del tzdata
RUN apk --no-cache add coreutils bash bash-completion curl
COPY del-index.sh /del-index.sh
RUN chmod u+x /del-index.sh
CMD /bin/bash /del-index.sh
```
Dockerfileの簡単な説明
#### 軽量な Alpine Linux を使う
* いろいろ削がれて軽量になっている。
* 今回のシェルスクリプトを実行するために必要なものを追加する。

#### Alpine Linux の timezone を JST に設定
* デフォルトは UTC になっているので、JST に設定する。
* Dockerfile 追加設定
<pre>
RUN apk --no-cache add tzdata && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && apk del tzdata
</pre>
* 参考: [https://wiki.alpinelinux.org/wiki/Setting_the_timezone](https://wiki.alpinelinux.org/wiki/Setting_the_timezone)

#### dataコマンド
* Alpine Linux のdataコマンドは、「date -d '30 days ago'」 を解釈してくれない。
なので、GNU coreutils の date をインストールする。
* Dockerfile 追加設定
<pre>
RUN apk --no-cache add coreutils
</pre>

#### bash
* Alpine Linux のデフォルトのシェルは sh なので、配列が使えない。bashで書いた次の部分でエラーになってしまう。

```bash

INDEXNAME=(
    "fastly-accesslog-"
    "Varnish-accesslog-"
    "nginx-accesslog-"
)
```

* シェルスクリプトを sh 用に書き直すのは面倒なので、bashを入れてしまう。
* Dockerfile 追加設定
<pre>
RUN apk --no-cache add bash bash-completion
</pre>

#### curl
* 今回のバッチは、curl で Elasticsearch の API を叩くので、curl をインストールする。
* Dockerfile 追加設定
<pre>
RUN apk --no-cache add curl
</pre>

#### bashスクリプト本体
* 今回は del-index.sh という名前で、上記のシェルスクリプトのファイルを作成する。これを Dockerfile と同じディレクトリに置く。
* docker build 時に del-index.sh ファイルを docker イメージの / にコピーして、実行権限を付与する。
* コンテナ起動時に、この del-index.sh を実行する。
* Dockerfile 追加設定
<pre>
COPY del-index.sh /del-index.sh
RUN chmod u+x /del-index.sh
CMD /bin/bash /del-index.sh
</pre>

## DockerイメージをECRへプッシュして、Fargete の設定を行う

* 今回のバッチは、Elasticsearch 側で接続元IPアドレスの制限を行うので、Fargateによって起動したコンテナは固定のIPアドレスをもたせる必要がある。
* [AWS Fargate で実行したコンテナに固定のグローバルIPアドレスを割り当てる](https://qiita.com/tonishy/items/810950dec4f1ae0d761f) を参考にして諸々設定する。

## Fargate タスクのスケジューリング 設定

#### Fargate タスクのスケジューリングの概要

* Fargate は CloudWatch イベントに応じたタスクのスケジューリングをサポートしている。
 * 参照：[Fargate は CloudWatch イベントに応じたタスクのスケジューリングをサポート](https://aws.amazon.com/jp/about-aws/whats-new/2018/08/aws-fargate-now-supports-time-and-event-based-task-scheduling/)

* スケジュールされたイベントルールは、特定の間隔 (N 分、時間、日ごとに実行)に設定することができるし、または、より複雑なスケジュールに設定する場合は、cron 式を使用することもできる（[ルールのスケジュール式](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html)）。
 * 参照：[タスクのスケジューリング (cron)](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/scheduled_tasks.html)

* すべてのスケジュールされたイベントは UTC タイムゾーンを使用している。
例えば、cron式で cron(0 10 * * ? *) と設定した場合、毎日午前 10:00 (UTC) に実行する。
日本時間で毎朝午前 10:00 に実行させるには、cron式で cron(0 1 * * ? *) と設定する（JST=UTC+9）。
 * 参照：[ルールのスケジュール式](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html)

#### タスクのスケジューリング設定例

* Amazon ECS クラスター設定画面 ⇒ タスクのスケジューリング ⇒ 作成
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/ee4c0dce-9470-0e70-11c0-dbc10b40bab1.png)

* スケジュールルール設定
今回は、毎日午前 6:00 (JST) に実行したいので、Cron 式は cron(0 21 * * ? *) 

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/fd9a4568-18b4-6a33-9d6a-60f6681a4880.png)

* ターゲット設定
起動タイプで「FARGATE」を選択する。
タスク定義で上記で作成したものを選択する。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148868/645ff663-7397-97d8-b56c-92fb275e6f04.png)

* VPC とセキュリティグループ
[AWS Fargate で実行したコンテナに固定のグローバルIPアドレスを割り当てる](https://qiita.com/tonishy/items/810950dec4f1ae0d761f) を参考

* 作成
以上で、毎日午前 6:00 (JST) に Elasticsearch のインデックスを削除するコンテナが起動して、処理を終えたらコンテナが消滅する。
実行ログは CloudWatch ログに送られる。


## 「タスクのスケジューリング」利用時の注意点
参照：[Amazon ECS の「タスクのスケジューリング」利用時の注意点](https://qiita.com/tonishy/items/a706b941a9046c71b9ea)


## おまけ
Slack にも実行結果を送りたい場合は、del-index.sh に Slack へ送る設定を追加する

```
#!/bin/bash
ESDOMAIN="hogehoge.ap-northeast-1.es.amazonaws.com"
INDEXNAME=(
    "fastly-accesslog-"
    "Varnish-accesslog-"
    "nginx-accesslog-"
)
DATA=`date -d '30 days ago' +"%Y-%m-%d"`

exec 3>&1
exec > /exec.log
echo "Elasticsearch delete index"
for INDEX in ${INDEXNAME[@]}; do
    echo "Delete Index: ${INDEX}${DATA}"
    curl -s -X DELETE https://${ESDOMAIN}/${INDEX}${DATA}
    echo -e "\n"
done
echo "curl -X GET https://${ESDOMAIN}/_aliases?pretty"
curl -s -X GET ${ESDOMAIN}/_aliases?pretty | grep -v '}' | sort
exec 1>&3

sed -i -e 's/\"//g' /exec.log

channel="hoge"
username="Container"
text="Elasticsearch delete index"
color="gray"
message=`cat /exec.log`
attachments="
{
    \"color\":\"${color}\",
    \"text\": \"${message}\"
}"
emoji=':envelope:'
url="https://hooks.slack.com/services/hoge/hogehoge"
payload="payload={\"channel\": \"${channel}\", \"username\": \"${username}\", \"text\": \"${text}\", \"attachments\":[${attachments}] , \"icon_emoji\": \"${emoji}\"}"
curl -s -m 5 -d "${payload}" $url

exit
```

* 標準出力をファイルへ出力する設定
<pre>
exec 3>&1
exec > /exec.log
〜
exec 1>&3
</pre>
* 実行結果ファイルからダブルクォーテーションを除外（Slack送信時に invalid_payload エラーとなるため）
<pre>
sed -i -e 's/\"//g' /exec.log

</pre>