AWS CDKプロジェクトの作成方法について

どうもこんにちは。
この前、AWS CDKのチュートリアルを意気揚々とやっていました。
これでコーディングしながらインフラ構築してるといけしゃあしゃあと言えると思っていましたが、チュートリアルでつまづきました。
何度やってもうまくいかずに、コピペしてもうまくいかない箇所がありました。
今回は何故うまくいかなったかと対処方法、そして、CDKプロジェクトの始め方について書いてみます。

目次

何が起きたのか

始めて触るので、チュートリアル通りに進めていました。

$ mkdir cdk-101
$ cd cdk-101
$ npx cdk init --language typescript
$ npm run build

ここまでは問題なく動き、S3バケットをCDKスタックの中に追加するコーディングを行いました。
まずは、ライブラリを追加しました。

$ npm install @aws-cdk/aws-s3

そして、S3バケットの中身をこんな感じで書き換えました。

import cdk = require('@aws-cdk/cdk');
import s3 = require('@aws-cdk/aws-s3');

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

    new s3.Bucket(this, 'MyFirstBucket', {
      versioned: true
    });
  }
}

そのあとに、変更を反映させるためにビルドコマンドをかけたところでエラーが発生しました。

$ npm run build

> cdk-101@0.1.0 build /Users/josh/Documents/github/cdk-101
> tsc

lib/cdk-101-stack.ts:8:19 - error TS2345: Argument of type 'this' is not assignable to parameter of type 'Construct'.
  Property 'synthesize' is missing in type 'Cdk101Stack' but required in type 'Construct'.

8     new s3.Bucket(this, 'MyFirstBucket', {
                    ~~~~

  node_modules/@aws-cdk/core/lib/construct.d.ts:267:15
    267     protected synthesize(session: ISynthesisSession): void;
                      ~~~~~~~~~~
    'synthesize' is declared here.


Found 1 error.

チュートリアル通りなはずなのに、typescriptのビルドで失敗してますね。
エラーの内容とGitHubのIssueを照らし合わせた感じでライブラリのバージョン(@aws-cdk/cdk@aws-cdk/aws-s3)が同じじゃなきゃダメそうでした。
ライブラリのバージョンを確認してみます。

{
  "name": "cdk-101",
  "version": "0.1.0",
  "bin": {
    "cdk-test": "bin/cdk-test.js"
  },
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "cdk": "cdk"
  },
  "devDependencies": {
    "@types/node": "^8.9.4",
    "typescript": "^3.1.2",
    "aws-cdk": "^0.24.1"
  },
  "dependencies": {
    "@aws-cdk/aws-s3": "^0.37.0",
    "@aws-cdk/cdk": "^0.24.1",
    "source-map-support": "^0.5.9"
  }
}

確かにバージョンに大きくズレがありますね。
ベータ版なので、マイナーバージョンの変更でも破壊的変更が含まれている可能性はあるので、原因はこれでしょう。
ライブラリのバージョンを直せばうまくいきそうですね。

なぜ起きたのか

グローバルインストールしていたaws-cdkのバージョンが古かったのが一番の要因です。
なので、initコマンドは古いのをベースにプロジェクトを作成し、新たにインストールした@aws-cdk/aws-s3はバージョンを指定していないため、最新の0.37.0をインストールしています。
結果してバージョンが合わずに問題が起きたということです。

グローバルインストールしたaws-cdkのバージョンを上げるか、インストールするライブラリのバージョンを指定するかをする必要がありました。
バージョンを指定して、入れる場合はこのようにします。

$ npm install @aws-cdk/aws-s3@0.24.1

この後再度ビルドなどを試してみると問題なく進んでいきます。

$ npm run build
$ npx cdk synth
Resources:
  MyFirstBucketB8884501:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
    DeletionPolicy: Retain
    Metadata:
      aws:cdk:path: CdkOldStack/MyFirstBucket/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=0.24.1,jsii-runtime=node.js/v12.5.0

プロジェクトの新規作成時に古いライブラリを使うのもおかしな話なので、aws-cdkのバージョンを上げるのがベストだとは思います。

$ npm i -g aws-cdk

今回のエラーの原因がわかったところで、initで行なっていることを手でやって、プロジェクトに対する理解を深めていきたいと思います。

フルスクラッチでプロジェクトを始める

結局のところcdk initが行なっていることは、ディレクトリ内にファイルを配置したり、ライブラリをインストールしたりしているだけです。
なので一から手で同じことも行えます。
出来上がったリポジトリはGitHubにあげているのでご確認ください。

それでは作業を始めていきましょう。何はともあれnpm initします。
そのあとに必要なライブラリをインストールしていきます。

$ npm init
$ npm i -D @types/node typescript aws-cdk
$ npm i @aws-cdk/core

先ほどまでインストールしていた@aws-cdk/cdkはどこに行ったのでしょうか。山に帰ったのでしょうか。
npmjsを確認すればわかりますが、@aws-cdk/coreという名前に変えられました。
出来上がったpackage.jsonの中身はこのようになりました。

{
  "name": "cdk-template-ts",
  "description": "template repo for AWS CDK",
  "version": "0.37.0",
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "cdk": "cdk"
  },
  "author": "37108",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^12.0.12",
    "aws-cdk": "^0.37.0",
    "typescript": "^3.5.2"
  },
  "dependencies": {
    "@aws-cdk/core": "^0.37.0"
  }
}

