Amazon Web Services ブログ

AWS CDK Pipelines を用いたマルチブランチ管理とインフラのデプロイ

この記事では、AWS Cloud Development Kit (AWS CDK) を用いて GitFlow モデルに従って開発するための AWS CDK Pipelines モジュール の利用方法について説明します。ソフトウェア開発チームはソリューション開発のライフサイクルの中で厳格なブランチ戦略に従うことがよくあります。新規作成されたブランチでは一般的に新しい機能を開発するために独自の分離されたインフラリソースのコピーが必要となります。

CDK Pipelines は AWS CDK アプリケーションの継続的デリバリーを行うためのコンストラクトライブラリモジュールです。CDK Pipelines はパイプラインを自己更新 (self-mutation) する機能を持っています。具体的にはアプリケーションのステージやスタックが追加されると、パイプラインは自動的に再構成され新しいステージやスタックがデプロイされるように機能します。

本ソリューションは、ソースリポジトリ (AWS CodeCommit) に新しいブランチが作成されるたびに開発アカウント内に新しい AWS CDK Pipeline を作成します。ブランチが削除されると、そのアカウントからパイプラインとすべての関連リソースが破棄されます。この GitFlow モデルによるインフラのプロビジョニングにより、開発者はアプリケーションの同じスタック内であっても互いに独立して同時並行的に作業することができます。

ソリューション概要

本ソリューションの概要は図 1 の通りとなります。異なるアプリケーション環境(開発環境、プレ本番環境、本番環境など)へのリソースのデプロイを担当するデフォルトのパイプラインが 1 つあります。
コードは CodeCommit に保存されます。新しい変更が CodeCommit リポジトリのデフォルトブランチにプッシュされると、AWS CodePipeline はデフォルトパイプラインを実行します。デフォルトのパイプラインがデプロイされると、2 つの AWS Lambda 関数が作成されます。

これらの 2 つの Lambda 関数は、リポジトリの新しいブランチが作成されたときまたは削除されたときに CodeCommit CloudWatch イベントによって呼び出されます。“Create” という名前の Lambda 関数は、boto3 の CodeBuild モジュールを使用してフィーチャーブランチ用のパイプラインを構築する AWS CodeBuild プロジェクトを作成します。このフィーチャーパイプラインは、ビルドステージとオプションのパイプライン自身を更新するステージから構成されます。”Destroy” という名前の Lambda 関数は、フィーチャーブランチのすべてのリソースとフィーチャーパイプラインをクリーニングする別の CodeBuild プロジェクトを作成します。

図1. アーキテクチャ図

前提条件

このチュートリアルを始める前に、以下の準備が必要です。

  • AWS アカウント
  • AWS CDK のインストール
  • Python3 のインストール
  • Jq(JSON プロセッサ)のインストール
  • 継続的インテグレーション/継続的開発(CI/CD)パイプラインの基本的な理解

初期設定

GitHub からリポジトリをダウンロードします。

# リポジトリをクローンするコマンド
git clone https://github.com/aws-samples/multi-branch-cdk-pipelines.git
cd multi-branch-cdk-pipelines

パイプラインをデプロイしたい AWS アカウントとリージョンに新しい CodeCommit リポジトリを作成し、このリポジトリに上記のソースコードをプッシュします。config.ini ファイルで、repository_nameregion の変数を適宜変更します。

まっさらな Python 環境でセットアップしていることを確かめた上で、依存関係をインストールします。

pip install -r requirements.txt

initial-deploy.sh スクリプトを実行して、開発環境と本番環境のブートストラップを行いデフォルトパイプラインをデプロイします。その際、(1) 開発アカウント ID、(2) 開発アカウント AWS プロファイル名、(3) 本番アカウント ID、(4) 本番アカウント AWS プロファイル名の各パラメータを入力します。

sh ./initial-deploy.sh --dev_account_id <YOUR DEV ACCOUNT ID> --
dev_profile_name <YOUR DEV PROFILE NAME> --prod_account_id <YOUR PRODUCTION
ACCOUNT ID> --prod_profile_name <YOUR PRODUCTION PROFILE NAME>

デフォルトパイプライン

このデフォルト CI/CD パイプラインでは、現在のブランチがデフォルトブランチである場合のみデフォルトブランチのリソースをデプロイするように if 条件を設定しています。デフォルトブランチは CodeCommit のリポジトリからプログラムによって取得されます。また、Amazon Simple Storage Service(Amazon S3)のバケットと 2 つの Lambda 関数を作成しています。バケットは、フィーチャーブランチの CodeBuild アーティファクトを保存する役割を担っています。最初の Lambda 関数は、CodeCommit で新しいブランチが作成されたときにトリガーされます。2 つ目の関数は、ブランチが削除されたときにトリガーされます。

