Amazon Web Services ブログ

AWS CDK Constructを使用してチーム間のコラボレーションを強化する

チームを編成して優れたソフトウェア製品を提供するには、さまざまな方法があります。Amazon の Two-Pizza チームのように、製品に関するエンドツーエンドの責任を単一のチームに割り当てている企業もあれば、複数のチームがインフラストラクチャ (またはプラットフォーム) チームとアプリケーション開発チームの間で責任を分担している企業もあります。この記事では、AWS Cloud Development Kit (CDK) を活用して Split-Team アプローチの場合に、コラボレーションの効率をどのように改善できるかについてのガイダンスを提供します。

AWS CDK は、クラウドアプリケーションリソースを定義するためのオープンソースのソフトウェア開発フレームワークです。そのためには、TypeScript、Python、Java、C#、Go などの使い慣れたプログラミング言語を使用します。これにより、従来のインフラストラクチャでは AWS CloudFormation や HashiCorp Terraform などの IaC ツールで表現されてきたインフラストラクチャを定義するコードと、アプリケーションをバンドル、コンパイル、パッケージングするコードを組み合わせることができます。

これは、製品に関連するすべてのコードを 1 か所と 1 つのプログラミング言語にまとめることができるため、エンドツーエンドの責任を持つ自律的なチームに最適です。1 つのチームでインフラストラクチャコードとアプリケーションコードを別のリポジトリに分ける必要はありませんが、チームが分割されるモデルについてはどうでしょうか。

大企業は通常、インフラストラクチャ (またはプラットフォーム) チームとアプリケーション開発チームの間で責任を分担します。この記事では複数のチームが関与している場合でも、AWS CDK を使用してチームの独立性と俊敏性を確保する方法を見ていきます。チームごとに異なる責任と各チームが作成した成果物を見ていきます。また、チームがスムーズに連携する方法についても説明します。

このブログ記事は、AWS CDK とその概念に関する基本的な知識があることを前提としています。さらに、イベント駆動型アーキテクチャに関する非常に高いレベルの理解も必要です。

チームトポロジ

まず、さまざまなチームトポロジと各チームの責任について簡単に見てみましょう。

One-Team アプローチ

このブログ記事では、以降で説明する Split-Team アプローチに焦点を当てます。ただし、「1 つのチームがアプリケーションをエンドツーエンドで所有する」という “One-Team” アプローチが何を意味するのかを理解しておくと役に立ちます。この部門の枠を超えたチームは、次に実装する機能、使用するテクノロジー、そしてその結果得られるインフラストラクチャとアプリケーションコードの構築方法とデプロイ方法を独自に決定します。チームの責任は、インフラストラクチャ、アプリケーションコード、デプロイメント、開発したサービスの運用です。

このような環境で AWS CDK アプリケーションを構築する方法に興味がある場合は、Alex Pulver のブログ記事 “Recommended AWS CDK project structure for Python applications” を参照してください。

Split-Teamアプローチ

実際には、アプリケーション開発とインフラストラクチャの開発と展開を別々のチームに分けるお客様が多く居ます。

インフラストラクチャチーム

ここで言及するインフラストラクチャチームは、プラットフォームチームまたは運用チームとも呼ばれます。インフラストラクチャチームは、他のチームがアプリケーションを実行するために使用する共有のインフラストラクチャを設定、デプロイ、運用します。これには、Amazon SQS キュー、Amazon Elastic Container Service (Amazon ECS) クラスター、新しいバージョンのアプリケーションを本番環境に導入するために使用される CI/CD パイプラインなどがあります。
アプリケーションチームが開発したアプリケーションパッケージを AWS にデプロイして実行させること、およびアプリケーションの運用サポートを提供する事がインフラストラクチャチームの責任です。

アプリケーションチーム

