[レポート]AWS CDKのL2、L3 Constructを実装する具体的なテクニックと、CDKのベストプラクティスを学ぶワークショップに参加しました #reinvent
こんにちは。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の実装時
- jsiiを使用して公開すること
- TypeScriptで記述することで、jsiiを通して別の言語にトランスパイルでき、開発者は好きな言語を選べるため
- StackではなくConstructsレベルで留めること
- Constructsのレベルで留めることで、開発者は自由にconstructsを組み合わせてStackが構築できるため
- 設定値は環境変数ではなくConstructのpropsから受け取ること
- コンストラクトの構成を決めるのは、合成時ではなく実装時が好ましいため
- 単体テストを書くこと
- 便利さのために使い、コンプライアンスのためには使わない
- コンプライアンス要件の準拠にはCDKだけではなく他のアプローチも必要
アプリケーションの実装時
- リソース名は直接指定せず、生成した名前を使う
- Stackはデプロイのライフラサイクルに応じて分割する
- ステートフルとステートレスで分けるという考え方もある
- 外部リソースを参照する際はLookUpメソッドを使用する
- 一度参照するとcdk.context.jsonにキャッシュされる
- IAMロールはCDKで管理し、外部のロールをインポートしない
- すべての環境を本番と同じ扱いにする
- CDKだとif文で、簡単に各環境の構成を独自に設定できるが、長期的な視点で考えると、障害発生時の切り分けやオペレーションミスなどデメリットの方が大きい
ワークショップの内容
以上の説明の後、特定の日付になると動作を停止するCodePipelineをL2とL3Constructsで構築するワークショップを体験しました。
ワークショップは後ほど一般公開されるようなので、入手出来次第こちらに追記します。
まとめ
Constructsの中身だけでなく、CDK実装時のベストプラクティスまで知ることができ、大変学びの多いセッションでした。
CDKにコントリビュートしたいが内部実装がわからない、、という方は、ぜひ後ほど記載するワークショップを体験してみてください。