CICD CodeDeployアーティファクトサイズリミットの対応(その２)


#1. 概要
AWS CodePipelineのCodeCommitによるCICDのBlue/Greenを実装するには、Deployステージに渡されたアーティファクトのサイズ制限があり、前回の投稿ではS3のPipelineに変更と別のappspec.yml、taskdef.jsonだけを格納するCodeCommitリポジトリを作成の二つ対処方法を紹介した。

https://qiita.com/zhangsd/items/af4093075ef2d2a97296

S3 Pipelineの対処方法には、作成されたCodeCommitタイプのPipelineのCloudFormationのソースファイルの変更作業が多いの短所があり、別のCodeCommitリポジトリの対処では改めてCodeCommitリポジトリの作成が必要もあるため、両対処とも少し手間が掛かる。

本文は、両対処方法の長所を連携し、S3バーケットを事前に作成し、Dockerファイルをこのバーケットにおいて置き、AWS::CodeBuild::Project段階まずCodeCommitリポジトリにある古いパッケージファイル（target/*.jarなど）cleanし、S3バーケットを利用してCodePipelineのSourceステージで作成されたアーティファクトのサイズの抑えることができ、別のCodeCommitリポジトリを作成しなくてもオリジナルのCodeCommitリポジトリのままでCICDを実現する方法を紹介する。

#2. 対処法
Step1：S3バーケットを作る。Dockerfileを格納する。
作成されたS3バーケットと格納されたDockerfile を下記の図をご参照ください。実際、このStepによりS3バーケットにDockerfileをアップロードしておけば、DockerfileをCodeCommitリポジトリに格納しなくてもCodeDeployを起動することが出来る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/4bc46185-b47d-98c5-1b1c-549339247a33.png)

```Dockerfile
FROM amazoncorretto:11-al2-jdk
EXPOSE 22
EXPOSE 80
EXPOSE 8080
RUN yum install -y iputils
RUN yum install -y net-tools
RUN yum install -y iputils
RUN yum -y install procps

RUN mkdir /usr/local/bin/api
RUN mkdir /usr/local/bin/batch
RUN mkdir /usr/local/bin/common
RUN mkdir /usr/local/bin/database
RUN mkdir /usr/local/bin/stubs
RUN mkdir /usr/local/bin/lib
ADD target/xxxxxxx_api-0.0.1-SNAPSHOT.jar /usr/local/bin/api/xxxxxxx_api-0.0.1-SNAPSHOT.jar
ENTRYPOINT ["java", "-jar", "/usr/local/bin/api/xxxxxxx_api-0.0.1-SNAPSHOT.jar"]
```
Step2：buildspec.ymlを編集する

Buildする際使用するDockerfileを、S3バーケットからコピーする、とDockerImageを作るにPathを“xxxxxxx-api/.”を指定することをご注意。
このbuildspec.ymlファイルをCodeBuildのProjectに埋め込み。

```buildspec.yml
version: 0.2
env:
  parameter-store:
    DOCKER_USER: dockerhub-user
    DOCKER_TOKEN: dockerhub-token
phases:
  install:
    runtime-versions:
      java: corretto11
    commands:
      - mvn clean
      - aws s3 cp s3://xxx-dev-xxxx-s3-01/Dockerfile-xxxx-api-s3 .
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email)
      - REPOSITORY_URI=xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx-dev-xxxx-app-ecr
      - COMMIT_HASH=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HAST:=latest}
      - echo Logging in to Docker Hub...
      - echo ${DOCKER_TOKEN} | docker login -u ${DOCKER_USER} --password-stdin
  build:
    commands:
      - echo Build started on `date`
      - mvn package --also-make --projects xxxxxxx-api -Dmaven.test.skip=true
      - echo Building the Docker image...
      # - docker build -t ${REPOSITORY_URI}:latest .
      - docker build -t $REPOSITORY_URI:latest xxxxxxx-api/. -f ./Dockerfile-xxxx-api-s3
      - 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...
      - printf '{"ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
artifacts:
  files:
    - imageDetail.json
```

Step3：CodeCommitのリポジトリに応用開発のソースコード、及びappspec.yml、taskdef.jsonを格納する。下記図は例である。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/dd11f181-804d-168c-4270-554abdef794e.png)

```appspec.yml
version: 0.0
Resources:
        - TargetService:
                Type: AWS::ECS::Service
                Properties:
                        TaskDefinition: "<TASK_DEFINITION>"
                        LoadBalancerInfo:
                                ContainerName: "xxx-dev-xxxx-app-container"
                                ContainerPort: "80"
```

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

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

```pipeline-s3.yml
AWSTemplateFormatVersion: 2010-09-09
Description: CodePipeline For ECS Fargate Blue/Green Deploy with PlaceHolder
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
  AwsXxxxxRegion:
    Type: String
    Default: xx-xxxxxxxxx-1
  Service:
    Type: String
    Default: xxxx
  Env:
    Type: String
    Default: dev

  ECRName:
    Type: String
    Default: xxx-dev-xxxx-app-ecr
  ContainerName:
    Type: String
    Default: xxx-dev-xxxx-app-container
  CodeCommitRepositoryName:
    Type: String
    Default: xxx-dev-xxxx-codecommit
  CodeDeployAppName:
    Type: String
    Default: xxx-dev-xxxx-deploy-app1
  CodeDeployGrpName:
    Type: String
    Default: xxx-dev-xxxx-deploy-grp1

  NameTagPrefix:
    Type: String
    Default: test
    Description: Prefix of Name tags.
  ServiceName:
    Type: String
    Default: xxxx
    Description: Prefix of Service tags.


# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Resources:
  # ------------------------------------------------------------#
  # IAM Roles
  # ------------------------------------------------------------#
  # CodeWatchEventを実行できるIAMRole
  CloudwatchEventRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub  cwe-tko-${Service}-${Env}-phld-role
      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-tko-${Service}-${Env}-phld-service-role
      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: '*'

# add policy to get parameter from SSM parameter store
              - Effect: Allow
                Action:
                  - ssm:DescribeParameters
                  - ssm:GetParameters
                Resource: '*'

  # CodePipelineに適用するIAMRole
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub codepipeline-tko-${Service}-${Env}-phld-role
      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

  # CodeDeployに適用するIAMRole
  CodeDeployRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'codedeploy.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
      RoleName: !Sub codedeploy-tko-${Service}-${Env}-phld-role

  # S3Bucket
  ArtifactBucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

  # 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'
                - ':'
                - !Ref CodeCommitRepositoryName
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - master
      Targets:
        - Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
          RoleArn: !GetAtt CloudwatchEventRole.Arn
          Id: codepipeline-AppPipeline

  # CodeBuild
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      ServiceRole: !Ref CodeBuildServiceRole
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          env:
            parameter-store:
              DOCKER_USER: dockerhub-user
              DOCKER_TOKEN: dockerhub-token
          phases:
            install:
              runtime-versions:
                java: corretto11
              commands:
                - mvn clean
                - aws s3 cp s3://xxx-dev-xxxx-s3-01/Dockerfile-xxxx-api-s3 .
            pre_build:
              commands:
                - echo Logging in to Amazon ECR...
                - $(aws ecr get-login --no-include-email)
                - REPOSITORY_URI=xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx-dev-xxxx-app-ecr
                - COMMIT_HASH=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | cut -c 1-7)
                - IMAGE_TAG=${COMMIT_HAST:=latest}
                - echo Logging in to Docker Hub...
                - echo ${DOCKER_TOKEN} | docker login -u ${DOCKER_USER} --password-stdin
            build:
              commands:
                - echo Build started on `date`
                - mvn package --also-make --projects xxxxxxx-api -Dmaven.test.skip=true
                - echo Building the Docker image...
                # - docker build -t ${REPOSITORY_URI}:latest .
                - docker build -t $REPOSITORY_URI:latest xxxxxxx-api/. -f ./Dockerfile-xxxx-api-s3
                - 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:$IMAGE_TAG
                - echo Writing imageDetail json...
                - printf '{"ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
          artifacts:
            files:
              - imageDetail.json

      Environment:
        PrivilegedMode: true
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:4.0
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: REPOSITORY_URI
            Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRName}
          - Name: ContainerName
            Value: !Ref ContainerName
          - Name: DOCKER_BUILDKIT
            Value: '1'
      Name: !Sub ${Service}-${Env}-phld-project1
#      Name: !Ref AWS::StackName

#  Following was original from pre_build
#                - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
#  Following 2 lines was the last part in build
#                - docker build -t $REPOSITORY_URI:$IMAGE_TAG .
#                - docker tag $REPOSITORY_URI:$IMAGE_TAG $REPOSITORY_URI:$IMAGE_TAG
#  Following 1 line was the last part in post_build
#                - docker push $REPOSITORY_URI:$IMAGE_TAG


# Create Code deploy application
#  CodeDeployApplication:
#    Type: AWS::CodeDeploy::Application
#    Properties:
#      ApplicationName: !Ref CodeDeployAppName
#      ComputePlatform: ECS

#  CodeDeployDeploymentGroup:
#    Type: AWS::CodeDeploy::DeploymentGroup
#    Properties:
#      ApplicationName: !Ref CodeDeployAppName
#      DeploymentGroupName: !Ref CodeDeployGrpName
#      DeploymentConfigName: CodeDeployDefault.ECSAllAtOnce
#      DeploymentStyle:
#        DeploymentType: BLUE_GREEN
#        DeploymentOption: WITH_TRAFFIC_CONTROL
#      ServiceRoleArn: !GetAtt CodeDeployRole.Arn
#      LoadBalancerInfo:
#        ElbInfoList:
#          - Name: !ImportValue DevXxxxAppNLB
#        TargetGroupInfoList:
#          - Name: !ImportValue DevXxxxAppTG1
#          - Name: !ImportValue DevXxxxAppTG2


  # ------------------------------------------------------------#
  # CodePipeline 
  # ------------------------------------------------------------#
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      Name: !Sub ${ServiceName}-phld-pipeline1
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: '1'
                Provider: CodeCommit
              Configuration:
                RepositoryName: !Ref CodeCommitRepositoryName
                PollForSourceChanges: false
                BranchName: master
              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
        # - Name: Approval
        #   Actions:
        #     - Name: Manual_Approval
        #       ActionTypeId:
        #         Category: Approval
        #         Owner: AWS
        #         Version: '1'
        #         Provider: Manual
        #       Configuration:
        #         CustomData: !Sub '${ServiceName} will be updated. Do you want to deploy it?'
        #         NotificationArn: arn:aws:sns:ap-xx-xxxx-2:xxxxxxxx:hogehoge
        - Name: Deploy
          Actions:
            - Name: Deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: '1'
                Provider: CodeDeployToECS
              Configuration:
                AppSpecTemplateArtifact: App
                AppSpecTemplatePath: appspec.yml
                TaskDefinitionTemplateArtifact: App
                TaskDefinitionTemplatePath: taskdef.json
                ApplicationName: !Ref CodeDeployAppName
                DeploymentGroupName: !Ref CodeDeployGrpName
#                StackName: xxxx-dev-ecs
#                ClusterName: !ImportValue DevXxxxEcsAppCluster
#                ServiceName: !ImportValue DevXxxxEcsAppService
                Image1ArtifactName: BuildOutput
                Image1ContainerName: IMAGE1_NAME
              RunOrder: 1
              InputArtifacts:
                - Name: App
                - Name: BuildOutput
              Region: !Ref AWS::Region
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
  PipelineDev:
    Description: Dev Xxxx Pipeline
    Value: !Ref Pipeline
    Export:
      Name: DevPhldAppPipeline
```

# 4. 作成されたアーティファクトのサイズ

CodePipelineのSourceステージで作成されたアーティファクト（本文はAPPの名前にした）のサイズは、CloudFormationにて作成されたRsourceのS3で調べられる。本文の場合は609KBになった。

# 5. まとめ
CodePipelineのCodeCommitによるCICDのBlue/Greenを実装するには、現状のDeployステージに渡されたアーティファクトの3MBサイズ制限に対し、本文のS3バーケットを事前に作成、Dockerファイルをこのバーケットにおいて置き、CodeBuildの作成された中間JarファイルもS3バーケットを利用し、CodePipelineのSourceステージで作成されたアーティファクトのサイズの抑えることができた。この方法では、オリジナルのCodeCommitリポジトリままでCICDを実現することができる。