Get better at building AWS CDK constructs DOP401 参加レポート #AWSreInvent

re:Invent2023で「Get better at building AWS CDK constructs」のワークショップに参加したのでレポートします。
2023.12.01

はじめに

今回はre:Invent2023で開催されたワークショップ「Get better at building AWS CDK constructs」の内容をご紹介します。内容は、CDKで自作のConstruct作る場合の作り方をL2、L3に分けて紹介するものでした。CDKをある程度使っていて、複数のリポジトリやPJ、または外部に公開するために有用なないようでした。どんな内容だったのか雰囲気も含めて紹介します。

ワークショップ概要

登壇者

  • Aaron Sempf Principal Solution Architect
  • Om Jha Prakash Jha Senior Solution Architect

概要

In this workshop, dive deep into how to design AWS CDK constructs, which are reusable and shareable cloud components that help you meet your organization’s security, compliance, and governance requirements. Learn how to build, test, and share constructs representing a single AWS resource, as well as how to create higher-level abstractions that include built-in defaults and allow you to provision multiple AWS resources. You must bring your laptop to participate.

翻訳

このワークショップでは、組織のセキュリティ、コンプライアンス、およびガバナンス要件を満たすのに役立つ、再利用可能で共有可能なクラウドコンポーネントであるAWS CDK Constructを設計する方法を深く掘り下げます。単一のAWSリソースを表すコンストラクトを構築、テスト、共有する方法だけでなく、ビルトインのデフォルト設定を含み、複数のAWSリソースをプロビジョニングできる、より高いレベルの抽象層を作成する方法を学びます。参加にはラップトップが必要です。

前説明

最初に大まかな説明があったので、登壇者の話した内容を要約した形で記載します。

CDKはApp層、Stack層、Construct層で構築されます。

Constructはベースとなる実装を抽象化し、propsによって使用者にむけてカスタマイズを提供します。CDKのApp層はコンストラクトの階層を作成するために使用します。App内では、1つ以上のStackがインスタンス化され、Stack内で1つ以上のConstructやリソースがインスタンス化されます。

単純な構造をStackに実装すると、右側のツリーのようになります。論理IDを使ってリソースに名前を付けます。なので、論理IDはスコープ内で一意でなければなりません。

Constructはリソースと1:1対応になるもの、上位で抽象化されたL2 Construct、さらに複数のリソースを組み合わせたものなどがあるL3 Constructがあります。ecs-patternsではLBなどをリソースに含みます。

AWS CDKを使用して独自のカスタムリソースを実装できるように、利用可能な作業のほとんどを抽象化できます。利用者の視点からは、カスタムリソースを利用していることを感じないので、他のコンストラクトと同じように感じます。以下のコードは、リソースの作成、オブジェクトの削除、およびリソースへの招待に反応します。

コンストラクトを開発する最も一般的なユースケースの1つは、一番上にあるように、単純なデフォルトや単純な論理機能を持つレベルやConstructをオープンソースとして開発することです。次に、あなたの組織や要件に合わせてL3のConstructを開発し、もう少し複雑なものを開発するかもしれません。そして最後に、おそらくより一般的な使用例として、複雑なサービスのコンポジションをまとめて、ソリューションを組織内やチーム内で共有するものです。

開発チーム全員がデプロイ環境やパイプラインの構築について心配する必要はなく、何を構築するかに集中するだけです。そのため、L2やL3の構築物を開発するチームからスタートし、組織間で共有できるライブラリを開発したり、オープンソースで開発している場合はGitHubのリポジトリで共有したりします。Constructを使用しているチームは使うだけでなく、コンポーネントを再開発したり、追加したりといったことができます。

では、Construct Libraryを開発する際の注意点をいくつか挙げてみます。jssiを使用して公開すること、Stackとして提供しないこと、環境ファイルからの読み込みを避けること、ConstructのIDを変更しないこと、Constructをコンプライアンスのために提供しないこと。

また今日のワークショップにはボーナスセクションがあります。ワークショップをすべて終えていれば、テストのやり方を教えてくれるセクションがあります。少なくともスナップショットテストとアサーションテストの違いや、実際のプラットフォームでインフラ要件がテストされているかどうかを検証するバリデーションテストについては見ておいてください。

