[レポート]AWS CDKのL2、L3 Constructを実装する具体的なテクニックと、CDKのベストプラクティスを学ぶワークショップに参加しました #reinvent

re:Invent 2022でCDK Constructの中身と実装時のベストプラクティスに詳しくなれるワークショップに参加してきたセッションレポートです。
2022.11.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。CX事業本部Delivery部のきんじょーです。
re:Inventの現地からお届けしています。

AWS CDKに簡単なIssueについてのPRは出したことがあるものの、がっつりとした実装のコントリビュートをしたいと思っていたところ、re:Inventでちょうど良いワークショップが開かれていたので参加してきました。

CDKを使うだけではなく、一歩踏み込んでコントリビュートしてみたい方にはもちろん、Constructの仕組みを理解して実装やトラブルシューティングを効率化したい方にもオススメのワークショップだったので、このレポートブログでご紹介します。

Constructとは?

「Stack」は同時にデプロイするリソースをまとめた論理的な概念で、「Resource」はDynamoDB、Lambdaなどの個々のリソースをデプロイします。

では「Construct」はというと、オブジェクト指向の文脈で言うクラスに該当します。
個々のリソースや、SQS + Lambda、Stack、CDK Appもすべて「Construct」です。

ConstructはBase Constructクラスを拡張して実装します。 CDKのフレームワークでは、ConstructのAPIについての制限はなく、開発者は自由にメソッドやプロパティを定義可能です。 ただし、AWS Constructs Libraryに含まれるConstructは、すべてのAWSリソースで一貫した体験を提供するためのガイドラインと共通のパターンにしたがっています。

Constructのコンストラクター渡すid(リソースの論理ID)はscope内でユニークである必要があり、リソースに名前を付けるために使用されます。 scopeには通常、このConstructをインスタンス化するStackなど該当しますが、さらに上位のConstructが値する場合もあります。

Stackが2つのConstructをインスタンス化するコードを例にあげると、Construct同士が階層構造になっていることがわかります。これをConstruct Treeと呼びます。

Construct Tree

明示的にリソースの名前を指定しない場合、名前はConstruct Treeとハッシュによって命名されます。 合成されたCloudFormationのリソース名の例が上記です。

Construct Treeの中で、各Constructは、nodeのプロパティを通じて相互にnodeを参照可能です。 CDKのConstructのClassそのものを参照できるわけではないので注意してください。

nodeが持つプロパティの意味は以下の通りです。

プロパティ 説明
node.children 配下のConstructの配列
node.id Constructのid
node.path rootから見た該当Constructのパス
node.root Treeの最上位のConstruct
node.scope 一つ上のConstruct
node.scopes 親となるConstructすべての配列
node.uniqueId 英数字で構成されるTreeの中で一意になるID
デフォルトではpathのhashで生成される

Construct Treeは暗黙的に、cdk synthした際のCDK→CloudFormationへと合成する順序を決定していますが、明示的に順序を指定することも可能です。

Constructの3つのレベル

Constructには3つのレベルがあります。

  • L1: CloudFormation Resources
    • CloudFormationの仕様から自動生成されるConstructで、CloudFormationのリソースと1対1で対応する
    • 一見するとCloudFormationと変わらないように思えるが、JSONやYAMLではなくプログラミング言語を使用するため、型チェックが効き、エディターのコード補完を受けながら記述することが可能
  • L2: AWS constructs
    • 複数のリソースから成り、L1より抽象化されたConstruct
    • たとえば、IAM関連のパーミッションやメトリクス、ログ出力の追加など、少ない記述で通常使用するパターンのリソースを定義可能
  • L3: Purpose-build constructs
    • 特定のユースケースで使用するインフラストラクチャのパターンをさらにハイレベルで抽象化したConstruct
    • ECSを例に挙げると、ALBやFargate含むECSを使用してアプリケーションをデプロイするインフラをハイレベルで抽象化したaws-ecs-patternsというConstructがあります。

Custom Resources

CloudFormationで定義できず、直接AWS SDKを叩く必要がある機能も、CloudFormationのCustom Resourceを利用することで、CDKで定義することが可能です。 Custom Resourceの実態はLambda Functionで、LambdaからAWS SDKを実行します。

急にCustom Resourceの話が出てきましたが、この後のワークショップで使用するためでした。

Construct libraries

L3 Constructsは先ほどのaws-ecs-patternsのようなOSSで提供されているパターンだけでなく、あなたの組織に合わせて自由に作成可能です。 たとえば、CognitoとAPI Gateway、Lambdaを組み合わせたセキュアなREST APIや、CloudWatchからのKinesis連携など、組織で共通して使用するユースケースに応じてL3 Constructsを作成すると良いでしょう。

今回のワークショップでは、特定の日に起動を停止するCI/CDパイプラインをL3 Constructsで構築するというテーマでした。

作成したL3 Constructsは、AWS Code ArtifactやJFrog Artifactory、GitHub packagesなどで一元管理して、各アプリケーションチームはそれを利用します。

CDKのBest Practice

Construct librariesの実装時

  1. jsiiを使用して公開すること
    • TypeScriptで記述することで、jsiiを通して別の言語にトランスパイルでき、開発者は好きな言語を選べるため
  2. StackではなくConstructsレベルで留めること
    • Constructsのレベルで留めることで、開発者は自由にconstructsを組み合わせてStackが構築できるため
  3. 設定値は環境変数ではなくConstructのpropsから受け取ること
    • コンストラクトの構成を決めるのは、合成時ではなく実装時が好ましいため
  4. 単体テストを書くこと
  5. 便利さのために使い、コンプライアンスのためには使わない
    • コンプライアンス要件の準拠にはCDKだけではなく他のアプローチも必要

アプリケーションの実装時

  1. リソース名は直接指定せず、生成した名前を使う
  2. Stackはデプロイのライフラサイクルに応じて分割する
    • ステートフルとステートレスで分けるという考え方もある
  3. 外部リソースを参照する際はLookUpメソッドを使用する
    • 一度参照するとcdk.context.jsonにキャッシュされる
  4. IAMロールはCDKで管理し、外部のロールをインポートしない
  5. すべての環境を本番と同じ扱いにする
    • CDKだとif文で、簡単に各環境の構成を独自に設定できるが、長期的な視点で考えると、障害発生時の切り分けやオペレーションミスなどデメリットの方が大きい

ワークショップの内容

以上の説明の後、特定の日付になると動作を停止するCodePipelineをL2とL3Constructsで構築するワークショップを体験しました。

ワークショップは後ほど一般公開されるようなので、入手出来次第こちらに追記します。

まとめ

Constructsの中身だけでなく、CDK実装時のベストプラクティスまで知ることができ、大変学びの多いセッションでした。

CDKにコントリビュートしたいが内部実装がわからない、、という方は、ぜひ後ほど記載するワークショップを体験してみてください。