従来、アプリケーションチームはアプリケーションのパッケージ (JAR ファイルや npm パッケージなど) を提供するだけで、AWS でのデプロイ、設定、実行の方法を考えるのはインフラストラクチャチームの責任でした。しかしながら、インフラストラクチャチームは複数のチームが開発したさまざまなアプリケーションをサポートしなければならないため、このような従来の手法ではボトルネックになることがよくあります。さらに、インフラストラクチャチームは多くの場合、それらのアプリケーションの内部構造についてほとんど知識がありません。これはしばしば、サービスのために最適化された選択肢を提供できないことにつながります。インフラストラクチャチームが提供するサービス実行のための選択肢が十分でない場合、アプリケーションチームはワークロードに最適化されたオプションを使用できません。

そのため、このブログ記事では、アプリケーションチームの従来の責任を拡張しています。チームはアプリケーションを提供し、さらにアプリケーションの実行に必要なインフラストラクチャの説明も提供します。「必要なインフラストラクチャ」とは、アプリケーションの実行に使用される AWS サービスのことです。このインフラストラクチャの説明は、インフラストラクチャチームが理解できる形式で記述する必要があります。

このような責任範囲の変化によってアプリケーションチームに追加のタスクが追加されることは理解していますが、長期的には努力する価値があると考えています。これが DevOps の概念を組織に導入する出発点になり得ます。ただし、このブログ記事で説明されている概念は、アプリケーションチームにこの責任を課したくない場合にも、まだ有効です。誰が何を提供するのかという境界線が、インフラストラクチャチームの方へ更に移るだけです。

このアプローチを成功させるには、アプリケーションの引き渡し方、インフラストラクチャの定義、本番環境への導入方法について、2 つのチームが共通の形式について合意する必要があります。Construct というコンセプトを備えた AWS CDK は、そのための役立つ手段を提供します。

入門: AWS CDK Construct

このセクションでは、コードを構築するために AWS CDK が提供する概念と、これらの概念を使用して CDK プロジェクトをチームトポロジにフィットさせる方法を見ていきます。

Constructs

Construct は AWS CDK アプリケーションの基本的な構成要素です。AWS CDK アプリケーションは複数の Construct で構成されており、最終的には AWS CloudFormation によってデプロイされる方法と内容が定義されます。

AWS CDK には、AWS サービスをデプロイするための Construct が付属しています。ただし、使用できるのは AWS CDK によって提供される Construct だけに限定されないことを理解しておくことが重要です。AWS CDK の真の利点は、デフォルトの Construct の上に独自の抽象化を作成して、特定の要件を満たすソリューションを作成できることです。これを実現するには、独自のカスタム Construct を作成、公開、使用する必要があります。特定の要件をコード化し、抽象化レベルを高め、他のチームがその Construct を利用したり使用したりできるようにします。

以降ではカスタム Construct を使用して、アプリケーションとインフラストラクチャチームの責任を分担します。アプリケーションチームは、アプリケーションコードの実行に必要なインフラストラクチャとその 設定を記述した Construct をリリースします。インフラストラクチャチームはこの Construct を使用して AWS にワークロードをデプロイして運用します。

Split-Team で AWS CDK を使用する方法

次は、AWS CDK を使用してアプリケーションチームとインフラストラクチャチームの間で責任を分担する方法を見てみましょう。サンプルシナリオを紹介し、このシナリオにおける各チームの責任を説明します。

シナリオ

架空のアプリケーション開発チームが AWS Lambda 関数を作成し、AWS にデプロイします。Amazon SQS キュー内のメッセージがこの関数を呼び出します。関数が注文を処理し (この例では詳細な意味は関係ありません)、各注文はキュー内のメッセージによって表されるとします。

アプリケーション開発チームは AWS Lambda 関数を柔軟に作成できます。どのランタイムを使用するか、どのくらいのメモリを設定するかを決定できます。関数が処理する SQS キューは、インフラストラクチャチームによって作成されます。アプリケーションチームは、メッセージがどのようにキューに入るのかを知る必要はありません。

これで、チームごとに実装例を見ていきましょう。

アプリケーションチーム

アプリケーションチームは、アプリケーションコード (JAR ファイルや npm モジュールなど) と、アプリケーションの実行に必要なインフラストラクチャ (AWS Lambda 関数とその設定) を AWS にデプロイするための AWS CDK Construct という 2 つの異なる成果物を担当します。

