🐕

AWS CodeBuildでdocker image をビルドするハンズオン!

2023/05/24に公開

はじめに

この記事では AWSのビルドサービスであるAWS CodeBuildについて解説した記事です。
記事の途中ではハンズオンがあります。

想定している読者としてはAWSのアカウントを既に持っており、Codeシリーズに興味がある人を対象にしています。

注意事項

  • AWS初学者向けかと問われると若干怪しいところがあります

    • AWSのアソシエイトレベルはクリアできる/クリアできる見込みのある人であれば、概ねできるように書いています
      • 上記に該当する人で難しいと感じてしまったのであれば、ごめんなさい
  • 記事の内容につきましてはある程度が時間が経過しても読めるように努力します

    • できる限りというベストエフォートな対応になりますのでご了承下さい
    • 最新の情報はAWSの公式ドキュメントを見ていただくようにお願いできればと思います
  • 本記事のハンズオンで発生した料金については一切責任を負いません

    • 利用しないサービスについては作成後に必ず削除することをお勧めします。

CodeBuild とは

AWSのドキュメントには以下のように説明があります。

AWS CodeBuild is a fully managed build service in the cloud. CodeBuild compiles your source code, runs unit tests, and produces artifacts that are ready to deploy. CodeBuild eliminates the need to provision, manage, and scale your own build servers. It provides prepackaged build environments for popular programming languages and build tools such as Apache Maven, Gradle, and more. You can also customize build environments in CodeBuild to use your own build tools. CodeBuild scales automatically to meet peak build requests.


訳:AWS CodeBuildは、クラウド上で完全に管理されたビルドサービスです。CodeBuildは、ソースコードのコンパイル、ユニットテストの実行、デプロイ可能なアーティファクトの生成を行います。CodeBuildは、独自のビルドサーバーをプロビジョニング、管理、スケールアップする必要性を排除しています。一般的なプログラミング言語や、Apache Maven、Gradleなどのビルドツール用のビルド環境があらかじめパッケージ化されています。また、CodeBuildでビルド環境をカスタマイズして、独自のビルドツールを使用することも可能です。CodeBuildは、ピーク時のビルド要求に対応するために自動的にスケールします。


参考

AWSで数あるCodeシリーズの中ではビルドを担当するサービスであり、フルマネージドでソースコードのビルドができます。

CodeBuildの使い方や仕組み

ここまででCodeBuildがCodeシリーズの中でビルドを担当するものであることを理解できたと思います。
ただし、CodeBuildにはビルドの設定ファイルとしてbuildspec.ymlというビルドの方法を記録した設定ファイルが必要です。

buildspec.ymlをCodeBuildでビルドするリポジトリに配置しておくことでCodeBuildがbuildspec.ymlの内容に従ってビルドを実行します。

CodeBuildの料金体系

CodeBuildでビルドを実行する前に料金について解説します。
※最新の料金体系を把握したい場合はAWS公式ドキュメントを参照してください。

CodeBuildはビルド時間とコンピューティングタイプ(インスタンスタイプ)に応じて課金が発生します。
無料利用枠には1か月あたり100分のビルド時間があります。

以下、AWS公式ドキュメントから抜粋

AWS CodeBuild の無料利用枠には、general1.small または arm1.small のインスタンスタイプで 1 か月あたり 100 分の総ビルド時間が含まれています。CodeBuild の無料利用枠は、12 か月間の AWS 無料利用枠の期間が終了しても自動的に期限切れになることはありません。新規または既存の AWS のお客様が利用できます。

利用環境

前提

ハンズオンではIAM Identity Centerを使って解説します。シングルサインオンを実行します。

事前準備

リポジトリを作成する

参考

※CloudFormation で作成する場合は以下

AWSTemplateFormatVersion: 2010-09-09
Description: cicd_handson
Parameters:
  PjName:
    Type: String
    Default: cicdhandson
Resources:
  CICDHandsonRepository:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryDescription: !Sub "${PjName} CodeCommit Repositroy"
      RepositoryName: !Sub "${PjName}"
      Tags:
        - Key: Name
          Value: !Sub "${PjName}"

Outputs:
  CodeCommitRepositoryName:
    Value: !GetAtt "CICDHandsonRepository.Name"
    Export:
      Name: CodeRepositoryName
  CodeCommitRepositoryArn:
    Value: !GetAtt CICDHandsonRepository.Arn
    Export:
      Name: CodeRepositoryArn

※既にリポジトリをクローンしている場合

デスクトップ上にリポジトリをクローン済みの場合はカレントディレクトリを変更してIAM Identity Centerに対してシングルサインオンを実行します。

cd ~/Desktop/cicd_handson
aws sso login --profile {Profile名}

mainブランチを切る

git checkout -b main
git push --set-upstream origin main

code_build_handsonブランチを切る

新しいブランチでビルドを実行する為にCodeBuild用に新しくブランチを切ります。

git checkout -b code_build_handson

リモートリポジトリにブランチを追加します。

git push --set-upstream origin code_build_handson

