📈

AWS CDKにテストを導入する

2023/09/17に公開

はじめに

初めまして!
都内IT企業で、データアルゴリズムチームのエンジニアをしております、Noraと申します。

今回の記事では、最初にAWS CDKとは何か、という話を行います。
その後、今回は、S3、ECR、IAMロールのテストコードを解説いたします。

これまではCDKの動作確認として、デプロイされたものとローカルの差分を確認するcdk diffを実行していましたが、より高いコード品質を担保するためにテストを導入することになりました。

記事執筆の背景としては、SageMaker Serverless Inferenceで推論エンドポイントを呼び出すためには、私が関わった施策の特性上、次のアクションが必要でした。
そこで、AWSリソースの作成が必要となったというのが背景です。
SageMaker Serverless Inferenceについては、こちらをご参照ください

  • 推論用DockerイメージをECRに格納する
  • 使用するモデルをS3バケット内に格納する(Lukeモデルを使用)
  • AWSリソースの権限を管理するIAMロール

読者対象

  • CDKを導入していて、テストを書こうとしている
  • インフラの管理コストや人為的ミスに苦労しており、コードで管理できるCDKを導入したい

AWS CDKとは(スクリプト付き)

AWS CDKとは、コードでクラウドインフラを管理することができるフレームワークです。
コードをでインフラを管理することは、IaC(Infrastructure as Code)とも呼ばれています。
https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/home.html

導入メリットとしては、以下などが挙げられます。

  • インフラ、アプリケーションを全て1箇所にまとめることができる
  • コードレビューや、ユニットテスト、ソース管理などのソフトウェアエンジニアリング手法を用いることができる
  • インフラの設計パターンを、組織のメンバーに共有することができる

今回は、以下のようなディレクトリ構成でCDKを実装しました。(一部省略しております)
ファイル内のスクリプトは極力シンプルなものにしています。

root/
  ├cdk/
    ├bin/
      ├cdk.ts #表側のアプリケーションを記述
    ├lib
      ├sample-stack.ts # 
    ├node_modules
      ├...
    ├test
      ├sample-stack.test.ts
    ...
bin/cdk.ts
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";

import { SampleStack } from "../lib/sample-stack";

const app = new cdk.App();

new SampleStack(app, "SampleStack");

lib/sample-stack.ts
import * as cdk from "aws-cdk-lib";
import * as ecr from "aws-cdk-lib/aws-ecr";
import * as iam from "aws-cdk-lib/aws-iam";
import * as s3 from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";

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

        const repositories = ["sample-repository-1", "sample-repository-2"];

        //ECRリポジトリの作成
        repositories.forEach((repo) => {
            new ecr.Repository(this, repo, {
                repositoryName: repo,
                imageScanOnPush: true,
            });
        });

        // S3バケットの作成
        new s3.Bucket(this, "sample-bucket", {
            bucketName: "sample-bucket",
            removalPolicy: cdk.RemovalPolicy.DESTROY,
        });

        // IAMロールの作成
        new iam.Role(this, "sample-role", {
            roleName: "sample-role",
            assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
            path: "/service-role/",
            // ポリシーのアタッチ
            managedPolicies: [
                new iam.ManagedPolicy(this, "sample-policy", {
                    managedPolicyName: "sample-policy",
                    statements: [
                        new iam.PolicyStatement({
                            effect: iam.Effect.ALLOW,
			    // 最低限の権限を付与する
                            actions: [
                                "s3:GetObject",
                                "ecr:GetDownloadUrlForLayer",
                                "ecr:BatchGetImage",
                            ],
                            resources: ["*"],
                        }),
                    ],
                }),
            ],
        });
    }
}


AWS CDKのテストを実装する

前章で、CDKコードを実装しました。
次に、本題のテストを実装していきます。
CDKのテストには、アサーションテストと、スナップショットテストの2つがあります。
以下、公式ドキュメントから引用したそれぞれの役割です。

きめ細かなアサーションは、「このリソースにはこの値を持つこのプロパティがあります」など、AWS CloudFormation生成されたテンプレートの特定の側面をテストします。これらのテストは回帰を検出できます。また、テスト駆動開発を使用して新しい機能を開発する場合にも役立ちます。(最初にテストを書いてから、正しい実装を書いて合格にすることができます。) きめ細かなアサーションは、最も頻繁に使用されるテストです。

スナップショットテストでは、AWS CloudFormation合成されたテンプレートを以前に保存されたベースラインテンプレートと照合してテストします。スナップショットテストでは、リファクタリングされたコードが元のコードとまったく同じように動作することを確認できるため、自由にリファクタリングできます。変更が意図的なものであれば、future テスト用に新しいベースラインを適用できます。ただし、CDK のアップグレードによって合成テンプレートが変更されることもあるため、スナップショットだけに頼って実装が正しいことを確認することはできません。

今回では、以下をテストしたいことを考慮し、アサーションテストを実装します。
正しいリソース数が生成されるか(例:S3バケットが1つ生成され、ECRリポジトリは2つ生成される)
リソースのプロパティは適切か

npx cdk listでスタックを一覧し、npx cdk synth スタック名で指定したスタックのテンプレートを確認したのちに、テストコードを書くと、問題なくテストコードを書くことができるかと思います。

sample-stack.test.ts
import * as cdk from "aws-cdk-lib";
import { Template } from "aws-cdk-lib/assertions";

import * as Cdk from "../lib/sample-stack";

test("Empty Stack", () => {
    const app = new cdk.App();
    const stack = new Cdk.SampleStack(app, "MyTestStack");

    const template = Template.fromStack(stack);

    // ECRリソースのテスト
    template.resourceCountIs("AWS::ECR::Repository", 2);
    template.hasResourceProperties("AWS::ECR::Repository", {
        RepositoryName: "sample-repository-1",
        ImageScanningConfiguration: {
            ScanOnPush: true,
        },
    });
    template.hasResourceProperties("AWS::ECR::Repository", {
        RepositoryName: "sample-repository-2",
        ImageScanningConfiguration: {
            ScanOnPush: true,
        },
    });

    // S3リソースのテスト
    template.resourceCountIs("AWS::S3::Bucket", 1);
    template.hasResourceProperties("AWS::S3::Bucket", {
        BucketName: "sample-bucket",
    });

    // IAMリソースのテスト
    template.resourceCountIs("AWS::IAM::Role", 1);
    template.hasResourceProperties("AWS::IAM::Role", {
        RoleName: "sample-role",
        Path: "/service-role/",
    });
});

テストコードを実装したのちに、アサーションテストを実行します。

$ cd path/to/root/cdk
$ npm test
----
> cdk@0.1.0 test
> jest

 PASS  test/sample-stack.test.ts

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.263 s, estimated 4 s
Ran all test suites.

無事、テストが通りました。

より詳しく、AWS CDKのテストについて知りたい場合は、公式ドキュメントをご参照ください。
https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/testing.html

あと書き

記事をお読みいただき、ありがとうございました。
もし補足点・修正等ございましたら、ご気軽にコメントいただけますと幸いです。

今回はCDKのテストにフォーカスしましたが、CDKに関する他記事や、執筆背景となったSageMaker周りの記事も、今後執筆できればと思います。
引き続き、ハマったポイントや、タメになる情報を発信できればと思います!

Discussion