CodePipelineを使ってCodeCommitプッシュをトリガーにしてECRにイメージをプッシュする

目次

CodePipelineを使ってCodeCommitプッシュをトリガーにしてECRにイメージをプッシュする

CodePipelineを使用することによって、CI/CD構成を構築することができます。

今回はCodeCommitにコードをプッシュした際に、CodeBuildでDockerイメージをビルドし、ECRにプッシュするパイプラインを構成すること目指します。

構築する環境

Diagram of use CodePipeline to trigger CodeCommit pushes to push images to ECR.

CodePipelineを構成し、2つのリソースを連携させます。
ソースとビルドの2つのステージを定義します。

1つ目はCodeCommitです。
Gitリポジトリとして使用します。

2つ目はCodeBuildです。
CodeCommitにプッシュされたコードから、Dockerイメージをビルドします。
ビルドしたイメージをECRにプッシュします。

S3バケットを作成します。
CodePipelineで生成されるアーティファクトを格納するために使用します。

SSMパラメータストアにDockerHubアカウント情報を保存します。
DockerBuildでイメージを生成する際に、DockerHubにサインインした上でベースイメージをプルするために、これらを使用します。

CodePipelineが開始されるきっかけですが、CodeCommitへのプッシュを条件とします。
具体的には、EventBridgeに上記を満たすルールを用意します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレート等を配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/075

テンプレートファイルのポイント解説

S3バケット

Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref Prefix
      AccessControl: Private
Code language: YAML (yaml)

CodePipelineで生成されるアーティファクトを格納するバケットです。
特別な設定は不要です。

SSMパラメータストア

Resources:
  SSMParameterDockerHubPassword:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub "${Prefix}-DockerHubPassword"
      Type: String
      Value: !Ref DockerHubPassword

  SSMParameterDockerHubUsername:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub "${Prefix}-DockerHubUsername"
      Type: String
      Value: !Ref DockerHubUsername
Code language: YAML (yaml)

DockerHubにサインインするためのアカウント情報です。
ユーザ名・パスワードをパラメータとして登録します。

ECR

Resources:
  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Ref Prefix
Code language: YAML (yaml)

自作イメージを保存するリポジトリです。
特別な設定は不要です。

CodeCommit

Resources:
  CodeCommitRepository:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref Prefix
Code language: YAML (yaml)

コードリポジトリです。
特別な設定は不要です。

CodeBuild

Resources:
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Cache:
        Type: NO_CACHE
      Environment:
        ComputeType: !Ref ProjectEnvironmentComputeType
        EnvironmentVariables:
          - Name: DOCKERHUB_PASSWORD
            Type: PARAMETER_STORE
            Value: !Ref SSMParameterDockerHubPassword
          - Name: DOCKERHUB_USERNAME
            Type: PARAMETER_STORE
            Value: !Ref SSMParameterDockerHubUsername
        Image: !Ref ProjectEnvironmentImage
        ImagePullCredentialsType: CODEBUILD
        Type: !Ref ProjectEnvironmentType
        PrivilegedMode: true
      LogsConfig:
        CloudWatchLogs:
          Status: DISABLED
        S3Logs:
          Status: DISABLED
      Name: !Ref Prefix
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Source:
        Type: CODEPIPELINE
        BuildSpec: !Sub |
          version: 0.2

          phases:
            pre_build:
              commands:
                - echo Logging in to Amazon ECR...
                - aws --version
                - aws ecr get-login-password --region ${AWS::Region} | docker login --username AWS --password-stdin ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com
                - REPOSITORY_URI=${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepositoryName}
                - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
                - IMAGE_TAG=${!COMMIT_HASH:=latest}
                - echo Logging in to Docker Hub...
                - echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin
            build:
              commands:
                - echo Build started on `date`
                - echo Building the Docker image...
                - 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
      Visibility: PRIVATE
Code language: YAML (yaml)

ポイントとなるパラメータを取り上げます。

Artifactsプロパティで、CodeBuildで生成されるアーティファクトに関する設定を行います。
内部のTypeプロパティでアーティファクトのタイプを設定します。
今回はCodeBuildを後述のCodePipeline内で実行するため、「CODEPIPELINE」を指定します。

