はじめに
前回のブログ記事では、FastAPIで画像内容を判定できるAPIを作成しました。そのAPIは、CloudFormationのテンプレートでAWS LambdaとElastic Beanstalkにデプロイしました。
今回のブログ記事では、AWS CodePipelineを使ってCI/CDプロセスを実装することで、そのAPIのデプロイを自動化します。
自己紹介
私はアダムと申します。PROTO SolutionのAIテクノロジー推進室で機械学習/深層学習のシステムやプロトタイプを開発しております。
主な仕事は、TensorFlowを使った深層学習モデルの開発と、それらを使ってAmazon Web Services (AWS)にデプロイできるAPIやバッチアプリケーションの開発です。
CI/CDとは
Continuous Integration (CI)
Continuous Integrationとは、開発者が行った変更をソースリポジトリのメインブランチに可能な限り頻繁にマージすることです。
これにより、ソースコードを結合する時間が短縮され、リリース前に他の開発者のアプリケーションの変更をマージすることで発生する問題を軽減することができます。
開発者がコードをメインブランチにマージするたびに、自動ユニットテストや統合テストが実行され、自動的にテストされます。
Continuous Delivery/Deployment (CD)
Continuous Delivery/Deploymentとは、アプリケーションの変更をテスト環境や本番環境に自動的にデプロイするプロセスです。
このプロセスのすべてのステップが完全に自動化され、人の介入を必要としないのが理想です。
ただ、CI/CDの導入する前に下記の導入も重要だと考えています。
●新機能を別々のブランチで開発し、リリースのためにメインブランチにマージするワークフローです。
●ビルドおよびデプロイメントパイプラインのさまざまな段階で実行可能な自動テストです。
●クラウドサービスを使用している場合、アプリケーションが実行されるインフラが自動的に立ち上がる方法です。
●インフラにアプリケーションを自動的にデプロイする方法です。
●新しいカラムの追加や参照データの挿入など、データベースの変更を管理するための自動化されたプロセスが存在することも理想的です。
CI/CDに向けたAWSサービス
AWSでは、CI/CDパイプラインを実装するための様々なサービスが提供されています。以下に主なサービスを紹介します。
AWS CodeCommit
CodeCommit は、AWSにホストされているGitリポジトリです。GitHubのようなサービスです。
AWS CodeBuild
CodeBuild は、ソースコードをコンパイル・ビルド・テストできるAWSのサービスです。
AWS CodeDeploy
CodeDeploy は、CloudFormation、ECSとLambdaに向けたデプロイメントのサービスです。Blue/Green デプロイなど機能を提供しています。
AWS CodePipeline
CodePipeline は、ビルドとデプロイのプロセスの全体的なフローを定義します。
CI/CDフローの実装
先に紹介したAWSサービスを使ってCI/CDのフローを実装します。
プロジェクトの要件に応じて必要なステップと技術が異なる場合もあると思いますが、CI/CDのフローは最低限でも下記に載っているステップが必要だと思います。
1,開発者は、ソースコードをCodeCommitにpushします。
2,CodePipelineは、CodeCommitのソースコードが更新されたたびに、最新のソースコードを取得します。
3,CodePipelineは、CodeBuildにソースコードを渡します。CodeBuildは、ソースコードをテスト・パッケージし、ECRにアップロードし、タグを付けます。アップロードしたイメージのURIをアウトプットします。
4,CodeCommitにあったCloudFormationテンプレートを実行します。最新のイメージをデプロイするために、CodeBuildで作成したイメージのURIにCloudFormationテンプレートのURIパラメーターを書き換えます。CloudFormationで開発環境のリソースをデプロイします。
5,承認を得るまで待ちます。
6,承認された場合、ステップ4と同じように本番環境のリソースをデプロイします。
サンプルコードには含まれていませんが、以下のタスクをフローに追加することを強く勧めます。
●自動ユニット・インテグレーションテストの追加
●デプロイ後、APIへの自動システムテストの追加
●Gitにビルドに使ってたソースコードにタグを付けます
●コード静的分析、コードカバレッジのレポート生成
これらのタスクのどれかが失敗したり、コードが一定の品質レベルを満たしていない場合、ビルドは停止します。
CodePipelineの構築
このフローを実現するために、フローのステップをCloudFormationのテンプレートで定義します。
CloudFormationのテンプレートでCI/CDフローに必要なAWSリソースを見てみましょう。
ユーザーが入力可能なパラメータ
ユーザーが入力可能なパラメータを定義しましょう。このような情報をパラメータとしてテンプレートに入れることで、テンプレートを変更することなく、他のプロジェクトで再利用することができます。
ほとんどのリソース名をApplicationName (アプリケーション名)のパラメータに基づいて決定します。また、ソースコードを取得したいブランチや、DockerイメージをアップロードしたいECRリポジトリの名前を指定できるようにします。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
ApplicationName:
Default: SampleApi
Type: String
Description: Enter the name of your application
CodeBuildImage:
Default: 'aws/codebuild/standard:4.0'
Type: String
Description: Name of codebuild image to use.
BranchName:
Description: 'The name of the branch to deploy from.'
Type: String
Default: 'main'
ImageRepository:
Description: 'The name of the ECR repository where images are stored.'
Type: String
Default: 'test'
CodeCommitリポジトリ作成
ユーザーが入力したApplicationNameの元にリポジトリの名前を設定し、作成します。
SourceRepository:
Type: 'AWS::CodeCommit::Repository'
Properties:
RepositoryName: !Ref ApplicationName
RepositoryDescription: !Sub 'Source code for ${ApplicationName}'
CodeBuildのジョブ
Dockerイメージをビルドし、ECRにアップロードするためにCodeBuildのプロジェクトを作成します。
こちらに設定された環境変数はビルドのコンテナ内でアクセスできます。
AppPackageBuild:
Type: 'AWS::CodeBuild::Project'
Properties:
Artifacts:
Type: CODEPIPELINE
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: !Ref CodeBuildImage
Type: LINUX_CONTAINER
PrivilegedMode: True
EnvironmentVariables:
- Name: REPOSITORY_NAME
Value: !Ref ImageRepository
- Name: AWS_DEFAULT_REGION
Value: !Sub '${AWS::Region}'
- Name: AWS_ACCOUNT_ID
Value: !Sub '${AWS::AccountId}'
Name: !Sub '${ApplicationName}Build'
ServiceRole: !GetAtt
- CodeBuildRole
- Arn
Source:
Type: CODEPIPELINE
buildspec.yaml
デフォルトでは、CodeBuildのプロジェクトは、buildspec.yaml
というファイルの中のコマンドを実行して、アプリケーションをパッケージします。このファイルの内容を説明します。
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws --version
- ECR_URL=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
- REPOSITORY_URI=$ECR_URL/$REPOSITORY_NAME
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_URL
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- BUILD_TAG=$(echo $CODEBUILD_BUILD_ID | awk -F":" '{print $2}')
- BUILD_LABEL=build-$BUILD_TAG
ビルドの事前準備です。ECRのリポジトリにログインし、ビルドの番号を作成します。次のステップにこれを使う予定です。
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -f Dockerfile.lambda -t $REPOSITORY_URI:latest .
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$BUILD_LABEL
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$COMMIT_HASH
- printf '{"ImageUri":"%s"}' $REPOSITORY_URI:$BUILD_LABEL > image.json
- cp deploy/lambda.yaml deploy.yaml
buildspec.yaml
の主要部分です。Dockerのイメージにソースコードをパッケージします。イメージにバージョンのタグを付けます。最後にイメージURIをファイルにアウトプットします。
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$BUILD_LABEL
- docker push $REPOSITORY_URI:$COMMIT_HASH
artifacts:
files:
- image.json
- deploy.yaml
ビルド後の処理です。DockerのイメージをECRにアップロードします。CodePipelineでの次のステップに利用するファイルをartifacts
に指定します。
CodePipeline
こちらからCodePipelineのフローを定義します。まずは、CodePipelineの名前と必要なRoleを設定します。
AppPipeline:
Type: 'AWS::CodePipeline::Pipeline'
Properties:
Name: !Sub '${ApplicationName}Pipeline'
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucketStore
RoleArn: !GetAtt
- CodePipelineRole
- Arn
Stages
のところにCodePipelineのステップを定義します。
最初のステップは、CodeCommitからソースコードを取得するステップです。
Stages:
- Name: Source
Actions:
- ActionTypeId:
Category: Source
Owner: AWS
Version: 1
Provider: CodeCommit
Configuration:
BranchName: !Ref BranchName
RepositoryName: !GetAtt
- SourceRepository
- Name
OutputArtifacts:
- Name: SourceRepo
RunOrder: 1
Name: Source
その次のステップは、以前に定義されたCodeBuildのプロジェクトを参照し、ソースコードをビルドするステップです。
CodeBuildがビルドした成果物(DockerイメージのURIとCloudFormationのテンプレート)がBuildArtefacts
に保存されています。それは次のステップに参照できます。
- Name: Build
Actions:
- InputArtifacts:
- Name: SourceRepo
Name: CodeBuild
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
OutputArtifacts:
- Name: BuildArtefacts
Configuration:
ProjectName: !Ref AppPackageBuild
RunOrder: 1
CloudFormationのテンプレートで開発環境デプロイに向けた変更セット(Change Set)を作成します。
変更セット(Change Set)では、テンプレートが実際に実行される前に、どのリソースが作成、変更、削除されるかを確認することができます。
デプロイする前に先ほどECRにアップロードしたDockerイメージのURIをCloudFormationのテンプレートに書き換えます。デプロイするテンプレートのパスは、BuildArtefacts
に保存したCloudFormationのテンプレートに設定します。
- Name: Development
Actions:
- ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: CloudFormation
InputArtifacts:
- Name: BuildArtefacts
Name: CreateDevelopmentChangeSet
Configuration:
ActionMode: CHANGE_SET_REPLACE
ChangeSetName: !Sub '${ApplicationName}DevelopmentChangeSet'
RoleArn: !GetAtt
- CFNDeployRole
- Arn
Capabilities: CAPABILITY_IAM,CAPABILITY_AUTO_EXPAND
StackName: !Sub '${ApplicationName}DevelopmentStack'
TemplatePath: 'BuildArtefacts::deploy.yaml'
ParameterOverrides: |
{
"ImageUri": {"Fn::GetParam": ["BuildArtefacts", "image.json", "ImageUri"]}
}
RunOrder: 1
次に、前のステップで作成した変更セットを実行し、実際に開発環境へのデプロイを行います。
- RunOrder: 2
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: CloudFormation
Configuration:
StackName: !Sub '${ApplicationName}DevelopmentStack'
ActionMode: CHANGE_SET_EXECUTE
ChangeSetName: !Sub '${ApplicationName}DevelopmentChangeSet'
OutputFileName: StackOutputs.json
Name: ExecuteChangeSet
OutputArtifacts:
- Name: AppDeploymentValuesDevelopment
開発環境へのデプロイが完了したら、次は本番環境に同じパッケージしたイメージをデプロイします。
開発環境のように変更セットを作成し、DockerイメージのURIをCloudFormationのテンプレートに書き換えます。
- Name: Production
Actions:
- ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: CloudFormation
InputArtifacts:
- Name: BuildArtefacts
Name: CreateProductionChangeSet
Configuration:
ActionMode: CHANGE_SET_REPLACE
ChangeSetName: !Sub '${ApplicationName}ProductionChangeSet'
RoleArn: !GetAtt
- CFNDeployRole
- Arn
Capabilities: CAPABILITY_IAM,CAPABILITY_AUTO_EXPAND
StackName: !Sub '${ApplicationName}ProductionStack'
TemplatePath: 'BuildArtefacts::deploy.yaml'
ParameterOverrides: |
{
"ImageUri": {"Fn::GetParam": ["BuildArtefacts", "image.json", "ImageUri"]}
}
RunOrder: 1
本番デプロイが行われる前に、チームリーダーなどの承認が得られるまでデプロイを停止する承認ステップを追加します。 このステップでは、変更セットが実際に実行される前に変更セットの詳細を確認することができます。
- Name: DeploymentApproval
ActionTypeId:
Category: Approval
Owner: AWS
Version: 1
Provider: Manual
Configuration:
NotificationArn: !Ref ApprovalTopic
CustomData: 'Please approve the change set to allow deployment.'
RunOrder: 2
最後のステップでは、実際に本番環境にデプロイを行ないます。
- Name: ExecuteChangeSet
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: CloudFormation
Configuration:
StackName: !Sub '${ApplicationName}ProductionStack'
ActionMode: CHANGE_SET_EXECUTE
ChangeSetName: !Sub '${ApplicationName}ProductionChangeSet'
OutputFileName: StackOutputs.json
OutputArtifacts:
- Name: AppDeploymentValuesProduction
RunOrder: 3
テンプレートの残りの部分は、CodePipelineとCodeBuildのプロジェクトに権限を付与する周りです。
こちらにテンプレート全体を確認してください
Code Pipelineのデプロイ
先に定義したCode Pipelineのテンプレートを実行するには、以下の手順を行います。
1,CloudFormationのコンソールを開きます 。
2,利用したいリージョンを選択します。
3,Create Stack With new resources (standard) 「スタックの作成 (新しいリソース)」を選択します。
4,テンプレートファイルのアップロードを選択します。
5,deploy/pipeline.yamlのパスを入力します。(Next)「次へ」をクリックします。
6,テンプレートに必要なパラメータを入力します。
1,Stack Name。 今から作成するスタックの名前です。例:「ImageNetPipeline」。
2,ApplicationName。アプリケーションの名前です。多くのCodePipelineに入っているリソースはこの名前の元に作る予定です。
3,BranchName。どのブランチからソースコードを習得します。
4,ImageRepository。ECRのリポジトリの名前です。
7,(Next) 「次へ」をクリックします。
8,画面下部のチェックボックスをすべてチェックします。
9,(Create Stack)「スタック作成」をクリックします。
初めてのビルドとデプロイ
テンプレートのデプロイ後、CodeCommitリポジトリにコードが存在しないため、Code Pipelineは失敗状態になります。
ソースコードをpushすると、Code Pipelineのビルドが自動的に始まります。
ビルドが無事に完了するとCloudFormationで開発環境を立ち上がってDockerのイメージをデプロイします。
その後、本番環境へのデプロイのステップに入り、ビルドは停止し、承認が得られるのを待ちます。
リリースが承認されると、ビルドが続行され、本番環境へのデプロイも完了します。
最後に
この記事では、AWSのCI/CDに向けたサービスを使い、自動ビルドとデプロイのフローを実現できました。
このパイプラインは出発点として使用されるべきで、プロジェクトの要件に合わせてステップの追加や削除、フローの変更を自由に行うことができます。
ソースファイル
このプログ記事で使ったソースファイルをGitHubに公開しました。
●buildspec.yaml – CodeBuildが利用されたbuildspec.yamlファイル
●Dockerfile.lambda – Lambdaのデプロイに向けたDockerfile
●deploy/lambda.yaml – Lambdaのデプロイに向けたCloudFormationのテンプレート
●deploy/pipeline.yaml – CodePipelineのデプロイに向けたCloudFormationのテンプレート
最後までお読みいただき、ありがとうございました。