AWS CDK でカスタム Construct を作って、他の CDK プロジェクトから使ってみる

2019.10.16

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

はじめに

CX事業本部@札幌の佐藤です。最近はCDKが面白くてCDKのブログばかり書いています。

AWS CDKでは、CDK標準のライブラリを使用してリソースを定義する他にも、独自にライブラリを作って作成することもできます。

概要

今回は独自のConstructライブラリを作成して、それをnpmにpublishして、他のCDKプロジェクトのスタックでrequireしてAWSにデプロイするということをやっていきます。

環境

項目 バージョン
macOS Mojave 10.14.5
node v10.16.0
npm 6.9.0
cdk 1.12.0 (build 923055e)

事前準備

npmjsでアカウントを作成しておきます。その後、以下のコマンドで、username、password、emailを入力してユーザーを作成しておきます。

npm adduser

やってみた

CDKのプロジェクトの作成

まずはCDKのプロジェクトを作成します。

$ cdk init
Available templates:
* app: Template for a CDK Application
   └─ cdk init app --language=[csharp|fsharp|java|javascript|python|typescript]
* lib: Template for a CDK Construct Library
   └─ cdk init lib --language=typescript
* sample-app: Example CDK Application with some constructs
   └─ cdk init sample-app --language=[csharp|javascript|python|typescript]

cdk init とすると、コマンド体系の一覧が表示されます。普通のCDKのプロジェクトの場合は、cdk init appとなりますが、今回はライブラリを作成するため、cdk init libの方を使用します。言語はTypeScriptを使用します。

$ cdk init lib --language=typescript

サンプルConstruct Libraryのソースの確認

プロジェクトを作成すると、以下のようなサンプルコードが作られています。サンプルコードでは、cdk.Constructを継承したクラスが作成されています。独自ライブラリを作るには、cdk.Constructクラスを継承したクラスを作成し、コンストラクタでAWSリソースを定義していきます。このサンプルコードでは、SQSSNSトピックSNSサブスクリプションを作っています。また、インスタンス変数でSQSキューのARNを設定しているため、これを他のConstructの引数などに設定できるようになっています。

import sns = require('@aws-cdk/aws-sns');
import subs = require('@aws-cdk/aws-sns-subscriptions');
import sqs = require('@aws-cdk/aws-sqs');
import cdk = require('@aws-cdk/core');

export interface AwsCdkLibSampleProps {
  /**
   * The visibility timeout to be configured on the SQS Queue, in seconds.
   *
   * @default Duration.seconds(300)
   */
  visibilityTimeout?: cdk.Duration;
}

export class AwsCdkLibSample extends cdk.Construct {
  /** @returns the ARN of the SQS queue */
  public readonly queueArn: string;

  constructor(scope: cdk.Construct, id: string, props: AwsCdkLibSampleProps = {}) {
    super(scope, id);

    const queue = new sqs.Queue(this, 'AwsCdkLibSampleQueue', {
      visibilityTimeout: props.visibilityTimeout || cdk.Duration.seconds(300)
    });

    const topic = new sns.Topic(this, 'AwsCdkLibSampleTopic');

    topic.addSubscription(new subs.SqsSubscription(queue));

    this.queueArn = queue.queueArn;
  }
}

npmに公開する

他のCDKプロジェクトから使うためにnpmに公開してみます。他のパッケージと名前がぶつからないように、名前空間を付与します。usernameはnpmjsに登録されているusernameを使用します。

{
  "name": "@username/aws-cdk-lib-sample",
  "version": "0.1.0",
  ...
  ...
  ...
}

プロジェクト直下で以下のコマンドを実行するだけです。これで、@username/aws-cdk-lib-sampleパッケージとしてnpmに公開されます。

$ npm run build
$ npm publish --access=public
+ aws-cdk-lib-sample@0.1.0

他のCDKプロジェクトから参照する

npmに公開したので、他のCDKプロジェクトでnpm installしてスタックを作成してみます。先ほどと同じように、CDKプロジェクトを作成します。こちらはCDKのAppを作成するため、cdk init appの方を使います。

$ cdk init app --language=typescript

その後、先ほどnpmに公開したパッケージをインストールします。

$ npm install @username/aws-cdk-lib-sample

コードを修正します。以下のサンプルコードでは、先ほどnpmに公開した独自のConstruct Libraryをrequireして使用しています。

import cdk = require('@aws-cdk/core');
import sample = require('@username/aws-cdk-lib-sample') // 先ほどの独自パッケージをrequire

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

    const sampleConstruct = new sample.AwsCdkLibSample(this, "SampleConstruct", {
      visibilityTimeout: cdk.Duration.seconds(10)
    });

    // インスタンス変数から、SQSのARNを取得できる
    // sampleConstruct.queueArn
  }
}

デプロイ

AWSへデプロイします。

$ cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬─────────────────────────────────────────────┬────────┬─────────────────┬───────────────────────────┬─────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                    │ Effect │ Action          │ Principal                 │ Condition                                                                   │
├───┼─────────────────────────────────────────────┼────────┼─────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ + │ ${SampleConstruct/AwsCdkLibSampleQueue.Arn} │ Allow  │ sqs:SendMessage │ Service:sns.amazonaws.com │ "ArnEquals": {                                                              │
│   │                                             │        │                 │                           │   "aws:SourceArn": "${SampleConstruct/AwsCdkLibSampleTopic}"                │
│   │                                             │        │                 │                           │ }                                                                           │
└───┴─────────────────────────────────────────────┴────────┴─────────────────┴───────────────────────────┴─────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)? y
AwsCdkRequireLibrarySampleStack: deploying...
AwsCdkRequireLibrarySampleStack: creating CloudFormation changeset...
 0/6 | 7:10:48 PM | CREATE_IN_PROGRESS   | AWS::SQS::Queue        | SampleConstruct/AwsCdkLibSampleQueue (SampleConstructAwsCdkLibSampleQueue6C642B46) 
 0/6 | 7:10:48 PM | CREATE_IN_PROGRESS   | AWS::SNS::Topic        | SampleConstruct/AwsCdkLibSampleTopic (SampleConstructAwsCdkLibSampleTopicEE2D292F) 
 0/6 | 7:10:48 PM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata     | CDKMetadata 
 0/6 | 7:10:48 PM | CREATE_IN_PROGRESS   | AWS::SQS::Queue        | SampleConstruct/AwsCdkLibSampleQueue (SampleConstructAwsCdkLibSampleQueue6C642B46) Resource creation Initiated
 0/6 | 7:10:49 PM | CREATE_IN_PROGRESS   | AWS::SNS::Topic        | SampleConstruct/AwsCdkLibSampleTopic (SampleConstructAwsCdkLibSampleTopicEE2D292F) Resource creation Initiated
 1/6 | 7:10:49 PM | CREATE_COMPLETE      | AWS::SQS::Queue        | SampleConstruct/AwsCdkLibSampleQueue (SampleConstructAwsCdkLibSampleQueue6C642B46) 
 1/6 | 7:10:50 PM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata     | CDKMetadata Resource creation Initiated
 2/6 | 7:10:50 PM | CREATE_COMPLETE      | AWS::CDK::Metadata     | CDKMetadata 
 3/6 | 7:10:59 PM | CREATE_COMPLETE      | AWS::SNS::Topic        | SampleConstruct/AwsCdkLibSampleTopic (SampleConstructAwsCdkLibSampleTopicEE2D292F) 
 3/6 | 7:11:01 PM | CREATE_IN_PROGRESS   | AWS::SQS::QueuePolicy  | SampleConstruct/AwsCdkLibSampleQueue/Policy (SampleConstructAwsCdkLibSampleQueuePolicyB26F01DD) 
 3/6 | 7:11:01 PM | CREATE_IN_PROGRESS   | AWS::SNS::Subscription | SampleConstruct/AwsCdkLibSampleQueue/AwsCdkRequireLibrarySampleStackSampleConstructAwsCdkLibSampleTopic4BACA155 (SampleConstructAwsCdkLibSampleQueueAwsCdkRequireLibrarySampleStackSampleConstructAwsCdkLibSampleTopic4BACA15518B88C48) 
 3/6 | 7:11:01 PM | CREATE_IN_PROGRESS   | AWS::SQS::QueuePolicy  | SampleConstruct/AwsCdkLibSampleQueue/Policy (SampleConstructAwsCdkLibSampleQueuePolicyB26F01DD) Resource creation Initiated
 4/6 | 7:11:01 PM | CREATE_COMPLETE      | AWS::SQS::QueuePolicy  | SampleConstruct/AwsCdkLibSampleQueue/Policy (SampleConstructAwsCdkLibSampleQueuePolicyB26F01DD) 
 4/6 | 7:11:02 PM | CREATE_IN_PROGRESS   | AWS::SNS::Subscription | SampleConstruct/AwsCdkLibSampleQueue/AwsCdkRequireLibrarySampleStackSampleConstructAwsCdkLibSampleTopic4BACA155 (SampleConstructAwsCdkLibSampleQueueAwsCdkRequireLibrarySampleStackSampleConstructAwsCdkLibSampleTopic4BACA15518B88C48) Resource creation Initiated
 5/6 | 7:11:02 PM | CREATE_COMPLETE      | AWS::SNS::Subscription | SampleConstruct/AwsCdkLibSampleQueue/AwsCdkRequireLibrarySampleStackSampleConstructAwsCdkLibSampleTopic4BACA155 (SampleConstructAwsCdkLibSampleQueueAwsCdkRequireLibrarySampleStackSampleConstructAwsCdkLibSampleTopic4BACA15518B88C48) 
 6/6 | 7:11:03 PM | CREATE_COMPLETE      | AWS::CloudFormation::Stack | AwsCdkRequireLibrarySampleStack 

 ✅  AwsCdkRequireLibrarySampleStack

独自ライブラリで定義した、SQSSNSが作成されています。

クリーンアップ

デプロイしたAWSリソースと、npmjsのパッケージを削除します。

$ npm unpublish @username/aws-cdk-lib-sample --force
$ cdk destroy

まとめ

CDKで独自のライブラリを使う方法でした。CDKにおけるスタックの粒度やConstruct Libraryの上手な使い方などはまだまだ模索中の段階です。これからどんどん実案件などで使って知見をためていけたらなと思っています。