【AWS】AWS CDKのテストとベストプラクティス【テスト】

# はじめに
過去の記事ではAWS CDKに関して変更セットを利用した変更内容の確認や第三者への確認依頼の方法を紹介しました。
CDKはIaCを実現するサービスであり、デプロイの構成がコードで定義されています。
つまりはそのデプロイ前のコードの品質を高いものに保つことでデプロイするリソースもより安全で質の高いものにできます。
今回の記事ではCDKに関して主にCDKのテストに関するベストプラクティスとテストの実装方法について解説します。

# デプロイ前にIaCコードをテストするメリット
・デプロイ対象のインフラ構成情報がコードとしてまとめて記載されているため、実行時の動きで確認するより差分の把握が容易
・デプロイ後にAWSサービスを動かして問題発見した後で再デプロイテストするのに比べて、デプロイ前の内容に対してテストを行うことでテスト工程に係るコスト削減が期待できる

# CDKのベストプラクティスの例（テストに関する内容）
## 1.インフラストラクチャのユニットテスト
・AWS CDKではインフラ構成をコードで持つことができます。そのため、そのコードに対するテストをすることが可能です。
・AWS CDKはCloudFormationのテンプレートを生成してAWSアカウントにリソース(スタック)をデプロイすることができますが、このテンプレートに対してテストを行うことによりテンプレートが期待通りのものかを信頼してデプロイ作業を行うことができます。

## 2.ステートフルリソースの論理 ID を変更しない
・ステートフルリソース：データベースや S3 バケットなどのデータを保持するリソース
・AWS CDKはCloudFormationテンプレートを生成しますが、AWS CloudFormationはリソースを論理IDで識別します。そのため、論理IDが変更されるとそれは別の新しいリソースであると認識され、既存のリソースは削除されます。
・リソースが削除されることが許容できるシステムであればよいですが、そうではないシステムではサービスの中断やデータの損失などの問題が発生する危険があります。
・そのため、ステートフルリソースの論理IDは変更がないことを保証するようなテストがデプロイ前に必要になります。


# テストの実装例
## 1.Snapshot Test
【こんな場合に有効】
・前回作成したCloudFormationテンプレートと変更を加えたCDKスタックから作成したCloudFormationテンプレートを比較することで、意図せぬ変更がないかを確認する

【実装方法】
＜準備＞
・CDKプロジェクトを作成する際に実行する「cdk init app」の結果、以下のファイルが作成される
【CDKプロジェクト名】/test/【CDKプロジェクト名】.test.ts
・このファイルにテスト内容を定義してテストを行う
・以下のコードを記載する
```【CDKプロジェクト名】.test.ts
import { Match, Template } from "aws-cdk-lib/assertions";
import * as cdk from "aws-cdk-lib";
import { CdktestStack } from "../lib/【CDKスタック名】"; //テスト対象のスタックを指定

describe("CDK test", () => {
  test("matches the snapshot", () => {
    const app = new cdk.App();
    const stack = new CdktestStack(app, "MyTestStack");
    const template = Template.fromStack(stack); //テンプレート作成
    expect(template.toJSON()).toMatchSnapshot(); //スナップショットテストを実行する
  });
});
```
＜比較元になる(変更前の)テンプレートを作る＞
・以下のコマンドを実行して比較元のテンプレートを作成する

```
npm test -- -u
```
※-uのオプションが比較元テンプレートの作成/更新の意味になる
＜CDKスタック定義の変更後にテストを実行する＞
・（CDKスタック定義の変更後に）以下のコマンドを実行してテストを実行する
```
npm test
```
※-uのオプションがないのため、差分が見つかっても比較元のテンプレートは更新されず、次回実行時もひかくできる
↓変更がない場合は以下のキャプチャのように成功である旨のメッセージが表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192949/dac70e3a-34b2-404a-a3a0-655de0d122f2.png)
↓変更がある場合の結果は以下のキャプチャのように

↓変更がない時は以下のキャプチャのようにテストが失敗である旨のメッセージと差分の箇所が表示される
↓テスト失敗を示すメッセージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192949/f0460d40-e8bf-4d78-9730-9f93e02dfd96.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192949/5dc473c7-4688-4b96-bc32-80449800076d.png)
↓変更箇所の表示（この例ではLambda関数の論理IDを変更した)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192949/502d0b3b-da88-4877-b076-0710290703ca.png)


## 2.Policy Validation
【こんな場合に有効】
・独自に設定したチェック項目(ポリシー)に基づいてCloudFormation テンプレートが作られているかを検証する
・CDKの変更内容の期待値を事前にポリシーとして設定しておき、それと比較することで仕様通りのリソースが作られることを確認できる
・LambdaのランタイムバージョンやS3のパブリックアクセスのブロック、バージョニング有効化など各定義の設定値をチェックすることが可能であるため、独自のセキュリティポリシーのチェックにも応用できる

【実装方法】
＜準備＞
・CloudFormationGuardをインストールする
※Windowsの場合
→Rustをダウンロード。その後、rustup-init.exeを実行します。
→Cargo がインストールされたら、次のコマンドを実行して Guard をインストールします。