Environmentプロパティでビルド環境を定義します。
ComputeTypeプロパティで、ビルド環境に割り当てるリソースを設定します。今回は最小環境を意味する「BUILD_GENERAL1_SMALL」を指定し、メモリ3GB、2vCPUのリソースを確保します。
EnvironmentVariablesプロパティで、ビルド環境用の環境変数を定義できます。今回は先述の2つのSSMパラメータストアの値を環境変数として設定します。Nameプロパティで変数名を指定できます。Typeプロパティに「PARAMETER_STORE」を指定することで、SSMパラメータストアの値を参照できます。Valueプロパティに参照するSSMパラメータストアのパラメータ名を指定します。
Imageプロパティで、ビルド環境として使用するDockerイメージを設定します。今回は「aws/codebuild/amazonlinux2-aarch64-standard:2.0」を指定し、ARM版Amazon Linux 2のイメージを使用します。
ImagePullCredentialsTypeプロパティで、ビルド環境用イメージをプルするために使用するクリデンシャルタイプを設定します。先述のCodeBuild用イメージを使用する場合は、「CODEBUILD」を指定します。
PrivilegedModeプロパティはtrueを設定します。Dockerコンテナ内でDockerデーモンを実行できるようにするためです。これはDockerイメージをビルドするために必要です。
Typeプロパティでビルド環境のタイプを設定します。今回は「ARM_CONTAINER」を指定し、ARM版コンテナを使用します。

Sourceプロパティでビルドで使用するソースコードや、ビルド仕様ファイル(buildspec.yml)に関する設定です。
Typeプロパティでソースコードのリポジトリのタイプを設定します。今回はCodePipelineの中でCodeBuildを使用しますので、「CODEPIPELINE」を指定します。
BuildSpecプロパティはbuildspec.ymlに関するパラメータです。今回は本プロパティにbuildspec.ymlの内容を直接記載します。内容はAWS公式ページを参考にして記載しました。

https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/ecs-cd-pipeline.html#code-build-perms

1つ変更点があります。イメージをビルドする時に、DockerHubからベースイメージをプルしますが、DockerHubにサインインする処理を加えた点です。サインインなしでDockerHubからイメージをプルしようとすると、プルに失敗する場合があるためです。以下のページを参考にして記載しました。

https://dev.classmethod.jp/articles/codebuild-has-to-use-dockerhub-login-to-avoid-ip-gacha/

CodeBuild用のIAMロールを確認します。

Resources:
  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
      Policies:
        - PolicyName: PipelineExecutionPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ssm:GetParameters
                Resource:
                  - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SSMParameterDockerHubPassword}"
                  - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SSMParameterDockerHubUsername}"
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${BucketName}"
                  - !Sub "arn:aws:s3:::${BucketName}/*"
Code language: YAML (yaml)

ECRにイメージをプッシュするためにAWS管理ポリシーAmazonEC2ContainerRegistryPowerUserをアタッチします。
環境変数を設定する際に、SSMパラメータストアからパラメータを取得しますので、これを許可します。
イメージをビルドする際に、S3バケットに格納されたCodePipelineアーティファクトを取得しますので、これを許可します。

CodePipeline

Resources:
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref BucketName
        Type: S3
      Name: !Ref Prefix
      RoleArn: !GetAtt CodePipelineRole.Arn
      Stages:
        - Actions:
            - ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: 1
              Configuration:
                BranchName: !Ref BranchName
                OutputArtifactFormat: CODE_ZIP
                PollForSourceChanges: false
                RepositoryName: !GetAtt CodeCommitRepository.Name
              Name: SourceAction
              OutputArtifacts:
                - Name: !Ref PipelineSourceArtifact
              Region: !Ref AWS::Region
              RunOrder: 1
          Name: Source
        - Actions:
            - ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: !Ref CodeBuildProject
              InputArtifacts:
                - Name: !Ref PipelineSourceArtifact
              Name: Build
              OutputArtifacts:
                - Name: !Ref PipelineBuildArtifact
              Region: !Ref AWS::Region
              RunOrder: 1
          Name: Build
Code language: YAML (yaml)

ポイントとなるパラメータを取り上げます。

ArtifactStoreプロパティで、パイプラインを実行中に生成されるアーティファクトを格納するS3バケットを指定します。

