【AWS】ECR, ECS(Fargate), StepFunctions, CloudWatch でバッチ処理~前編~

# はじめに
本記事は、AWS FargateタスクとActivityステートマシンを利用して
・ソースの管理用リポジトリ（ECR）
・Activityをポーリングするワーカー
・アクティビティの処理内容
を実装しつつ、CloudWatchで定期実行するバッチ処理を作成するための概要（＆備忘録）です。

筆者自身、まだ理解が追いついていないと感じる部分はありますが、初学者の方の一助にでもなれば幸いです。

誤字・脱字などあればご指摘いただけると幸いです。m(_ _)m

# 前提
まず、前提となる技術要素のおさらいです。

## Amazon ECR
Amazon ECR とは Elastic Container Registry の略で、AWSが提供するDockerレジストリサービスです。IAMを使用する点を除けば、通常のDocker同様、
リポジトリを通してコンテナイメージを管理できます。
ECR レジストリはAWSアカウントごとに用意されており、レジストリ内にイメージ用のリポジトリを作成して各イメージを管理します。

## AWS Fargate
AWS Fargate は Amazon ECS(以下、ECS) 同様、コンテナベースのサービスとなります。
ECS が裏で EC2 インスタンスを起動しているのに対し、Fargate ではそれを行っていません。
つまり、コンテナベースサービスとはいえ実質サーバ(EC2)のリソースをきにする必要があったECSに対し、Fargateでは純粋にコンテナの上で稼働するアプリケーションの設計のみに注力できるという利点があります。
（「アプリが大きくなってきたし、コンテナのEC2のメモリを増やそうかな。。。」などと検討する必要が無いのはとても嬉しいですね）