ワークショップ本体

ワークショップ自体は、AWSがワークショップ用のアカウントなどを払い出してくれる、Workshop Studioで実施されました。

内容としては、カレンダー用の完全に新しい機能をCDKのカスタムリソースでL2 Constructとして作るもの。その後に、L3 ConstructとしてCodePipelineを動作させた際、各ステージをLambdaで検証したりアラームを発生させるConstructの作成がありました。またボーナストラックとして、Constructのテスト方法やConstructを外部に公開する方法などが紹介されていました。

L2 Construct

新しいカレンダーConstructを作成して、カスタムリソースを使いSSMのカレンダーを更新するConstrcutの実装が紹介されました。紹介の中で単にCDKを書く場合と比べて、propsの設計方針(インターフェースを更新するには、インターフェースをさらに拡張する方法)が紹介されました。

例えば、カレンダーの情報を受け取りる際データソースはS3やファイルなど拡張性をもたせる必要があります。その場合、ただ個別に定義するのではなくBaseとなるInterfaceを定義してそれを拡張することが推奨されていました。例えば以下のようなコードです。単にファイルパスを文字列として受け取る場合と、S3にアクセスする場合はバケットとロール指定などがそれぞれ入っていて拡張性があります。

import { IRole } from "aws-cdk-lib/aws-iam";
import { IBucket } from "aws-cdk-lib/aws-s3";

/**
 * Options for creating a calendar object. */
export interface CalendarLocationOptionsBase {
  /**
   * The name of the calendar file.
   */
  readonly calendarName: string;
}

/**
 * Options for creating a calendar from a local file path */
export interface LocalPathOptions extends CalendarLocationOptionsBase {
  /**
   * The relative path to the calendar file.
   */
  readonly calendarPath: string;
}

/**
 * Options for creating a calendar from a file in a S3 Bucket. */
export interface S3LocationOptions extends CalendarLocationOptionsBase {
  /**
   * The bucket where the calendar is stored.
   */
  readonly bucket: IBucket;
  /**
   * The role used for getting the calendar file.
   *
   * @default - A role created by this function (we don't really know yet and will update this later). */
  readonly role?: IRole;
}

/**
 * The source types for the calendar file. */
export enum CalendarSourceType {
  /**
   * The calendar source is a local path.
   */ 
  PATH = "path",
  /**
   * The calendar source is an S3 Bucket. */
  S3_OBJECT = "s3Object",
}

上記のようにサンプルを交えて、コードをどう抽象化すべきなのか、カスタムリソースはどのように実装すべきかが語られていました。

L3 Construct

L3 ConstructとしてCodePipelineのステージの変更を検知する機能を実装していました。検知するLambdaと合わせて異常時のアラームなども合わせて実装されていました。内容としては以下のようなスケルトンに対して、ステップバイステップでコードを構成していきます。

export class ChangeController extends Construct {
  constructor(scope: Construct, id: string, props: ChangeControllerProps) {
    super(scope, id);
    /**
     * Code to import ChangeController function goes here
     */
    /**
     * Code to add policies to ChangeController function goes here
     */
    /**
     * Code to add lambda error alarm goes here
     */
    /**
     * Code to add rule with event target goes here
     */
  }
}

筆者としてはあまりL3 Constructを作ることはなかったので、複数リソース組み合わせた抽象化をこのようにするのかと勉強になりました。

ボーナストラック

ボーナストラックでは、作成したConstructを自分でテストする方法としてスナップショットテストとFine-grained Asssertionが紹介され、最終的にはConstruct Hubに公開するまでの方法が紹介されています。総じて独自のConstrcutの作成、テスト、外部公開について技術を身につけることが出来る内容でした。

所感

実は昨年もあった内容だったのですが、記事化できていなかったので今回記事にしました。CDKをフルに使うような組織では、Constructの構築が重要になるので、抽象化の勘所やテスト方法までしれて、有用な内容でした。re:Invent参加される際は是非申し込んでみて下さい!