```
cargo install cfn-guard
```
→次のコマンドを実行してcfn-guardのバージョンが表示されたら成功
```
cargo install cfn-guard
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192949/da06ed03-0f03-4fd0-b122-4d158c0522ca.png)

手順の参考URL

https://docs.aws.amazon.com/cfn-guard/latest/ug/setting-up-windows.html


＜カスタムルールの作成＞
・任意のディレクトリにルールを記載するファイルを作成します(拡張子 rurleset)
↓今回の例では次の図のようにCDKプロジェクト直下にフォルダとファイルを作成しました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192949/961b54b2-4e53-4504-8a28-d723db87cad1.png)
・ルールファイルにルールを定義
↓今回はLambda関数のランタイムバージョンの期待値に関するルールを定義しました
```
let aws_lambda_function_resources = Resources.*[ Type == 'AWS::Lambda::Function' ]
rule aws_lambda_function when %aws_lambda_function_resources !empty {
  %aws_lambda_function_resources.Properties.Runtime == 'nodejs22.x'
}
```

＜テンプレートファイルの作成＞
・以下のコマンドでCloudFormationテンプレートを作成します
```
cdk synth
```
↓このコマンドにより、cdk.out配下にテンプレートが出力されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192949/740e9667-2332-4678-ac7b-426efd0aadfb.png)

・以下のコマンドをテンプレートとルールファイルを指定して実行することによりチェックを実行します
```
cfn-guard validate -d cdk.out/【テンプレートファイル名】 -r cfnguard/【ルールファイル名】   
```
↓今回の例ではLambdaランタイムの期待値を「nodejs22.x」と設定しているが、テンプレートには「nodejs20.x」が定義されているためエラーが出るようにしています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192949/95dbfbb6-28ef-4417-8437-614739cbd9d6.png)
チェックの結果を見てみると「Status = FAIL」というところでルールに合わない記載があることが確認でき、
それ以降のメッセージでどこの記載がどのように差があったかが確認できます

### 【ルールの記載内容】
①let
・テスト対象となるリソースのオブジェクトを定義します
②rule
・ルールを定義する
③when
・ルールに応じてチェックを実行する条件を定義する
・「!empty」とすることでそのリソースの定義があるときだけチェックを実行できる。リソースがない場合はそのルールのチェックをスキップする
④ルール本文
・%【letで定義したオブジェクト】.【チェック項目】 【==などの演算子】 【期待値】
の構文でルールを定義する。複数のルールを記載することも可能
・演算子は単項演算子と二項演算子が利用可能
　→単項演算子：exists,empty,is_string,is_listなど
　→二項演算子：==,!=,>=,<,<=,IN(A,B,C…)など


# AWS CDKのIaCコードとデプロイにおける各ステップとテストの実行タイミングまとめ
・過去の記事の内容を含め、CDKの運用ステップと各テストの実行タイミングは以下のようになると思います

## IaCコード作成前/作成中
・CloudFormationGuardのルール作成
→テンプレート作成前に要件や設計内容やテスト内容を作成する
→開発者はルールの内容を参考にしながらIaCコードを作成することで仕様書とのズレの発生の防止やテストの負荷を軽減できる

## テンプレート作成直前（cdk synth前）
・Snapshot Testを実行する
→変更したIaCコードの内容を前回作成したIaCコードと比較することで、不要な変更や意図せぬ変更がないことを確認する
→CloudFormationGuardで仕様(ポリシー)通りに変更が加わっているか確認することができるが、それだけだと今回の変更対象以外の意図せぬリソースに対してポリシーに合致する変更が加わったことを検知できない
→そのため、Snapshot Testで前回の内容と比較して必要な変更だけが加わっていることを確認する

## テンプレート作成後（cdk synth後）
・デプロイ前の確認としてCloudFormationテンプレートが事前に設定した仕様(ポリシー)に合う形で各リソースの設定がされているかを確認する

## デプロイ中(CloudFormation変更セット作成)
・過去の記事で紹介しましたが、CodePipelineを利用した変更セット作成と第三者のレビュー担当者からの手動承認を行うことでバグの発生を含むデプロイの失敗のリスクが軽減できます

https://qiita.com/Nana_777/items/0160ec6a52a901a59599

## デプロイ後
・こちらも過去の記事で紹介しましたが、CodeDeployを利用したデプロイ戦略の実施が有効になります

https://qiita.com/Nana_777/items/4627fc27713217ca93db

・過去の記事でも紹介していますが、例えばカナリアデプロイの戦略を利用することで、一定時間の間少数の利用者に対して新バージョンへのトラフィックを許可し、その一定時間の間にエラーが発生しなければすべてのトラフィックを新バージョンに移すという制御が自動で行えます
・これにより、デプロイによる問題が発生した場合も影響範囲を少数のユーザーに制限することができます

【表で表すとこんなイメージ】
|工程  |IaCコード作成前  |IaCコード作成結果確認  |テンプレート内容確認 |デプロイ前第三者確認 |デプロイ結果確認 |
|---|---|---|---|---|---|
|実施する作業 | cfn-guardルール定義 |SnapShotテスト |cfn-guardテスト |変更セットでデプロイ内容確認 |CodeDeployなど
|作業の意図  |テスト内容を意識したコード作成  |意図せぬ変更がないか確認  |ポリシーに沿って作られたことを確認  |第三者目線でデプロイ内容の確認/承認 |デプロイ後の監視と影響範囲の制限と問題発生時のロールバック

※ここでは紹介しませんでしたが、デプロイ前にAmazonQやcdk-nagを使用してセキュリティチェックを行うことも有効です


# 参考資料

AWSBlackBeltのCDKのテストについて紹介されている資料

https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_AWS-CDK-Advanced-1-Testing_1227_v1.pdf

CDKのベストプラクティスに関するAWSブログとドキュメント

https://aws.amazon.com/jp/blogs/news/best-practices-for-developing-cloud-applications-with-aws-cdk/

https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/best-practices.html

# その他の過去の関連記事
CDKの環境の作り方

https://qiita.com/Nana_777/items/e86fae02989c98dc986d