Stagesプロパティで、パイプラインを構成するステージを定義します。
今回は2つのステージを定義します。

1つ目のステージはソースステージです。
以下のAWS公式ページを参考にして設定します。

https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/action-reference-CodeCommit.html

ActionTypeIdプロパティで、CodeCommitがパイプラインのソースとなるように設定します。
Configurationプロパティで、ソースとしてCodeCommitを扱う上でのパラメータを設定します。RepositoryNameおよびBranchNameプロパティで、参照するCodeCommitリポジトリ名およびブランチ名を指定します。OutputArtifactFormatプロパティはデフォルトの「CODE_ZIP」を指定します。PollForSourceChangesプロパティはパイプラインを開始するきっかけに関するパラメータです。これをtrueとすると、CodePipelineからCodeCommitへポーリングすることで、パイプラインを開始すべきアクティビティを検出します。今回は後述のEventBridgeを使用してアクティビティを検出するため、本パラメータはfalseとします。
OutputArtifactsプロパティはソースステージで生成したアーティファクト名に関するパラメータです。

2つ目のステージはビルドステージです。
以下のAWS公式ページを参考にして設定します。

https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/action-reference-CodeBuild.html

ActionTypeIdプロパティで、パイプラインにおいてCodeBuildがビルドを実行するように設定します。
Configurationプロパティは、CodeBuildを使ってビルドするためのパラメータです。ProjectNameプロパティでパイプラインに関連づけるCodeBuildを指定します。
InputArtifactsプロパティで、ビルドする上で使用するアーティファクトの名前を指定します。ポイントはソースステージで指定したOutputArtifactsプロパティの値と同一にする必要があるという点です。ソースステージで生成されたアーティファクトを使ってビルドするためです。
OutputArtifactsプロパティはビルドステージで生成したアーティファクト名に関するパラメータです。

CodePipeline用のIAMロールを確認します。

Resources:
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codepipeline.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: PipelinePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codecommit:CancelUploadArchive
                  - codecommit:GetBranch
                  - codecommit:GetCommit
                  - codecommit:GetRepository
                  - codecommit:GetUploadArchiveStatus
                  - codecommit:UploadArchive
                Resource:
                  - !GetAtt CodeCommitRepository.Arn
              - Effect: Allow
                Action:
                  - codebuild:BatchGetBuilds
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuildBatches
                  - codebuild:StartBuildBatch
                Resource:
                  - !GetAtt CodeBuildProject.Arn
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${BucketName}"
                  - !Sub "arn:aws:s3:::${BucketName}/*"
Code language: YAML (yaml)

ソースステージでCodeCommitからソースコードをプルするため、これを可能にする権限を付与します。
ビルドステージでCodeBuildを使用してビルドを行うため、ビルドを実行する権限を付与します。
パイプライン実行中に、S3バケット上のアーティファクトを取得・保存しますので、これを許可します。

EventBridge

Resources:
  EventsRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - CodeCommit Repository State Change
        resources:
          - !GetAtt CodeCommitRepository.Arn
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - !Ref BranchName
      Name: !Ref Prefix
      Targets:
        - Arn: !Sub "arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}"
          Id: !Sub "${Prefix}-CodePipeline-CodeCommit"
          RoleArn: !GetAtt EventsRuleRole.Arn
Code language: YAML (yaml)

CodePipelineが開始するきっかけとして、EventBridgeを使用します。

以下のAWS公式ページを参考にして設定します。

https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/pipelines-trigger-source-repo-changes-cfn.html

EventPatternプロパティで検知するイベントを指定します。今回はソースであるCodeCommitにブランチが作成、または更新されたことを検知するように設定します。
Targetsプロパティで通知先のリソースを指定します。今回はCodePipelineを指定し、パイプラインを開始させます。

EventBridge用のIAMロールを確認します。

Resources:
  EventsRuleRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: PipelineExecutionPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codepipeline:StartPipelineExecution
                Resource:
                  - !Sub "arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}"
Code language: YAML (yaml)

CodePipelineのパイプライン実行を許可する設定を付与します。

アプリコンテナ

Dockerfile

FROM amazonlinux

RUN yum update -y && yum install python3 python3-pip -y
RUN pip3 install bottle

COPY main.py ./

CMD ["python3", "main.py"]

EXPOSE 8080
Code language: Dockerfile (dockerfile)

