CFNカスタムリソースとCodeBuildを使って、テストイメージをECRに自動的にプッシュする
CloudFormationスタック作成時に、ECRリポジトリに自動的にテストイメージをプッシュすることを考えます。
今回はCloudFormationカスタムリソースとCodeBuildを使用して、上記を実現する方法をご紹介します。
構築する環境
CloudFormationで4つのリソースを作成します。
Secrets ManagerはDockerHubのアカウント情報を保存するために使用します。
JSON形式でアカウント名とパスワードを登録します。
CodeBuildでテスト用のDockerイメージをビルドします。
イメージはDockerHubからプルしたものをベースにします。
ビルドしたイメージをECRリポジトリにプッシュします。
Lambda関数を作成します。
この関数の働きは、CodeBuildを開始させることです。
関数のランタイム環境はPython3.8です。
この関数がCloudFormationスタック作成時に自動的に実行されるように、CloudFormationカスタムリソースに関連づけます。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/129
テンプレートファイルのポイント解説
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用の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を確認します。
シークレットが正常に作成されています。
Lambda関数および実行状況を確認します。
正常に関数が作成された上で、実行されていることがわかります。
つまりCloudFormationカスタムリソースによって、CloudFormationスタック作成時に、自動的にLambda関数が実行されたことになります。
動作確認
準備が整いましたので、CodeBuildを確認します。
確かにCodeBuildが実行されています。
つまりCloudFormationカスタムリソースに紐付くLambda関数が実行されたことによって、ビルドが開始されたということです。
ちなみにCodeBuildの環境変数の設定も確認します。
確かにSecrets Managerの値が参照されるように設定されています。
最後にECRリポジトリを確認します。
確かにイメージがプッシュされています。
このようにCodeBuildおよびCloudFormationカスタムリソースを使用することによって、自動的にテストイメージをECRリポジトリにイメージをプッシュすることができました。
まとめ
CloudFormationカスタムリソースとCodeBuildを使用して、CloudFormationスタック作成時に、ECRリポジトリに自動的にテストイメージをプッシュする方法を確認しました。