if branch == default_branch:
    
...

    # フィーチャーブランチ用 AWS CodeBuild プロジェクトのアーティファクトバケット
    artifact_bucket = Bucket(
        self,
        'BranchArtifacts',
        encryption=BucketEncryption.KMS_MANAGED,
        removal_policy=RemovalPolicy.DESTROY,
        auto_delete_objects=True
    )
...
    # ブランチ作成時にトリガーされる AWS Lambda 関数
    create_branch_func = aws_lambda.Function(
        self,
        'LambdaTriggerCreateBranch',
        runtime=aws_lambda.Runtime.PYTHON_3_8,
        function_name='LambdaTriggerCreateBranch',
        handler='create_branch.handler',
        code=aws_lambda.Code.from_asset(path.join(this_dir, 'code')),
        environment={
            "ACCOUNT_ID": dev_account_id,
            "CODE_BUILD_ROLE_ARN": iam_stack.code_build_role.role_arn,
            "ARTIFACT_BUCKET": artifact_bucket.bucket_name,
            "CODEBUILD_NAME_PREFIX": codebuild_prefix
        },
        role=iam_stack.create_branch_role)


    # ブランチ削除時にトリガーされる AWS Lambda 関数
    destroy_branch_func = aws_lambda.Function(
        self,
        'LambdaTriggerDestroyBranch',
        runtime=aws_lambda.Runtime.PYTHON_3_8,
        function_name='LambdaTriggerDestroyBranch',
        handler='destroy_branch.handler',
        role=iam_stack.delete_branch_role,
        environment={
            "ACCOUNT_ID": dev_account_id,
            "CODE_BUILD_ROLE_ARN": iam_stack.code_build_role.role_arn,
            "ARTIFACT_BUCKET": artifact_bucket.bucket_name,
            "CODEBUILD_NAME_PREFIX": codebuild_prefix,
            "DEV_STAGE_NAME": f'{dev_stage_name}-{dev_stage.main_stack_name}'
        },
        code=aws_lambda.Code.from_asset(path.join(this_dir,
                                                  'code')))

そして、下記 2 つのイベントに基づいてこれらの Lambda 関数がトリガーされるよう CodeCommit のリポジトリは構成されています。

(1) Git 参照が作成されたとき

# 新しいブランチが作成されたときに Lambda 関数をトリガーするよう AWS CodeCommit を構成
repo.on_reference_created(
    'BranchCreateTrigger',
    description="AWS CodeCommit reference created event.",
    target=aws_events_targets.LambdaFunction(create_branch_func))

(2) Git 参照が削除されたとき

# ブランチが削除されたときに Lambda 関数をトリガーするよう AWS CodeCommit を構成
repo.on_reference_deleted(
    'BranchDeleteTrigger',
    description="AWS CodeCommit reference deleted event.",
    target=aws_events_targets.LambdaFunction(destroy_branch_func))

訳注)“Git 参照” とは Git コミットに分かりやすい名前を付けるための仕組みであり、実態は Git コミットの SHA-1 ハッシュが記されたファイルです。そして、ブランチも対応する Git コミットを参照するために Git 参照の仕組みを利用しています。そのため、Git 参照ファイルの作成・削除を検知することでブランチの作成・削除時の処理をトリガーしています。

Lambda 関数

2 つの Lambda 関数は、各フィーチャーブランチにマッピングされたアプリケーション環境を構築および削除します。ブランチが新規作成されるたびに、Amazon CloudWatch イベントが LambdaTriggerCreateBranch 関数をトリガーします。そして、boto3 の CodeBuild クライアントがビルドフェーズを作成しフィーチャーパイプラインをデプロイします。

Create 関数

Create 関数は、ビルドステージとオプションのパイプライン自身のアップデートステージで構成されるフィーチャーパイプラインをデプロイします。パイプラインはまず CodeCommit リポジトリからフィーチャーブランチのコードをダウンロードした後、CodeBuild を使用してビルドとテストのアクションを開始し、構築されたアーティファクトを S3 バケットに安全に保存します。

Lambda 関数の handler のコードは以下の通りです。