アプリコンテナ用のイメージは、Amazon Linux 2をベースとして作成します。

Python製WebフレームワークのBottleを使用します。
ですからPythonおよびpipをインストール後、これをインストールします。

アプリロジックを記載したPythonスクリプト(main.py)をコピーし、これを実行するように設定します。

先述の通り、アプリはtcp/8080でHTTPリクエストを待ち受けますので、このポートを公開します。

main.py

from bottle import route, run

@route('/')
def hello():
  return 'Hello CodePipeline!'


if __name__ == '__main__':
  run(host='0.0.0.0', port=8080)
Code language: Python (python)

Bottleを使用して、簡易的なWebサーバを構築します。
8080/tcpでHTTPリクエストを待ち受け、「Hello CodePipeline!」を返すという単純な構成です。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • ECR:fa-075
  • CodeCommit:fa-075
  • CodeBuildプロジェクト:fa-075
  • CodePipeline:fa-075
  • SSMパラメータのパラメータ名1:fa-075-DcokerHubUsername
  • SSMパラメータのパラメータ名2:fa-075-DcokerHubPassword

作成されたリソースをAWS Management Consoleから確認します。
ECRを確認します。

Detail of ECR 1.

空です。
パイプラインが実行されることによって、ここにイメージがプッシュされることになります。

CodeCommitを確認します。

Detail of CodeCommit 1.

こちらも空です。
ここにコードをプッシュすることによって、パイプラインが実行されることになります。

CodeBuildを確認します。

Detail of CodeBuild Settings 1.
Detail of CodeBuild Settings 2.

CloudFormationテンプレートで指定した通りに、CodeBuildが作成されています。

CodePipelineを確認します。

Result of CodePipeline 1.

パイプラインの実行に失敗しています。
これはCloudFormationスタック作成時に、CodeCommitおよびその内部にブランチが作成されたことがきっかけとして、パイプラインが実行されたためです。
現時点ではCodeCommitにコードをプッシュしていないため、パイプライン実行の過程でエラーが発生しました。

動作確認

準備が整いましたので、CodeCommitにコードをプッシュします。

まずCodeCommitをプルします。

$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/fa-075
Cloning into 'fa-075'...
warning: You appear to have cloned an empty repository.
Code language: Bash (bash)

空のリポジトリがプルされました。

リポジトリにDockerfileおよびmain.pyを加えます。

$ ls -al fa-075
total 8
drwxrwxr-x 3 ec2-user ec2-user  51 Aug 11 23:53 .
drwxrwxr-x 3 ec2-user ec2-user  20 Aug 11 23:53 ..
-rw-rw-r-- 1 ec2-user ec2-user 186 Aug 11 07:02 Dockerfile
drwxrwxr-x 7 ec2-user ec2-user 119 Aug 11 23:52 .git
-rw-rw-r-- 1 ec2-user ec2-user 630 Aug 11 07:42 main.py
Code language: Bash (bash)

2ファイルをCodeCommitにプッシュします。

$ git add .

$ git commit -m "first commit"
[master (root-commit) 2596718] first commit
...
 2 files changed, 37 insertions(+)
 create mode 100644 Dockerfile
 create mode 100644 main.py

$ git push
...
 * [new branch]      master -> masterCode language: JavaScript (javascript)

正常にプッシュできました。

改めてCodeCommitを確認します。

Detail of CodeCommit 2.

確かに2ファイルがプッシュされています。

CodePipelineが実行が開始されました。
しばらく待機します。

Result of CodePipeline 2.

パイプラインの実行が正常に完了しました。

CodeBuildの実行履歴を確認します。

Result of CodeBuild execution.

確かに正常にビルドが実行されています。

ECRを確認します。

Detail of ECR 2.

イメージがプッシュされています。
CodeBuildによってビルドされたイメージが、ECRにプッシュされたということです。

このようにCodePipelineでパイプラインを構成することで、CodeCommitにプッシュしたコードをソースとして、CodeBuildでDockerイメージをビルドし、ECRにイメージをプッシュすることができます。

まとめ

CodePipelineを使って、CodeCommitにコードをプッシュした際に、CodeBuildでDockerイメージをビルドし、ECRにプッシュするパイプラインを構成する方法を確認しました。

目次