これらの成果物のライフサイクルは異なります。アプリケーションコードは、実行されるインフラストラクチャよりも頻繁に変更されます。だからこそ、成果物は別々にしておきたいのです。これにより、それぞれの成果物は、変更された場合のみ独自のペースでリリースできます。

このような個別のライフサイクルを実現するためには、アプリケーションのリリースは CDK Construct のリリースから完全に独立している必要があることに注意することが重要です。これは、CDK Construct 内でアプリケーションコードをビルドしてパッケージ化する標準的な CDK の方法と比較して、チームを分けるという私たちのアプローチに合っています。

しかし、このサンプルソリューションではどのように実現するのでしょうか?チームは CDK に関係のないアプリケーションを構築して公開します。
この Construct を含む CDK スタックを合成すると、指定されたバージョン番号のビルド済みアーティファクトを AWS CodeArtifact からダウンロードし、それを使用して Lambda 関数のための zip ファイルを作成します。CDK の合成の間は、アプリケーションパッケージのビルドは行われません。

Constructとアプリケーションコードを分離したので、CodeArtifact から取得するアプリケーションコードの特定のバージョンを CDK Construct に伝える方法を見つける必要があります。この情報をコンストラクターのプロパティを介してConstructに渡します。

アプリケーションチームの責任範囲外のインフラストラクチャへの依存関係については、依存性注入のパターンに従います。共有 VPC や Amazon SQS キューなどの依存関係は、インフラストラクチャチームから Construct に渡されます。

例を見てみましょう。SQS キューへの外部依存関係を、目的の appPackageVersion とその CodeArtifact の詳細とともに渡します。

export interface OrderProcessingAppConstructProps {
    queue: aws_sqs.Queue,
    appPackageVersion: string,
    codeArtifactDetails: {
        account: string,
        repository: string,
        domain: string
    }
}

export class OrderProcessingAppConstruct extends Construct {

    constructor(scope: Construct, id: string, props: OrderProcessingAppConstructProps) {
        super(scope, id);

        const lambdaFunction = new lambda.Function(this, 'OrderProcessingLambda', {
            code: lambda.Code.fromDockerBuild(path.join(__dirname, '..', 'bundling'), {
                buildArgs: {
                    'PACKAGE_VERSION' : props.appPackageVersion,
                    'CODE_ARTIFACT_ACCOUNT' : props.codeArtifactDetails.account,
                    'CODE_ARTIFACT_REPOSITORY' : props.codeArtifactDetails.repository,
                    'CODE_ARTIFACT_DOMAIN' : props.codeArtifactDetails.domain
                }
            }),
            runtime: lambda.Runtime.NODEJS_16_X,
            handler: 'node_modules/order-processing-app/dist/index.lambdaHandler'
        });
        const eventSource = new SqsEventSource(props.queue);
        lambdaFunction.addEventSource(eventSource);
    }
}

lambda.Code.fromDockerBuild(...) というコードに注意してください。ここでは AWS CDK の機能を使用して、Docker ビルドを介して Lambda 関数のコードをバンドルしています。提供された Dockerfile 内で発生する処理は以下のものだけです。

  • 事前ビルド済みのアプリケーションコードのパッケージが格納されている AWS CodeArtifactリポジトリへのログイン
  • アプリケーションコードのアーティファクトを AWS CodeArtifact (この場合は npm 経由) からダウンロードしてインストールすること

AWS CDK アセットをビルド、バンドル、デプロイする方法について詳しく知りたい場合は、 Cory Hall による記事 “Building, bundling, and deploying applications with the AWS CDK” を強くお勧めします。ここで説明している内容よりもずっと詳細に説明されています。
Dockerfile の例を見ると、上記の 2 つのステップが分かります。

FROM public.ecr.aws/sam/build-nodejs16.x:latest

ARG PACKAGE_VERSION
ARG CODE_ARTIFACT_AWS_REGION
ARG CODE_ARTIFACT_ACCOUNT
ARG CODE_ARTIFACT_REPOSITORY

RUN aws codeartifact login --tool npm --repository $CODE_ARTIFACT_REPOSITORY --domain $CODE_ARTIFACT_DOMAIN --domain-owner $CODE_ARTIFACT_ACCOUNT --region $CODE_ARTIFACT_AWS_REGION
RUN npm install order-processing-app@$PACKAGE_VERSION --prefix /asset

