AWS上でのCI/CDパイプラインの構築

REPORT
2021.12.21

はじめに

前回のブログ記事では、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のテンプレート

最後までお読みいただき、ありがとうございました。

AIエンジニア

AIエンジニア

沖縄本社では、一緒に働くAIエンジニアを募集しています

システムエンジニア、その他多数募集中!

ページトップへ戻る