バッチ処理用のECS Fargateタスクをほぼゼロから作る

RunTask APIでタスクが起動できるようにするまでの手順があやふやだったので。
VPC, サブネットのみがある状態から好きな処理をRunTaskできるようにするまでの手順。


## Dockerイメージ作成まで

Amazon Linux 2インスタンス起動。ECSへのアクションはIAMロールで許可しておく。

```bash
sudo yum install -y docker
sudo service docker start
```

node用の`Dockerfile`の作り方は[node公式](https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/)が詳しい。

```Dockerfile:Dockerfile
FROM node:14

WORKDIR /usr/src/app
COPY . .

CMD [ "node", "index.js" ]
```

`index.js`を`Dockerfile`と同じディレクトリに作る。1秒sleepするだけ。

```Javascript:index.js
const main = async () => {
    console.log("start");
    await sleep(1000);
    console.log("end");
}

const sleep = (waitSec) => {
    return new Promise( (resolve) => {
        setTimeout(() => { resolve() }, waitSec);
    });
} 

main();
```

ビルドしてrunすると実行される。

```bash
sudo docker build . -t sleep-batch
sudo docker run sleep-batch
```

ECRコンソールからレポジトリを作成しておき、表示されるコマンドを順次打つだけでECRへ上記のイメージがpushできる。

## ECSタスク定義をCloudFormationで作成するまで

### 前提条件
- VPC は事前作成されていること
- サブネットも事前作成されていること、多分インターネットアクセスが必要？(特に確かめておらず)


### テンプレート
[この記事](https://qiita.com/okubot55/items/02911f81fec614255f90)をすごく参考にした。

作成されるリソース

- アウトバウンドのみ全許可のSG
- タスクのログ出力用のCloudWatchロググループ
- ECSクラスター
- ECSタスク定義

※ 先ほど作ったdockerイメージを使ってサービスもデプロイすると、ひたすらタスクが起動と停止を繰り返して気持ち悪いのでコメントアウト。書く段階であまりサービスの意味がわかっていなかった...

```yaml:template.yaml
Parameters:
  ECSClusterName:
    Type: String
    Default: "ECS-cluster"
  
  ECSTaskName:
    Type: String
    Default: "ECS-task"
  
  ECSContainerName:
    Type: String
    Default: "ECS-container"

  ECSServiceName:
    Type: String
    Default: "service"

  ECSTaskCPUUnit:
    AllowedValues: [ 256, 512, 1024, 2048, 4096 ]
    Type: String
    Default: "256"

  ECSTaskMemory:
    AllowedValues: [ 512, 1024, 2048, 4096 ]
    Type: String
    Default: "512"
  
  ECSImage:
    Type: String
    Description: "Image URL or name"
  
  TargetVpc:
    Type: AWS::EC2::VPC::Id

  # TargetSubnets:
  #   Type: List<AWS::EC2::Subnet::Id>

Resources:
  # SGはアウトバウンドのみ
  ECSSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: !Ref TargetVpc
      SecurityGroupEgress:
      - IpProtocol: -1
        CidrIp: 0.0.0.0/0
      GroupDescription: "ECS SG created by CloudFormation"

  # ContainerDefinitions.Options で　awslogs-create-group: true にするためにはタスク実行ロールに reateLogGroup 権限をつける必要があるという噂があるので、ロググループを作る
  ECSLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub "/ecs/${ECSTaskName}"
      RetentionInDays: 30

  # クラスター
  ECSCluster:
    Type: 'AWS::ECS::Cluster'
    Properties:
      ClusterName: !Ref ECSClusterName

  # タスク
  ECSTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      Cpu: !Ref ECSTaskCPUUnit
      ExecutionRoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole"
      Memory: !Ref ECSTaskMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      
      # コンテナ
      ContainerDefinitions:
        - Name: !Ref ECSContainerName
          Image: !Ref ECSImage
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: !Ref "AWS::Region"
              awslogs-stream-prefix: "ECS"

  # # サービス
  # ECSService:
  #   Type: AWS::ECS::Service
  #   Properties:
  #     Cluster: !Ref ECSCluster
  #     LaunchType: FARGATE
  #     NetworkConfiguration:
  #      AwsvpcConfiguration:
  #          AssignPublicIp: ENABLED
  #          SecurityGroups:
  #            - !Ref ECSSecurityGroup
  #          Subnets: !Ref TargetSubnets
  #     ServiceName: !Ref ECSServiceName
  #     TaskDefinition: !Ref ECSTaskDefinition
```

### その後
力尽きたのでコンソールからタスク実行した。AssignPublicIpを明示的にtrueにしないと以下の理由でタスクの起動に失敗していたので、やっぱりインターネット接続が必要なのではなかろうか...？

```
ResourceInitializationError: unable to pull secrets or registry auth: pull command failed: : signal: killed
```
あとはStep Functionsなりで定期実行するなりするつもり。