buildspec.yamlを作成する

buildspec.yamlを作成します。

touch buildspec.yml

以下の内容でbuildspec.yamlを修正します。

version: 0.2
phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

リポジトリにbuildspecをpushします。

git add .
git commit -m "buildspec.yml"
git push -u 

dockerfileを作成する

dockerfileを作成します。

touch dockerfile

以下の内容で修正します。

FROM httpd@sha256:2326ba7ae9bff1c55a618051b86c7d71401712898afe1a833734076962a231e5

dockerfileを追加します。

git add .
git commit -m "dockerfile"
git push -u 

CodePipeline の環境構築

以下のCloudFormationでCodePipelineを構築します。

AWSTemplateFormatVersion: 2010-09-09
Description: cicd_handson
Parameters:
  PjName:
    Type: String
    Default: cicdhandson
Resources:
  # Save Artifact
  CICDHandsonBuket:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: "Suspended"
      BucketName: !Sub "${PjName}-bucket-${AWS::AccountId}"
      AccessControl: "Private"
      Tags:
        - Key: "Name"
          Value: !Sub "${PjName}"
  # ECR
  CICDHandsonCodeBuildECR:
    Type: "AWS::ECR::Repository"
    Properties:
      RepositoryName: !Sub "${PjName}"
  # CodeBuild
  CICDHandsonCodeBuildPj:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Description: CodeBuild
      Environment:
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Type: PLAINTEXT
            Value: !Ref AWS::Region
          - Name: AWS_ACCOUNT_ID
            Type: PLAINTEXT
            Value: !Ref AWS::AccountId
          - Name: IMAGE_REPO_NAME
            Type: PLAINTEXT
            Value: !Sub "${PjName}"
          - Name: IMAGE_TAG
            Type: PLAINTEXT
            Value: latest

        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:4.0
        Type: LINUX_CONTAINER
        PrivilegedMode: true
      Name: !Sub "${PjName}"
      ServiceRole: CiCdHandsonCodeBuildRole
      Source:
        BuildSpec: buildspec.yml
        Type: CODEPIPELINE
      Tags:
        - Key: Name
          Value: cicd_handson
    DependsOn:
      - CiCdHandsonCodeBuildRole
      - CICDHandsonCodeBuildECR
      - CICDHandsonBuket
  # CodeBuildRole
  CiCdHandsonCodeBuildRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: CiCdHandsonCodeBuildRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: "codebuild.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: codebuild-role
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "ecr:GetDownloadUrlForLayer"
                  - "ecr:BatchGetImage"
                  - "ecr:BatchCheckLayerAvailability"
                Resource:
                  - !GetAtt CICDHandsonCodeBuildECR.Arn
              - Effect: Allow
                Action:
                  - "ecr:CompleteLayerUpload"
                  - "ecr:InitiateLayerUpload"
                  - "ecr:UploadLayerPart"
                  - "ecr:PutImage"
                Resource:
                  - !GetAtt CICDHandsonCodeBuildECR.Arn
              - Effect: Allow
                Action:
                  - "ecr:GetAuthorizationToken"
                Resource:
                  - "*"
              - Effect: Allow
                Action:
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "logs:CreateLogGroup"
                Resource: "*"
              - Effect: Allow
                Action:
                  - "s3:PutObject"
                  - "s3:GetObject"
                  - "s3:GetObjectVersion"
                  - "s3:GetBucketAcl"
                  - "s3:GetBucketLocation"
                Resource:
                  - !Join
                    - ""
                    - - !GetAtt CICDHandsonBuket.Arn
                      - "/*"
                  - !GetAtt CICDHandsonBuket.Arn
  # CloudWatch Logs
  CICDHandsonCloudWatchLogs:
    Type: "AWS::Logs::LogGroup"
    DeletionPolicy: Delete
    UpdateReplacePolicy: Retain
    Properties:
      LogGroupName: !Sub "${PjName}"
      RetentionInDays: 60
      Tags:
        - Key: Name
          Value: !Sub "${PjName}"

  CICDHandsonCodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref CICDHandsonBuket
        Type: S3
      Name: !Sub "${PjName}-pipeline"
      RoleArn: !GetAtt CICDHandsonCodePipelineIAMRole.Arn
      Stages:
        - Actions:
            - ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: 1
              Configuration:
                RepositoryName: !ImportValue "CodeRepositoryName"
                BranchName: main
                PollForSourceChanges: false
                OutputArtifactFormat: CODE_ZIP
              Name: Source
              Namespace: SourceVariables
              OutputArtifacts:
                - Name: SourceArtifact
              Region: ap-northeast-1
              RunOrder: 1
          Name: Source
        - Actions:
            - ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: !Ref CICDHandsonCodeBuildPj
              InputArtifacts:
                - Name: SourceArtifact
              Name: Build
              Namespace: BuildVariables
              OutputArtifacts:
                - Name: BuildArtifact
              Region: ap-northeast-1
              RunOrder: 1
          Name: Build
      Tags:
        - Key: Name
          Value: !Sub "${PjName}"

  CICDHandsonCodePipelineIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - "codecommit:CancelUploadArchive"
              - "codecommit:GetBranch"
              - "codecommit:GetCommit"
              - "codecommit:GetRepository"
              - "codecommit:GetUploadArchiveStatus"
              - "codecommit:UploadArchive"
            Resource:
              - "*"
          - Effect: Allow
            Action:
              - "codebuild:BatchGetBuilds"
              - "codebuild:StartBuild"
            Resource:
              - "*"
          - Effect: Allow
            Action:
              - "s3:PutObject"
              - "s3:GetObject"
              - "s3:GetObjectVersion"
              - "s3:GetBucketAcl"
              - "s3:GetBucketLocation"
            Resource:
              - !Join
                - ""
                - - !GetAtt CICDHandsonBuket.Arn
                  - "/*"
              - !GetAtt CICDHandsonBuket.Arn
      ManagedPolicyName: iam-policy-codepipeline
  CICDHandsonCodePipelineIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codepipeline.amazonaws.com
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - !Ref CICDHandsonCodePipelineIAMPolicy
      RoleName: iam-role-codepipeline
      Tags:
        - Key: Name
          Value: !Sub "${PjName}"

  # EventBridge
  EventBridgeIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - "codepipeline:StartPipelineExecution"
            Resource:
              - !Join
                - ""
                - - "arn:aws:codepipeline:ap-northeast-1:"
                  - !Sub "${AWS::AccountId}:"
                  - !Sub "${PjName}-pipeline"
      ManagedPolicyName: iam-policy-eventbridge

  EventBridgeIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - !Ref EventBridgeIAMPolicy
      RoleName: iam-role-eventbridge
      Tags:
        - Key: Name
          Value: !Sub "${PjName}"

  EventBridge:
    Type: AWS::Events::Rule
    Properties:
      Description: for codepipeline
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - "CodeCommit Repository State Change"
        resources:
          - !ImportValue "CodeRepositoryArn"
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - main
      Name: cicdhandson-codepipeline
      State: ENABLED
      Targets:
        - Arn: !Join
            - ""
            - - "arn:aws:codepipeline:ap-northeast-1:"
              - !Sub "${AWS::AccountId}:"
              - !Sub "${PjName}-pipeline"
          Id: !Sub "${PjName}-pipeline"
          RoleArn: !GetAtt EventBridgeIAMRole.Arn