def handler(event, context):
    """Lambda function handler"""
    logger.info(event)

    reference_type = event['detail']['referenceType']

    try:
        if reference_type == 'branch':
            branch = event['detail']['referenceName']
            repo_name = event['detail']['repositoryName']

            client.create_project(
                name=f'{codebuild_name_prefix}-{branch}-create',
                description="Build project to deploy branch pipeline",
                source={
                    'type': 'CODECOMMIT',
                    'location': f'https://git-codecommit.{region}.amazonaws.com/v1/repos/{repo_name}',
                    'buildspec': generate_build_spec(branch)
                },
                sourceVersion=f'refs/heads/{branch}',
                artifacts={
                    'type': 'S3',
                    'location': artifact_bucket_name,
                    'path': f'{branch}',
                    'packaging': 'NONE',
                    'artifactIdentifier': 'BranchBuildArtifact'
                },
                environment={
                    'type': 'LINUX_CONTAINER',
                    'image': 'aws/codebuild/standard:4.0',
                    'computeType': 'BUILD_GENERAL1_SMALL'
                },
                serviceRole=role_arn
            )

            client.start_build(
                projectName=f'CodeBuild-{branch}-create'
            )
    except Exception as e:
        logger.error(e)

ブランチ新規作成時に実行される CodeBuild プロジェクトの buildspec.yaml は以下の通りです。

version: 0.2
env:
  variables:
    BRANCH: {branch}
    DEV_ACCOUNT_ID: {account_id}
    PROD_ACCOUNT_ID: {account_id}
    REGION: {region}
phases:
  pre_build:
    commands:
      - npm install -g aws-cdk && pip install -r requirements.txt
  build:
    commands:
      - cdk synth
      - cdk deploy --require-approval=never
artifacts:
  files:
    - '**/*'

Destroy 関数

2 番目の Lambda 関数は、フィーチャーブランチのリソースの削除を行います。フィーチャーブランチの削除時に、Amazon CloudWatch のイベントがこの Lambda 関数をトリガーします。この関数はフィーチャーパイプラインとそのパイプラインによって作成されたすべての関連リソースを削除する CodeBuild プロジェクトを作成します。CodeBuild プロジェクトの source プロパティには Amazon S3 にアーティファクトとして保存されているフィーチャーブランチのソースコードを指定します。

Lambda 関数の handler のコードは以下の通りです。

def handler(event, context):
    logger.info(event)
    reference_type = event['detail']['referenceType']

    try:
        if reference_type == 'branch':
            branch = event['detail']['referenceName']
            client.create_project(
                name=f'{codebuild_name_prefix}-{branch}-destroy',
                description="Build project to destroy branch resources",
                source={
                    'type': 'S3',
                    'location': f'{artifact_bucket_name}/{branch}/CodeBuild-{branch}-create/',
                    'buildspec': generate_build_spec(branch)
                },
                artifacts={
                    'type': 'NO_ARTIFACTS'
                },
                environment={
                    'type': 'LINUX_CONTAINER',
                    'image': 'aws/codebuild/standard:4.0',
                    'computeType': 'BUILD_GENERAL1_SMALL'
                },
                serviceRole=role_arn
            )

            client.start_build(
                projectName=f'CodeBuild-{branch}-destroy'
            )

            client.delete_project(
                name=f'CodeBuild-{branch}-destroy'
            )

            client.delete_project(
                name=f'CodeBuild-{branch}-create'
            )
    except Exception as e:
        logger.error(e)

ブランチ削除時に実行される CodeBuild プロジェクトの buildspec.yaml は以下の通りです。

version: 0.2
env:
  variables:
    BRANCH: {branch}
    DEV_ACCOUNT_ID: {account_id}
    PROD_ACCOUNT_ID: {account_id}
    REGION: {region}
phases:
  pre_build:
    commands:
      - npm install -g aws-cdk && pip install -r requirements.txt
  build:
    commands:
      - cdk destroy cdk-pipelines-multi-branch-{branch} --force
      - aws cloudformation delete-stack --stack-name {dev_stage_name}-{branch}
      - aws s3 rm s3://{artifact_bucket_name}/{branch} --recursive

フィーチャーブランチの作成

ローカルリポジトリで以下のコマンドを使用して新しいフィーチャーブランチを作成します。user-feature-123 はあなたのフィーチャーブランチ独自の名前に置き換えてください。ただし、このブランチ名は CodePipeline の命名規則に従わなければならないことに注意してください。このブランチ名は、このチュートリアルの後半で独自のパイプラインの名前に使用されます。