以下の点にご注意ください。

  • npm のインストールコマンドで--prefix /asset を使用します。これにより、CDK がコンテナーにマウントするフォルダーに依存関係をインストールするよう npm に指示します。Docker build の出力に含まれるはずのすべてのファイルをここに配置する必要があります。
  • aws codeartifact loginを続行するには、適切な権限を持つ認証情報が必要です。これを AWS CodeBuildCDK Pipelines 内で実行する場合は、使用するロールに適切なポリシーがアタッチされていることを確認する必要があります。

インフラストラクチャチーム

インフラストラクチャチームは、アプリケーションチームが公開した AWS CDK Constructを使用します。彼らはアプリケーション全体を構成する AWS CDK スタックを所有しています。おそらく、これはインフラストラクチャチームが所有する複数のスタックのうちの 1 つに過ぎないでしょう。他のスタックは、共有インフラストラクチャ (VPC、ネットワークなど) や他のアプリケーションを作成する可能性があります。

アプリケーションのスタック内では、インフラストラクチャチームがアプリケーションチームの Construct を利用してインスタンス化し、依存関係を解決してから、適切と思われる手段 (AWS CodePipelines、GitHub Actions、またはその他の CI/CD ツール) でスタックをデプロイします。
アプリケーションチームの Construct への依存関係は、インフラストラクチャチームの CDK アプリの package.json に表れています。

{
  "name": "order-processing-infra-app",
  ...
  "dependencies": {
    ...
    "order-app-construct" : "1.1.0",
    ...
  }
  ...
}

作成された CDK スタックには、アプリケーションパッケージの依存バージョンと、インフラストラクチャチームが追加情報(使用するキューなど)を渡す方法が表示されます。

export class OrderProcessingInfraStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);   

    const orderProcessingQueue = new Queue(this, 'order-processing-queue');

    new OrderProcessingAppConstruct(this, 'order-processing-app', {
       appPackageVersion: "2.0.36",
       queue: orderProcessingQueue,
       codeArtifactDetails: { ... }
     });
  }
}

新しいリリースの普及

これで、各チームが所有するアーティファクトとともに、各チームの責任を整理できるようになりました。しかし、アプリケーションチームが行った変更を本番環境に反映するにはどうすればよいでしょうか。あるいは、アプリケーションチームの最新バージョンのアーティファクトを使用して、インフラストラクチャチームの CI/CD パイプラインを呼び出すにはどうすればよいでしょうか。

アプリケーションまたは AWS CDK Construct のいずれかの新しいバージョンが公開されるたびに、インフラストラクチャチームのアプリケーションチームのアーティファクトへの依存関係を更新する必要があります。依存関係が更新されたら、リリースパイプラインを開始できます。

1 つのアプローチは、Amazon EventBridge 経由で AWS CodeArtifact によって発行されたイベントを受け取ることです。リリースごとに、AWS CodeArtifact は Amazon EventBridge にイベントを発行します。そのイベントのペイロードから新しいリリースのバージョン番号を抽出し、CDK Construct への依存関係 (例えば CDK アプリケーションの package.json) を更新するワークフローを開始するか、インフラストラクチャチームが利用する Construct に渡す appPackageVersion を更新するワークフローを開始できます。
新しいアプリケーションのリリースがシステム内を流れる仕組みは次のとおりです。

A release of the application package triggers a change and deployment of the infrastructure team's CDK Stack

図 1 — アプリケーションパッケージがリリースされると、インフラストラクチャチームの CDK スタックが変更され、デプロイされる

  1. アプリケーションチームは新しいアプリケーションバージョンを AWS CodeArtifact に公開します。
  2. CodeArtifact は Amazon EventBridge でイベントをトリガーします。
  3. インフラストラクチャチームが発行されたイベントを受け取ります。
  4. インフラストラクチャチームは、最新の appPackageVersion が含まれるように CDK スタックを更新します。
  5. インフラストラクチャチームの CDK スタックがデプロイされます。

