ECS(Fargate)のバッチをCloudFormationで作成する

お試しで作ったものの備忘メモ

- 以下のリソースが作成されます
    - ECS
        - クラスタ
        - タスク定義
            - Fargateタイプ
            - ネットワークモードはawsvpc
        - タスクスケジュール（CloudWatch Events）
            - 毎日12時に動く
    - ECR
        - 直近2イメージのみ保持
- 以下が前提になります
    - プライベートサブネット、セキュリティグループが作成済
    - [ecsTaskExecutionRole](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_execution_IAM_role.html)が作成済
    - SSMのパラメーターストアにて`your_secrets_value`という名前のシークレットが作成済
        - 必要なければ`Secrets`を消せばOK
- 本日時点で、[AWS::Events::Rule EcsParametersの日本語記事](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-ecsparameters.html)を読むと`NetworkConfiguration`などの指定ができないように思えますが、[英語版](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-ecsparameters.html)には普通に書いてある...ということを伝えたくて書きました
  - フィードバックは送り済みなのでそのうち直るかも

```yaml:sample.yml
AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  ResourceName:
    Type: String
    Description: Resource Name
  RepositoryName:
    Type: String
    Description: Repository Name
  ImageTagName:
    Type: String
    Description: Image Tag Name
  TaskDefinitionCpu:
    Type: Number
    Description: TaskDefinition Cpu
  TaskDefinitionMemory:
    Type: Number
    Description: TaskDefinition Memory
  TaskExecutionSecurityGroups:
    Type: List<AWS::EC2::SecurityGroup::Id>
    Description: Service SecurityGroups
  TaskExecutionSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: Service Subnets

Resources:
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName:
        Ref: ResourceName

  Repository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName:
        Ref: RepositoryName
      LifecyclePolicy:
        # https://dev.classmethod.jp/cloud/aws/cfn-for-ecr-lifecyclepolicy/
        LifecyclePolicyText: |
          {
            "rules" : [
              {
                "rulePriority" : 1,
                "description" : "Delete more than 2 images",
                "selection" : {"countType" : "imageCountMoreThan", "countNumber" : 2, "tagStatus" : "any"},
                "action" : {"type" : "expire"}
              }
            ]
          }

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family:
        Ref: ResourceName
      Cpu:
        Ref: TaskDefinitionCpu
      Memory:
        Ref: TaskDefinitionMemory
      NetworkMode: awsvpc
      ExecutionRoleArn:
        Fn::Sub: arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name:
            Ref: ResourceName
          Image:
            Fn::Sub: ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RepositoryName}:${ImageTagName}
          Secrets:
            # https://dev.classmethod.jp/cloud/aws/ecs-secrets/
            - Name: YOUR_SECRETS_VALUE
              ValueFrom: your_secrets_value
          LogConfiguration:
            LogDriver: awslogs
            Options:
              "awslogs-region":
                Ref: AWS::Region
              "awslogs-stream-prefix": ecs
              "awslogs-group":
                Fn::Sub: /aws/ecs/${ResourceName}

  TaskSchedule:
    Type: AWS::Events::Rule
    Properties:
      Name: sample_task
      State: ENABLED
      ScheduleExpression: cron(0 3 * * ? *)
      Targets:
        - Id: sample_task
          Arn:
            Fn::GetAtt: [Cluster, Arn]
          RoleArn:
            Fn::Sub: arn:aws:iam::${AWS::AccountId}:role/ecsEventsRole
          EcsParameters:
            # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-ecsparameters.html
            TaskDefinitionArn:
              Ref: TaskDefinition
            LaunchType: FARGATE
            PlatformVersion: LATEST
            NetworkConfiguration:
              AwsVpcConfiguration:
                AssignPublicIp: DISABLED
                Subnets:
                  Ref: TaskExecutionSubnets
                SecurityGroups:
                  Ref: TaskExecutionSecurityGroups
          Input:
            # コマンドは任意に設定してください！
            Fn::Sub: |
              {
                "containerOverrides" : [
                  {
                    "name" : "${ResourceName}",
                    "command" : ["python3", "-m", "sample_task"]
                  }
                ]
              }

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName:
        Fn::Sub: /aws/ecs/${ResourceName}
      RetentionInDays: 30
```

以下のような設定ファイルを定義して

```ini:parameters.ini
ResourceName=sample-resource
RepositoryName=sample-repository
ImageTagName=smpl
TaskDefinitionCpu=256
TaskDefinitionMemory=512
TaskExecutionSecurityGroups=sg-xxxx
TaskExecutionSubnets=subnet-yyyy,subnet-zzzz
```

以下のような感じで実行すると楽

```shell:ターミナル
# 作成
$ aws cloudformation deploy \
  --stack-name sample-stack \
  --template-file sample.yml \
  --no-fail-on-empty-changeset \
  --parameter-overrides $(cat parameters.ini | tr '\n' ' ')

# オマケ
# DockerイメージをビルドしてECRにpush（99999999はアカウントID）
$ $(aws ecr get-login --no-include-email)
$ docker build -t 99999999.dkr.ecr.ap-northeast-1.amazonaws.com/sample-repository:smpl .
$ docker push 99999999.dkr.ecr.ap-northeast-1.amazonaws.com/sample-repository:smpl
```

結構面倒でしたが、実際に定義から環境ができると楽しいですね！