# フィーチャーブランチを作成
git checkout -b user-feature-123
git push origin user-feature-123

最初の Lambda 関数で CodeBuild プロジェクトをデプロイし、その後フィーチャーパイプラインをデプロイします。これには数分かかることがあります。AWS Console にログインすると、CodeBuild のコンソールページで CodeBuild プロジェクトが実行されていることが確認できます。

図2. AWS コンソール – CodeBuild プロジェクト

ビルドが正常に終了すると、CodePipeline のコンソールページにデプロイされたフィーチャーパイプラインが表示されます。

図3. AWS コンソール – CodePipeline パイプライン

このソリューションを示すためのインフラリソースとして、AWS CDK Samples の Lambda S3 トリガープロジェクトを使用しています。コンテンツは src ディレクトリ内に配置され、パイプラインによってデプロイされます。Lambda のコンソールページにアクセスすると、デフォルトのパイプラインによってデプロイされた関数と今回のフィーチャーパイプラインによってデプロイされた関数の 2 つの関数が表示されます。

図4. AWS コンソール – Lambda 関数

フィーチャーブランチの削除

フィーチャーブランチを削除する方法には、2 つの一般的な方法があります。1 つ目はプルリクエストに関連する方法で、「PR」とも呼ばれます。これは、フィーチャーブランチをデフォルトブランチにマージして戻すときに行われます。マージされるとそのフィーチャーブランチは自動的に閉じられます。2 つ目の方法は、以下の git コマンドを実行して、明示的にフィーチャーブランチを削除する方法です。

# ローカルのブランチを削除
git branch -d user-feature-123
# リモートのブランチを削除
git push origin --delete user-feature-123

これにより、フィーチャーブランチのリソースを削除するための CodeBuild プロジェクトがトリガーされます。CodeBuild でリソースを削除している間のプロジェクトのログは、[Build history] で確認できます。

図5. AWS コンソール – CodeBuild プロジェクト

クリーンアップ

今後の課金を避けるため、使用したそれぞれのアカウントの AWS コンソールにログインし、デプロイしたリージョンの AWS CloudFormation コンソールに移動し、main とフィーチャーブランチのスタックを選択し [Delete] をクリックしてください。

まとめ

この記事では、イベント駆動戦略と AWS CDK を活用し AWS CDK Pipelines を使用してマルチブランチのパイプラインフローを実装する方法を紹介しました。説明したソリューションは Lambda と CodeBuild を活用して複数のブランチとパイプラインのリソースの動的オーケストレーションを提供します。
CDK Pipelines の詳細とそのすべての使用方法については、CDK Pipelines のリファレンスドキュメントをご参照ください。

この記事の著者について

Iris Kraja

Iris はニューヨークを拠点とする AWS プロフェッショナルサービスのクラウドアプリケーションアーキテクトです。サーバーレス技術、イベント駆動型アーキテクチャ、DevOps に強い関心を持ち、お客様が最新の AWS クラウドネイティブソリューションを設計・構築するための支援に尽力しています。仕事以外では、ハイキングと自然の中でできるだけ多くの時間を過ごすことを趣味としています。

Jan Bauer

Jan は AWS プロフェッショナルサービスのクラウドアプリケーションアーキテクトです。サーバレスコンピューティング、機械学習など、クラウドコンピューティングに関わるすべてのことに関心を持っています。

Rolando Santamaria Maso

Rolando はドイツを拠点とする AWS プロフェッショナルサービスのシニアクラウドアプリケーション開発コンサルタントです。彼は、モダンアプリケーションアーキテクチャと開発のベストプラクティスに特に重点を置いて AWS クラウドにおけるお客様のワークロードの移行と近代化を支援していますが、AWS CDK を使用して IaC のコードも書きます。仕事以外では、オープンソースプロジェクトを維持したり、家族や友人と過ごす時間を楽しんでいます。

Caroline Gluck

Caroline はニューヨークを拠点とする AWS クラウドアプリケーションアーキテクトで、顧客のクラウドネイティブなデータサイエンスアプリケーションの設計と構築を支援しています。Caroline は根っからの開発者であり、サーバーレスアーキテクチャと機械学習に熱意を持っています。余暇は、旅行、料理、家族や友人と過ごす時間を楽しんでいます。

この記事は、 Multi-branch pipeline management and infrastructure deployment using AWS CDK Pipelines を翻訳したものです。

翻訳は Solutions Architect の Lijie Wang が担当しました。