AWS

CFNカスタムリソースとCodeBuildを使って、テストイメージをECRに自動的にプッシュする

CFNカスタムリソースとCodeBuildを使って、テストイメージをECRに自動的にプッシュする

CloudFormationスタック作成時に、ECRリポジトリに自動的にテストイメージをプッシュすることを考えます。

今回はCloudFormationカスタムリソースとCodeBuildを使用して、上記を実現する方法をご紹介します。

構築する環境

Diagram of automatically push test image to ECR using CFN custom resources and CodeBuild.

CloudFormationで4つのリソースを作成します。

Secrets ManagerはDockerHubのアカウント情報を保存するために使用します。
JSON形式でアカウント名とパスワードを登録します。

CodeBuildでテスト用のDockerイメージをビルドします。
イメージはDockerHubからプルしたものをベースにします。
ビルドしたイメージをECRリポジトリにプッシュします。

Lambda関数を作成します。
この関数の働きは、CodeBuildを開始させることです。
関数のランタイム環境はPython3.8です。

この関数がCloudFormationスタック作成時に自動的に実行されるように、CloudFormationカスタムリソースに関連づけます。

CloudFormationテンプレートファイル

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

awstut-fa/129 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

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

ECR

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

ECRリポジトリを作成します。
特別な設定は不要です。

Secrets Manager

Resources:
  Secret:
    Type: AWS::SecretsManager::Secret
    Properties: 
      Name: !Ref Prefix
      SecretString: !Sub '{"username":"${Username}","password":"${Password}"}'
Code language: YAML (yaml)

Secrets ManagerにDockerHubのアカウント情報を保存します。

Secrets Managerに関する詳細は、こちらもご確認ください。

CodeBuild

Resources:
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties: 
      Artifacts:
        Type: NO_ARTIFACTS
      Cache: 
        Type: NO_CACHE
      Environment: 
        ComputeType: !Ref ProjectEnvironmentComputeType
        EnvironmentVariables:
          - Name: DOCKERHUB_PASSWORD
            Type: SECRETS_MANAGER
            Value: !Sub "${Secret}:password"
          - Name: DOCKERHUB_USERNAME
            Type: SECRETS_MANAGER
            Value: !Sub "${Secret}:username"
        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: NO_SOURCE
        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
                - |
                  cat << EOF > Dockerfile
                  FROM nginx:latest
                  EXPOSE 80
                  EOF
            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)

CodeBuildを使用して、Dockerイメージをビルド後、ECRリポジトリにイメージをプッシュする方法については、以下のページをご確認ください。

上記のページでは、CodePipeline内において、CodeCommitリポジトリ上のDockerfileをソースとして読み込んでビルドしました。

本ページでは、CodeBuild単体でビルドする方法を中心に取り上げます。

今回はアーティファクトは存在しません。
ですからArtifactsプロパティ内のTypeプロパティに「NO_ARTIFACTS」を指定します。

またソースも存在しないため、Sourceプロパティ内のTypeプロパティに「NO_SOURCE」を指定します。

BuildSpecプロパティ内のpre_buildフェーズがポイントです。
本フェーズ内でcatコマンドを使用して、Dockerfileを生成します。
つまりCodeCommit等の外部のリソースをソースとするのではなく、CodeBuild内の環境でDockerfileを生成して、こちらに基づいてビルドするということです。

Secrets Managerの値をCodeBuildで参照する場合、EnvironmentVariablesプロパティもポイントとなります。
詳細は以下のページをご確認いただきたいのですが、環境変数としてSecrets Managerの値を設定する場合は、Typeプロパティに「SECRETS_MANAGER」を、ValueプロパティにシークレットのARNと値のキーを組み合わせた文字列を指定します。

のビルド仕様リファレンス CodeBuild - AWS CodeBuild
AWS CodeBuildでのビルドの仕様 (buildspec) ファイルに関するリファレンス情報を提供します。

以下が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: GetSecretValuePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource:
                  - !Ref Secret
Code language: YAML (yaml)

AWS管理ポリシーAmazonEC2ContainerRegistryPowerUserに加えて、インラインポリシーとしてSecrets Managerの値を参照する権限を与えます。

Lambda関数

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt Function.Arn
      
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          CODEBUILD_PROJECT: !Ref CodeBuildProject
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os
          
          codebuild_project = os.environ['CODEBUILD_PROJECT']
          
          CREATE = 'Create'
          response_data = {}
          
          client = boto3.client('codebuild')
          
          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                response = client.start_build(
                  projectName=codebuild_project
                )
                print(response)
                
              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
              
            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

Lambda関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。

cfnresponseモジュールを使用して、関数をLambda-backedカスタムリソースとして実装します。
詳細につきましては、以下のページをご確認ください。

実行するコードの内容ですが、以下の通りです。

  • os.environにアクセスして、CloudFormationテンプレートで定義した環境変数を取得する。
  • Boto3でCodeBuild用クライアントオブジェクトを作成して、CodeBuildの実行を開始する。

関数用のIAMロールは以下の通りです。

Resources:
  FunctionRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: GetSecretValuePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codebuild:StartBuild
                Resource:
                  - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/${CodeBuildProject}"
Code language: YAML (yaml)

AWS管理ポリシーであるAWSLambdaBasicExecutionRoleに加えて、インラインポリシーでCodeBuildを開始する権限を与えます。

環境構築

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

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

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

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

  • ECRリポジトリ:fa-129
  • Secrets Manager:fa-129
  • Lambda関数:fa-129-function
  • CodeBuild:fa-129

AWS Management Consoleから各リソースを確認します。

Secrets Managerを確認します。

Detail of Secrets Manager 1.

シークレットが正常に作成されています。

Lambda関数および実行状況を確認します。

Detail of Lambda 1.
Detail of Lambda 2.

正常に関数が作成された上で、実行されていることがわかります。
つまりCloudFormationカスタムリソースによって、CloudFormationスタック作成時に、自動的にLambda関数が実行されたことになります。

動作確認

準備が整いましたので、CodeBuildを確認します。

Detail of CodeBuild 1.

確かにCodeBuildが実行されています。
つまりCloudFormationカスタムリソースに紐付くLambda関数が実行されたことによって、ビルドが開始されたということです。

ちなみにCodeBuildの環境変数の設定も確認します。

Detail of CodeBuild 2.

確かにSecrets Managerの値が参照されるように設定されています。

最後にECRリポジトリを確認します。

Detail of ECR 1.

確かにイメージがプッシュされています。

このようにCodeBuildおよびCloudFormationカスタムリソースを使用することによって、自動的にテストイメージをECRリポジトリにイメージをプッシュすることができました。

まとめ

CloudFormationカスタムリソースとCodeBuildを使用して、CloudFormationスタック作成時に、ECRリポジトリに自動的にテストイメージをプッシュする方法を確認しました。

タイトルとURLをコピーしました