Batch JobのCICDを実現する 

# 1. 概要

AWS CodePipelineのCodeCommitによるCICDのBlue/Greenを実装するには、Deploy(CD)するために、Deploy ApplicationとDeploy Groupの定義が必要である。Deploy Groupを定義するに、ECS サービスのELB（Load Balancer）とTargetGroupの指定が必要である。下記の図をご参照ください。

然し、ECSのスケジュールによる起動するBatchジョッブの殆どは毎日定時に実行するタスクに従いコンテナを作成、実行終了したらコンテナを削除される仕組みである。そのため、タスク定義を変更（パラメータの追加）する場合、CodeCommitのCICD（CodeDeployToECS）が使えない。
そこで、本文はCIのCodeBuilder段階を変更活用し、buildspec.ymlにタスクコンテナの新しい定義ファイルを読み込みと実行し、ECSの新しいタスク定義を作成し、次のBatchジョッブを起動するに更新されたタスク定義を利用して、新しいタスクを実現する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/81460201-afa3-38b5-6d0e-f910f9096cf5.png)

# 2. 対処法

Step1：更新するタスク定義ファイルtaskdef-update.jsonを作成する。
“aws ecs register-task-definition --cli-input-json”による新タスク定義をするため、元のタスク定義ファイルtaskdef.json（ECSのGUIにより現タスクの定義をJSONフォーマットで表示して、Copy/Pasteによる作成される）から新しいタスク定義ファイルを作成する。”aws ecs register-task-definition”のインプットファイルに対応しないフィールドがあるので、元のtaskdef.jsonから以下のフィールドを削除する。

インプットファイルによるtask-definitionの登録は下記の文章も参照になれる。
https://dev.classmethod.jp/articles/describe-task-definition-to-register-task-definition/

|No.|削除するフィールド(Tag)|値
|---|---|---|
|1|ipcMode|null|
|2|placementConstraints|[]|
|3|compatibilities|EC2, FARGATE|
|4|taskDefinitionArn(must delete)|arn:aws:ecs:xx-xxxxxx-1:xxxxxxxxxxxx:task-definition/xxxx-dev-xxxx-batch-task:5|
|5|requiresAttributes|targetId, etc|
|6|pidMode|null|
|7|revision|5|
|8|status|ACTIVE|
|9|inferenceAccelerators|null|
|10|proxyConfiguration|null|
|11|volumes|[]|
|12|IMAGE|IMAGE1_NAMEから実値に戻る|
|13|containerDefinitions|値がnullであるlinuxParameters等を削除する|

作成された新しいタスク定義ファイルの例を下記taskdef-update.jsonになる。

Step2：buildspec.ymlにregister-task-definitionとECSのサービス更新を実行する。
“aws ecs register-task-definition --cli-input-json”による新タスク定義をするとタスク定義ファイルのバージョンを更新される。次のBatchを起動するに新しいバージョンの定義ファイルを呼び出すため、ECSのサービス更新が必要である。以下の２つコマンドから実現する。

      - aws ecs register-task-definition --cli-input-json file://taskdef-update.json
      - aws ecs update-service --cluster xxxx-dev-fargate --service xxxx-dev-xxxx-batch-service --task-definition xxxx-dev-xxxx-batch-task

完成したbuildspec.ymlの例を下記になる。

```buildspec.yml
version: 0.2
env:
  parameter-store:
    DOCKER_USER: dockerhub-user
    DOCKER_TOKEN: dockerhub-token
phases: 
  install:  
    runtime-versions:  
      java: corretto11
  pre_build:
    commands:
      - echo mvn clean...
      - mvn clean
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email)
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - echo Logging in to Docker Hub...
      - echo ${DOCKER_TOKEN} | docker login -u ${DOCKER_USER} --password-stdin
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - /bin/sh mvnw  -Dmaven.test.skip=true -DincludeScope=runtime dependency:copy-dependencies package
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest 
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo Writing imageDetail json...
      - echo "{\"name\":\"${ContainerName}\",\"ImageURI\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}" > imageDetail.json
      - echo Update taskdefinition of fargate
      - aws ecs register-task-definition --cli-input-json file://taskdef-update.json
      - echo Update batch service, force to use latest task definitio revision
      - aws ecs update-service --cluster xxxx-dev-fargate --service xxxx-dev-xxxx-batch-service --task-definition xxxx-dev-xxxx-batch-task

artifacts:
  files: imageDetail.json
```

Step3：動作確認。
次のBatchJobを起動されたら、ECSのコンテナ定義を開いて確認する。