Outputs:
  CodePipelineIAMPolicy:
    Value: !Ref CICDHandsonCodePipelineIAMPolicy
    Export:
      Name: CodePipelineIAMPolicyArn

ビルドされたイメージを確認する

CloudFormationで作成されたリポジトリを確認します。

aws ecr describe-repositories --profile {プロファイル名} --output json 

コマンドを実行すると以下のようにリポジトリの詳細が出力されます。

{
    "repositories": [
        {
            "repositoryArn": "arn:aws:ecr:ap-northeast-1:{アカウント}:repository/cicdhandson",
            "registryId": "{アカウントID}",
            "repositoryName": "cicdhandson",
            "repositoryUri": "{アカウントID}.dkr.ecr.ap-northeast-1.amazonaws.com/cicdhandson",
            "createdAt": "2023-05-24T00:14:05+09:00",
            "imageTagMutability": "MUTABLE",
            "imageScanningConfiguration": {
                "scanOnPush": false
            },
            "encryptionConfiguration": {
                "encryptionType": "AES256"
            }
        }
    ]
}

イメージダイジェストを確認します。

aws ecr list-images --profile {プロファイル名} --repository-name cicdhandson --query "imageIds[*].imageTag" --output table
-----------------------------------------------------------------------------
|                                ListImages                                 |
+---------------------------------------------------------------------------+
|  sha256:2326ba7ae9bff1c55a618051b86c7d71401712898afe1a833734076962a231e5  |
+---------------------------------------------------------------------------+

イメージのタグを確認します。

aws ecr list-images --profile {プロファイル名} --repository-name cicdhandson --query "imageIds[*].imageDigest" --output table
------------
|ListImages|
+----------+
|  latest  |
+----------+

片付け

S3バケットを空にする

aws s3 ls --profile {プロファイル名} | grep cicdhandson | awk '{print $3}'
aws s3 rm s3://cicdhandson-bucket-{アカウント}/ --recursive --profile {プロファイル名}

中身を確認します。

aws s3 ls s3://cicdhandson-bucket-{アカウントID} --profile {プロファイル名}
aws s3 rb s3://cicdhandson-bucket-{アカウントID} --force

まとめ

これでハンズオンは以上です。上記の構成でCodeCommit にDockerfileをおくことにより
buildspec.ymlの設定に従ってCodeBuildでイメージをビルドできます。これでイメージをリポジトリにpushしたことをトリガーに
CodeDeployによるデプロイやApp Runnerへのアプリケーションデプロイができます。

おわり

参考資料

AWSの継続的インテグレーション/デリバリー総まとめ

AWS Codeシリーズを利用したパイプラインの自動化入門

Discussion