Lambdaカクテル

京都在住Webエンジニアの日記です

CDKアプリケーションのディレクトリ構造・処理フロー入門

CDKのことを良く知らなかったので,作りたいstackごとにcdk init してしまったWindymeltです。おっちょこちょい。仕事でCDKをいじっています。

さて,package.jsonがstackごとに生成されるのは困るので,1つのディレクトリにまとめる作業をするかたわら,CDKのディレクトリがどういう構造になっているのか勉強しました。

CDKにおいて,具体的にどのファイルがどういう過程を経てデプロイに至るかについて,日本語で解説された資料があまりないと感じ,初学者である自分には大変だったので,メモを整理して残すことにしました。サンプルは動かせるようになったけれどより詳しく仕組みを知りたい人の参考になれば幸いです。

構成

cdk init --typescript を実行すると,以下のようなディレクトリ構成になります。

.
├── README.md
├── bin              // ここにappを記述する
├── cdk.context.json // VPCのidなどの環境依存の情報(context)が入る
├── cdk.json         // デフォルトのapp(後述)などが入る
├── cdk.out          // cfnなどの出力先。コンパイルされたjsを動かすと吐く
├── jest.config.js   // テストの設定ファイル
├── lib              // stackの構成などを記述する
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json

この記事ではこのデフォルト構成を前提にして話を進めることにします。

binとlib: AppとStackの記述場所

われわれがCDKアプリケーションを書く上ではbin/lib/が重要になってきます。

lib/ではわれわれが構築したいstackの記述が行われます。すなわち,cdk.Stackを継承したなにがしかのクラスが定義され,exportされます。

bin/ではappを定義します。lib/で定義してexportしたStackクラスにappを渡してインスタンス化することで,コンテキストと呼ばれる種々のデータや,対象となるアカウントのIDやリージョンが渡り,CFnテンプレートに変換できるようになります。考え方としては,抽象的な設計図がインスタンス化されて具体的な存在になるという感じです。

appには複数のstackを含めることができます。しかしCFnにおけるデプロイ(プロビジョニング)の単位はstackなので,CFnテンプレートはstackごとに分離されます。appとstackとの関係については以下の記事が詳しいです。

this.aereal.org

Appについては以下のdeveloper guideを参照しました。

docs.aws.amazon.com

キホンの処理フロー

CDKアプリケーションの最終的なゴールを,aws-cdkがCFnテンプレートをデプロイすること,だと考えます。

CFnテンプレートはcdkライブラリによって記述されたappを実行することで生成されますが,appはTSで記述されているので事前にJSへとビルドし,nodeで実行可能にする必要があります。

TSからJSへのビルドはtscが,JSからCFnを生成してデプロイを行なう処理はcdkが担っています。

また現在主流である,ビルドとデプロイを一気に行なう方法については,後述します。まずは基本的な動作を見ていきましょう。

基本的な流れは,TSのビルド・JSの実行・cdkによるデプロイです。

npmによるタスクランナー

標準ではタスクランナーとしてnpm scriptsが用意されており,npm run ほげほげで大抵の動作を完結できます。後述するcdkコマンドも,npm run cdkの形式で実行可能です。

TSのビルド

デフォルトでは,npm run build を使うことでTSをJSにビルドできます。

npm run buildするとtsファイルがビルドされ,tsファイルと同じ場所にjsファイルが出力されます。package.jsonを見ればわかるとおり,npm script build の正体はtscが実行されるだけです。

// (snip)
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },
// (snip)

ここでtscにはオプションが指定されていないのでtsctsconfig.jsonに従ってコンパイルしようとします。 tsconfig.jsonにはコンパイル対象を指定するfilesが指定されていないので,tscはデフォルトの挙動に従って全ての.tsファイルをコンパイルします。

If the "files" and "include" are both left unspecified, the compiler defaults to including all TypeScript (.ts, .d.ts and .tsx) files in the containing directory and subdirectories tsconfig.json · TypeScript

bin/以下に記述されたbin/app.tsといったappはコンパイルされ,この場合はbin/app.jsが生成されます。lib/以下もまた然りです。

生成されるJSの役割

このJSは実行可能です。 実行すると指定したディレクトリにCFnテンプレートなどを出力してくれます。いわば実行可能なCFnのDSLみたいなもの。

手で実行することもできます。

$ CDK_CONTEXT_JSON='{"コンテキストがあれば": "ここに入れる"}' CDK_OUTDIR=cdk.out node bin/app.js

ここの環境変数は以下のリンク先で発見しました。

github.com

これを実行するとcdk.out/以下にCFnテンプレートが出力されますが,実用上は手で実行せずにcdkコマンドを使います。

cdkコマンドは内部でこれと同様の処理を行い,生成されたCFnテンプレート使ってappをデプロイしたり,差分を表示したりしています。

ここで「同様の処理を行な」うために指定するのが,次のセクションで解説する--appです。

cdkコマンドによるappのデプロイ

cdk deploy --app "node bin/app.js"を実行すると,前項で行われたJSファイルの実行が内部で行われ,CFnテンプレートが生成されます。 これをもとにappがデプロイされます。

また--app "node bin/app.js"は必須オプションですが,cdk.jsonを書くことで省略できます。cdk.jsonには以下のように記述します:

{ app: "node bin/app.js" }

appオプションでは,CFnテンプレートを生成するために必要なコマンドを指定しています。前項で示したコマンドと違って環境変数を指定していませんが,これはcdkコマンドが自動で指定してくれるため指定する必要がないのです。

より気の効いた処理フロー(主流)

前項の方式を発展させた,現在主流の方法をここで説明します。

ts-nodeによるビルドと実行の同時化

毎回tsファイルをビルドするのは大変なので,自動でtscを呼び,実行まで行なうようにします。cdk.jsonには次のように指定します:

{ app: "npx ts-node bin/app.ts" }

ts-nodeはTSをコンパイルしつつnodeを動かしてくれる便利なツールなので,cdkコマンドを叩くたびに自動的にbin/app.tsがコンパイルされ,これが実行されてCFnテンプレートが出力されるという寸法です。それ以降は手でビルドした場合と同様です。

npxについては拙エントリを参照ください。

blog.3qe.us

まとめ

CDKで自分たちが書いたTSファイルがデプロイされるまでのデータの流れを中心にまとめました。お役に立ったら幸いです。

この記事を書くことでCDKのおおまかな動作原理がわかり,ようやくCDKを活用する第一歩が踏み出せたような感じです。 最初はbin/lib/をどう使い分ければよいのかまったく分からず,勘で記述するような状態でしたが,調べ物を続けるうちに勘所が分かってきて,この記事を書くことができました。

良いCDKライフを!