#3. 動作確認
目的はタスクを実現するコンテナのCPUを256から512に、メモリーを2048に、設定するパラメータXXX_XXX_TEST_TESTを追加する。使用するtaskdef.jsonとtaskdef-update.jsonは下記になる。

```(abstract)taskdef.json
"taskDefinitionArn": "arn:aws:ecs:xx-xxxxxx-1:xxxxxxxxxxxx:task-definition/xxxx-dev-xxxx-batch-task:5",
  "family": "xxxx-dev-xxxx-batch-task",
  "requiresAttributes": [
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
    },
…
  "networkMode": "awsvpc",
  "cpu": "256",
  "revision": 5,
  "status": "ACTIVE",
  "inferenceAccelerators": null,
  "proxyConfiguration": null,
  "volumes": []
}


```

taskdef-update.json:

```taskdef-update.json
{
  "executionRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/xxxx-dev-xxxx-ecs-taskexec-role",
  "containerDefinitions": [
    {
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/logs/xxxx-dev-xxxx-ecs-batch",
          "awslogs-region": "xx-xxxxxx-1",
          "awslogs-stream-prefix": "xxxx-dev-xxxx"
        }
      },
      "portMappings": [
        {
          "hostPort": 8080,
          "protocol": "tcp",
          "containerPort": 8080
        }
      ],
      "cpu": 0,
      "secrets": [
        {
          "valueFrom": "xxxx-DEV-XXXX_SPRING_DATASOURCE_URL",
          "name": "xxxx.spring.datasource.url"
        },
…
        {
          "valueFrom": "xxxx-DEV-XXXX_BATCH_API_TRANSACTIONID",
          "name": "xxxx.batch.api.transactionId"
        }
      ],
      "memoryReservation": 128,
      "image": "xxxxxxxxxxxx.dkr.ecr.xx-xxxxxx-1.amazonaws.com/xxxx-dev-xxxx-batch:latest",
      "name": "xxxx-dev-xxxx-batch-container"
    }
  ],
  "memory": "1024",
  "taskRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/xxxx-dev-xxxx-ecs-task-role",
  "family": "xxxx-dev-xxxx-batch-task",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "networkMode": "awsvpc",
  "cpu": "512"
}
```

taskdef-update.jsonの新しいパラメータによる起動されたコンテナは下記図で確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/039aa857-a86b-db48-fbdc-14ae062730a8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/807b4fd1-b445-95a7-8350-61de385bd1ed.png)

#4. 完成したCloudFormationYamlファイル

参照するため、完成したCodePipelineを作成するCloudFormationのCICD　Yamlファイルを添付する。