詳細は[公式](https://aws.amazon.com/jp/blogs/aws/aws-fargate/)を確認すれば大体のイメージは掴めると思います。

ちなみに、マネジメントコンソールからクラスタ作成時に起動タイプを選択するだけで利用できます。

## AWS StepFunctions
AWS Step Functions とは
> 視覚的なワークフローを使用して、分散アプリケーションとマイクロサービスのコンポーネントを調整できるウェブサービス

です。個人的には、読むより触ってみる方が理解は早いかと思いますし、AWS の各種サービスの中でもそれほど習得の難易度は高くない方だと思います。
Step Functions を理解するために重要な概念であるステートマシンについてですが、これは主に

- ステートマシン全体への最初のInput
- ステートマシン内部での状態遷移
- ステートマシンから最後に出力されるOutput

で構成されています。
各情報はJSON形式で定義ファイルを作成することで設定していくことができますが、
マネジメントコンソール上では定義内容を以下の例のようにグラフィカルに確認することができます。

<img width="451" alt="スクリーンショット 2019-10-03 15.10.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265621/f9352d15-2354-e043-d725-95eeb32df775.png">

定義内容にミスがあっても気付きやすくていいですね。
ちなみに、図中の点線枠のブロックは各ステートとなりますが、定義ファイルの中では日本語も使用できるので、分かりやすい方を使えば良いと思います。

`States`内の実行パラメータである`Resource`には実行するジョブのARNを指定します。

- LambdaファンクションARN
- DynamoDBのAPI
- 別ステートマシンのARN

など様々なジョブを登録することができます。詳細はこちらも[公式](https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/connect-supported-services.html)を参照してください。

**アクティビティとは**
StepFunctionsの代表的な機能として、ステートマシンともう一つ、アクティビティがあります。
ステートマシンに設定できるLambdaファンクションは最大で15分（2019/10時点）の実行時間が設けられていますが、それを越えるとタイムアウトしてしまいます。
長時間かかることがあらかじめ分かっている処理などは、アクティビティを使用するのが得策でしょう。

[開発者ガイド](https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-activities.html)のマニュアルでは
> ワーカーによって作業が実行されるステートマシンでタスクを実行できるようにする AWS Step Functions 機能です。

と説明されていますが、個人的にはサンプルのソースを読む方が理解は早い気がします。

## Amazon CloudWatch Events
Amazon CloudWatch はログの収集、アラート検知、リソースモニタリングなど、運用・保守・監視といった業務にて主に使用されるサービスです。
CloudWatch Events は Amazon CloudWatch の機能の一つであり、主に次の二種類があります。

- 【イベントパターン】...特定のイベント(AWS環境内で起きた何らかの変化・イベント)をトリガーにしてターゲットを実行
- 【スケジュール】...一定の間隔、あるいは(AWS独自の)Cron構文にて定義されたターゲットを実行

ターゲットとはイベントを処理するための機能であり、EC2インスタンスの操作(起動/停止など)、Lambda関数の実行、StepFunctionsステートマシンの呼び出しなど様々なイベントをJSON形式で定義することで使用可能です。

# 本編
ではいよいよ本題となる「FargateとStepFunctionsアクティビティでバッチ処理を実装」していきたいと思います。

大まかな流れは以下のようになります。

1. ECR リポジトリの作成
2. アクティビティステートマシンの作成
    1. アクティビティの作成
    2. タスク定義の作成
    3. ステートマシンの作成
3. docker イメージの準備
    1. ソースのbuildとタグ付け
    2. リポジトリへpush
4. ECS クラスターの作成
5. ECS タスク定義の作成
    1. コンテナに登録するタスク定義ファイルの作成
    2. Fargateタスク定義の登録
6. CloudWatch Event 設定
    1. Fargateタスク起動用ルールの作成
    2. Fargateタスク起動用ターゲットの作成と割り当て
    3. アクティビティステートマシン用ルールの作成
    4. アクティビティステートマシン用ターゲットの作成と割り当て

少し長くなるので、本記事では `2-3. アクティビティステートマシンの作成`までを行い、以降は後編とします。

# 0. 事前準備

以降の手順はブラウザ/CLIでの手順となりますが、CLIにて実施する場合、aws-cli を別途インストールしておく必要があります。
awscli のインストールには大きく分けて`pip`を使用する方法、使用しない方法の2通りがあります。

各環境に合わせて [AWS CLI ](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html)をインストールしましょう。

以降の手順はWebコンソール経由での手順を記載しつつ、同様の作業を行うためのコマンドも記載しますが、コマンド手順で進める場合、CLI環境では

- プロファイルの設定が行われ、
- IAMリソースに適切なポリシーが都度割り当てられている

という前提にしております。エラーが出た場合などは必要なポリシーが割り当てられているか適宜確認してください（今回はIAM周りは省略します）。

# 1. ECR リポジトリの作成 : ブラウザ

マネジメントコンソールにログインしたら、ECRコンソールにて画面右上の [リポジトリの作成] をクリックし、空のリポジトリを作成します。
<img width="2526" alt="01_create_ecr_repository.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265621/b79dff27-1d3b-5c95-d4ed-d7078880b631.png">

リポジトリ名は任意ですが、ここでは`demo-repo`としておきます。

#### コマンド
```shell
aws ecr create-repository --repository-name $REPOSITORY_NAME
```

:warning: 以降、`$変数名`は任意の値を設定するか、適切な値に読み替えて下さい。

## 2. アクティビティステートマシンの作成
リポジトリが作成できたら、ワーカーからのポーリングを待機するためのアクティビティを作成します。

### 2-1. アクティビティの作成 : ブラウザ
Step Functionsコンソール（以下、SFNコンソール）から、アクティビティを作成します。
<img width="2492" alt="02_create_sfn_activity.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265621/82b7f875-1777-31be-7828-661cdf21643b.png">

ここでは`HelloWorldActivity`という名前にしておきます。

#### コマンド
```shell
aws stepfunctions create-activity --name $ACTIVITY_NAME
```

:warning: このステップで作成されたアクティビティのARNはメモしておきましょう。

### 2-2. タスク定義の作成 : ブラウザ
続いて、アクティビティをポーリングするためのタスク定義（JSON）を作成します。

ECRコンソールから [新しいタスク定義の作成] を選択し、起動タイプには "FARGATE" を指定します。
<img width="1701" alt="03_create_ecs_task_definition.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265621/c9f6ad1c-f39a-6679-6629-1e3bb195d34e.png">

<img width="1270" alt="04_choose_ecs_launch_type.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265621/9fb3c80b-fa4e-1e31-1397-760560f93a3c.png">

次に、タスクの詳細を定義していきます。
入力項目が多いため、JSON形式のサンプルの一部と、各パラメータの意味を# 以降に記載します。

```json:サンプル
{
  "Rule": "$RULE_NAME",                                      # CloudWatchEventRule名
  "Targets": [
    {
      "Id": "",                                              # 任意の文字列
      "Arn": "$CLUSTER_ARN",                                 # コンテナが起動するクラスタのARN。クラスタについては後編で。
      "RoleArn": "arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME", # 実行用のロールのARN
      "EcsParameters": {
        "TaskDefinitionArn": "$TASK_DEFINITION_ARN",
        "TaskCount": 1,
        "LaunchType": "FARGATE",                             # task 起動タイプ
        "NetworkConfiguration": {
          "awsvpcConfiguration": {
            "Subnets": [
              "subnet-XXXXXXXXXXXXXX"                        # 使用するSubnetID
            ],
            "SecurityGroups": [
              "sg-ABCABCABCABCABCAB",                        # 使用するSecurityGroup
              "sg-XYZXYZXYZXYZXYZXY"                         # 使用するSecurityGroup
            ],
            "AssignPublicIp": "ENABLED"                      # public IP アドレスの割り当て
          }
        }
      }
    }
  ]

```

定義ファイルの全体像と各パラメータの詳細な内容については[こちら](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definitions.html)を参照してください。

#### コマンド
```shell
# JSON 形式のSkeletonパラメータファイルを取得
aws ecs register-task-definition --generate-cli-skeleton

# 編集したJSONパラメータファイルを指定して、タスク定義を登録する
aws ecs register-task-definition --cli-input-json file://$TASK_DEFINITION_FILE_PATH
```

ここまでで、ワーカーからのポーリングを待つアクティビティと、ワーカーが実行する実際のタスクの定義を作成しました。

最後に、アクティビティを起動し待機させるためのステートマシンを作成します。

### 2-3. ステートマシンの作成 : ブラウザ

SFNコンソールに戻り、ステートマシンメニューを開きステートマシンを作成します。
ここでは、サンプル作成のためテンプレートからの作成を選び、`Hello World`を選択した状態にします。
<img width="964" alt="05_sfn_create_state_machine.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265621/2e78093e-d355-66b2-6802-cb316afe647b.png">

下にスクロールするとJSON形式の "ステートマシンの定義" が表示されているため、これを編集していきます。
<img width="952" alt="06_sfn_state-machine_definition.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265621/ceb94d29-f889-0b7b-8127-e922f992da12.png">

ステート要素内にある `"Results"`要素を `"Resource"`に書き換え、valueには `2-1. アクティビティの作成`で作成したアクティビティのARNを指定しておきます。

#### コマンド
```shell
# 作成
aws stepfunctions create-state-machine --name $STATE_MACHINE_NAME --definition file://$JSON_FILE --role-arn "arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME"

# 確認
aws stepfunctions describe-state-machine --state-machine-arn $STATE_MACHINE_ARN
```

ここまででアクティビティステートマシンの準備ができました。
後編では、サンプルコードを用いて、実際にアクティビティをポーリングしてタスクを実行するワーカーを作成していきます。
（近日中に投稿予定です）