図 2 – アプリケーションチームのCDK Constructの変更をトリガーにインフラストラクチャチームのCDK スタックが変更され、デプロイされる。

そして、CDK Constructの新しいバージョンのリリースも非常によく似ています。

  1. アプリケーションチームは新しい CDK Construct を AWS CodeArtifact に公開します。
  2. CodeArtifact は Amazon EventBridge でイベントをトリガーします。
  3. インフラストラクチャチームが発行されたイベントを受け取ります。
  4. インフラストラクチャチームは依存関係を最新の CDK Construct に更新します。
  5. インフラストラクチャチームの CDK スタックがデプロイされます。

このようなワークフローがどのようになるかについては詳しく説明しません。なぜなら、各チーム向けに高度にカスタマイズされている可能性が高いからです (コードリポジトリや CI/CD に使用されるさまざまなツールを考えてみてください)。ただし、これをどのように実現できるかについてのアイデアをいくつか紹介します。

CDK Construct 依存関係の更新

CDK Construct の依存関係バージョンを更新するには、インフラストラクチャチームの package.json (または pom.xml のような依存関係の追跡に使用される他のファイル) を更新する必要があります。ソースコードをチェックアウトして npm install sample-app-construct@NEW_VERSION (NEW_VERSION は EventBridge イベントペイロードから読み取られた値) のようなコマンドを発行するオートメーションを構築できます。次に、この変更をメインブランチに組み込むためのプルリクエストを自動的に作成します。これがどのようなものかについてのサンプルは、ブログ記事 “Keeping up with your dependencies: building a feedback loop for shared librares” を参照してください。

appPackageVersion の更新

インフラストラクチャチームの CDK スタック内で使用されている appPackageVersion を更新するには、上記と同じ方法に従うか、CDK の機能を使用してAWS Systems Manager (SSM) Parameter Store から読み取ることができます。そうすれば、appPackageVersion の値をソース管理に入力するのではなく、SSM パラメータストアから値を読み取ることになります。その方法については、AWS CDK のドキュメントに Systems Manager Parameter Store から値を取得するというものがあります。次に、パラメータが変更されたイベントに基づいてインフラストラクチャチームのパイプラインを開始します。

常に何がデプロイされているかを明確に把握し、CloudFormation で使用されているパラメーター値を確認するために、合成時の Systems Manager の値を読み取りで説明されているオプションを使用することをおすすめします。

結論

複数のチーム (この場合はアプリケーション開発チームとインフラストラクチャチーム) が協力してアプリケーションの新しいバージョンを本番環境に導入する場合でも、AWS Cloud Development Kit とその Construct のコンセプトがチームの独立性と俊敏性を確保するのにどのように役立つかを見てきました。そのために、アプリケーションチームにアプリケーションコードだけでなく、アプリケーションを実行するために使用するインフラストラクチャの部分の管理も任せました。すべての共有インフラストラクチャと最終的なデプロイメントはインフラストラクチャチームが管理し、アプリケーションチームの Construct はこの中で利用されるため、ここまで見てきた Split-Team アプローチと合致しています。

著者

Picture of the author Joerg Woehrle ソリューションアーキテクトとして、Jörg はドイツの製造業のお客様と協力しています。2019 年に AWS に加入する前は、開発者、DevOps エンジニア、SRE など、さまざまな役割を担当していました。そのため、Jörg はビルドと自動化のことが大好きです。AWS Cloud Development Kit に恋をしたのです。
Picture of the author Mohamed Othman Mo は2020年にテクニカルアカウントマネージャーとして AWS に加入し、7年間の AWS DevOps の実践経験と6年間のシステム運用管理者としての経験を持っています。 AWS 内の2つのコミュニティ(クラウド運用とビルダーエクスペリエンス)のメンバーであり、CI/CD パイプラインと AI for DevOps を使って、ビジネスニーズに合った適切なソリューションを持っていることを確認するために、お客様をサポートすることに焦点を当てています。

本記事は、 Joerg Woehrle と Mohamed Othman による “Improve collaboration between teams by using AWS CDK constructs” を翻訳したものです。翻訳はソリューションアーキテクトの平川 大樹が担当しました。