```cicd-batch.yml
AWSTemplateFormatVersion: 2010-09-09
Description: CodePipeline For ECS Fargate Rolling Update
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
  AwsTokyoRegion:
    Type: String
    Default: xx-xxxxxx-1
  Service:
    Type: String
    Default: xxxx
  Env:
    Type: String
Default: dev

# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Resources:

  # ------------------------------------------------------------#
  # IAM Roles
  # ------------------------------------------------------------#
  # CodeWatchEvent??????IAMRole
  CloudwatchEventRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub  cwe-xxx-${Service}-${Env}-role-batch
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: CloudWatchEventsPipelineExecution
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: codepipeline:StartPipelineExecution
                Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}

  # CodeBuild?????IAMRole
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub codebuild-xxx-${Service}-${Env}-service-role-batch
      Path: /
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: SampleCodeBuildAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource: '*'
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
              - Effect: Allow
                Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
              - Effect: Allow
                Action:
                  - codebuild:CreateReportGroup
                  - codebuild:CreateReport
                  - codebuild:UpdateReport
                  - codebuild:BatchPutTestCases
                  - codebuild:BatchPutCodeCoverages
                Resource: '*'
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchCheckLayerAvailability
                  - ecr:GetDownloadUrlForLayer
                  - ecr:GetRepositoryPolicy
                  - ecr:DescribeRepositories
                  - ecr:ListImages
                  - ecr:DescribeImages
                  - ecr:BatchGetImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload
                  - ecr:PutImage
                Resource: '*'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMFullAccess

  # CodePipeline?????IAMRole
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub codepipeline-xxx-${Service}-${Env}-role-batch
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: SamplePipeline
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - iam:PassRole
                Resource: '*'
                Effect: Allow
                Condition:
                  StringEqualsIfExists:
                    iam:PassedToService:
                      - ecs-tasks.amazonaws.com
              - Resource:
                  - !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketVersioning
              - Action:
                  - codecommit:CancelUploadArchive
                  - codecommit:GetBranch
                  - codecommit:GetCommit
                  - codecommit:GetRepository
                  - codecommit:GetUploadArchiveStatus
                  - codecommit:UploadArchive
                Resource: '*'
                Effect: Allow
              - Action:
                  - codedeploy:CreateDeployment
                  - codedeploy:GetApplication
                  - codedeploy:GetApplicationRevision
                  - codedeploy:GetDeployment
                  - codedeploy:GetDeploymentConfig
                  - codedeploy:RegisterApplicationRevision
                  - codedeploy:*
                Resource: '*'
                Effect: Allow
              - Action:
                  - elasticbeanstalk:*
                  - ec2:*
                  - elasticloadbalancing:*
                  - autoscaling:*
                  - cloudwatch:*
                  - sns:*
                  - cloudformation:*
                  - rds:*
                  - sqs:*
                  - ecs:*
                Resource: '*'
                Effect: Allow
              - Action:
                  - codebuild:BatchGetBuilds
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuildBatches
                  - codebuild:StartBuildBatch
                Resource: '*'
                Effect: Allow
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMFullAccess

  # S3Bucket
  ArtifactBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub xxx-${Env}-${Service}-s3-cicd-batch-artifactbucket
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      BucketEncryption: 
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      Tags: 
        - Key: Name
          Value: !Sub xxx-${Env}-${Service}-s3-cicd-batch-artifactbucket
        - Key: Service
          Value: !Ref Service
        - Key: Environment
          Value: !Ref Env

  # CloudWatchEvent??????
  AmazonCloudWatchEventRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - CodeCommit Repository State Change
        resources:
          - Fn::Join:
              - ''
              - - 'arn:aws:codecommit:'
                - !Ref 'AWS::Region'
                - ':'
                - !Ref 'AWS::AccountId'
                - ':'
                - !Sub xxx-${Env}-${Service}-codecommit-batch
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - main
      Targets:
        - Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
          RoleArn: !GetAtt CloudwatchEventRole.Arn
          Id: codepipeline-BatchPipeline

  # CodeBuild
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      ServiceRole: !Ref CodeBuildServiceRole
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
      Environment:
        PrivilegedMode: true
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:4.0
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: /CodeBuild/AWS_DEFAULT_REGION_XXXX
            Type: PARAMETER_STORE
          - Name: IMAGE_REPO_NAME
            Value: /CodeBuild/IMAGE_REPO_NAME_XXXX_BATCH
            Type: PARAMETER_STORE
          - Name: AWS_ACCOUNT_ID
            Value: /CodeBuild/AWS_ACCOUNT_ID_XXXX
            Type: PARAMETER_STORE
          - Name: IMAGE_TAG
            Value: /CodeBuild/IMAGE_TAG_XXXX
            Type: PARAMETER_STORE
          - Name: REPOSITORY_URI
            Value: /CodeBuild/REPOSITORY_URI_XXXX_BATCH
            Type: PARAMETER_STORE
          - Name: ContainerName
            Value: /CodeBuild/ContainerName_XXXX_BATCH
            Type: PARAMETER_STORE
          - Name: DOCKERHUB_USER
            Value: /CodeBuild/DOCKERHUB_USER_XXXX
            Type: PARAMETER_STORE
          - Name: DOCKERHUB_PASS
            Value: /CodeBuild/DOCKERHUB_PASS_XXXX
            Type: PARAMETER_STORE
      Name: !Sub ${Service}-${Env}-cicd-batch-project

  # ------------------------------------------------------------#
  # CodePipeline 
  # ------------------------------------------------------------#
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      Name: !Sub ${Service}-cicd-batch-pipeline
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: '1'
                Provider: CodeCommit
              Configuration:
                RepositoryName: !Sub xxx-${Env}-${Service}-codecommit-batch
                PollForSourceChanges: false
                BranchName: main
              RunOrder: 1
              OutputArtifacts:
                - Name: App
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: '1'
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildProject
              RunOrder: 1
              InputArtifacts:
                - Name: App
              OutputArtifacts:
                - Name: BuildOutput
```

CodeCommitリポジトリは下記図になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/8674009d-2622-47ad-25ed-fba8ae2de200.png)

#5. まとめ
本文のCICD buildspec.ymlファイルからタスク定義ファイルを実行するの活用により、BatchジョッブのようなELBやTargetGroupが付いていないBatchジョッブもCodePipelineのCICDのBlue/Green（CodeDeployToECS）も実装できる。ご実務に参照になれば幸いである。