次にTypeScriptの設定のためにtsconfig.jsonを作成します。
今回は何としてもビルドが通るように激甘な設定にしていますが、お好みの設定でお願いします。

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "lib": [
      "es2016",
      "es2017.object",
      "es2017.string"
    ],
    "noUnusedLocals": false,
    "noUnusedParameters": false
  }
}

AWS CDKの設定に入っていきます。
スタック作成時の起点をindex.tsとして、src以下にインポートしたいファイル群をまとめるようにするため、このようなディレクトリ構造を目指します。

.
├── index.ts
 │ 
├── src/
 │   ├── /xxx.ts
 │   ├── /yyy.ts

起点となるindex.tsの中身はこのような感じです。今回はsrc以下になにも作っていないのでインポートはしてないです。
実際はTemplateStackクラスのコンストラクタに必要なリソースについての記述を行なっていきます。

import cdk = require('@aws-cdk/core')

class TemplateStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props: cdk.StackProps = {}) {
    super(parent, name, props)
  }
}

const app = new cdk.App()
new TemplateStack(app, 'TemplateStack')
app.synth()

AWS CDKの設定ファイル、cdk.jsonをいじります。
だいぶスッキリしてますね。

{
  "app": "node index"
}

最後にREADMEをちょちょっと書いてAWS CDKの準備完了です。
一応問題なくビルドができるかを確認します。

$ npm run build
$ npx cdk synth
Resources:
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=0.37.0,@aws-cdk/core=0.37.0,@aws-cdk/cx-api=0.37.0,jsii-runtime=node.js/v12.5.0

中身のないスタックではありますが、問題なさそうですね。

そして、0.36.0以降のバージョンではでAWS CDKからデプロイする方法が少し変わっています。
cdk deployする前に、cdk bootstrapする必要があります。
具体的にはこんな感じです。

$ npx cdk bootstrap aws://123456789012/ap-northeast-1 
$ npx cdk deploy

aws://123456789012/ap-northeast-1 これの前半部分でAWSアカウント、後半部分でリージョンを指定しています。
これが実行されると、CDK用のS3バケットが作成されて、cdk deployが可能になります。

さいごに

AWS CDKを使うとTypeScriptなどのコードがらCloudFormationのテンプレートを作成したり、プロジェクトを管理できたりするのでユースケースによっては今までのIaCツールより有用な場合もあります。
なので十分導入したくなるものなので今後の発展をささやかながらお祈りしています。