かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

AWS CDK で TypeScript な Lambda をデプロイしたい

初めての AWS CDK です。AWS 関連は経験がまだ全然なくやったことのログ的なエントリーとなります。

AWS CDK って何?

AWS Cloud Development Kit (AWS CDK) は、使い慣れたプログラミング言語を使用してクラウドアプリケーションリソースを定義するためのオープンソースのソフトウェア開発フレームワークです。
AWS クラウド開発キット – アマゾン ウェブ サービス

  • AWS のリソースを TypeScript などのコードで定義できるフレームワーク
  • CloudFormation を通じて AWS にデプロイでき
  • デプロイされるまとまりのことを Stack と呼ぶ

AWS CDK コマンドのインストール

$ npm i -g aws-sdk
$ cdk --version
2.25.0 (build ae1cb4b)

sdk init でテンプレートを作成する際に空ディレクトリでないとエラーになったので aws-sdk は global にインストールした

CDK でテンプレートの作成

cdk init コマンドでリソースの設定を記述するテンプレートが生成される

# プロジェクトのディレクトリを作成 ※ディレクトリ名が Stack 名などになる
$ mkdir cdk-sample && cd cdk-sample
# テンプレートの生成
$ cdk init --language typescript

Stack の追加

📝 @aws-cdk/aws-lambdaaws-cdk-lib/aws-lambda の違いって何?

調べていると @aws-cdk のパッケージを使用している例がよく出てくるが、VSCode の補完では aws-cdk-lib の方になるので何が違うの????ってなった

@aws-cdk は v1.x 系

@aws-cdk は v1.x 系 で現行の v2.x 系は aws-cdk-lib トノコト。
cdk のヴァージョンが 2.25.0 なので aws-cdk-lib の方を使えば良さそう

参考にしている記事が v1.x 系の場合は違いを公式ドキュメント(AWS CDK · AWS CDK Reference Documentation)を参考に適時調べるの良さそう

Lambda の設定

Stack の設定ファイルは lib/ 内にある

lib/cdk-sample-stack.ts

import { Stack, StackProps } from 'aws-cdk-lib';
+ import { Runtime } from 'aws-cdk-lib/aws-lambda';
+ import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Construct } from 'constructs';

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

+   // Lambda
+   const lambda = new NodejsFunction(this, 'lambda', {
+     entry: 'lambda/index.ts',  // lambda 関数のエントリーポイント
+     handler: 'handler', // 実行する関数名
+     runtime: Runtime.NODEJS_16_X,
+   });
  }
}

📝

  • Lambda 関数を TypeScript で作成する場合は aws-cdk-lib/aws-lambda-nodejsNodejsFunction を使う
    • NodejsFunction は自動的に JS ファイルにコンパイルしたものを Lambda にデプロイしてくれる
  • aws-cdk-lib/aws-lambdaFunction は Lambda 関数を JavaScript で作成した場合に使用する

API Gateway の設定

lib/cdk-sample-stack.ts

import { Stack, StackProps } from 'aws-cdk-lib';
+ import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Construct } from 'constructs';

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

    // Lambda
    const lambda = new NodejsFunction(this, 'lambda', {
      entry: 'lambda/index.ts',
      handler: 'handler',
      runtime: Runtime.NODEJS_16_X,
    });

+   // API Gateway
+   const api = new LambdaRestApi(this, 'REST_API', {
+     handler: lambda,
+   });
  }
}

Lambda 関数の作成 (TypeScript)

Lambda 用の型をインストール

$ npm i -D @types/aws-lambda

Lambda 関数は Stack で lambda/index.ts にしたのでディレクトリとファイルを作成する

lambda/index.ts

import {
  APIGatewayEventRequestContextV2,
  APIGatewayProxyEventV2,
  APIGatewayProxyResultV2,
} from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEventV2,
  context: APIGatewayEventRequestContextV2,
): Promise<APIGatewayProxyResultV2> => {
  const responseBody = {
    message: 'Hello lambda!',
  };

  return {
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(responseBody),
  };
};

Hello lambda! を返すだけのシンプルな関数を作成
型も APIGatewayProxyEventAPIGatewayProxyEventV2 のように V2 があるものと無いものがサジェストされる。CDK v2.x 系なので V2 がある方にすれば良さそう (サジェストいい感じになってほしい〜)

🚀 Deploy

下準備 Bootstrap

$ cdk bootstrap --profile <USER NAME>

CDK Stack のデプロイに必要な S3 バケットを準備したりしてくれるらしい
実行したけど AWS コンソールをみてもバケットが新規にできてなかったので合っているのか判断できてません

デプロイ Deploy

$ cdk deploy --profile <USER NAME>
…
Do you wish to deploy these changes (y/n)? y
…
✨  Deployment time: 89.97s

Outputs:
CdkSampleStackStack.RESTAPIEndpoint0000000 = https://XXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXX:stack/CdkSampleStackStack/XXXXX-XXXX-XXXX-XXXX-XXXXXX

✨  Total time: 126.45s

デプロイされる内容がつらつら出力された後に本当にデプロイするかきかれるので y を入力
後は自動で AWS にデプロイされていく

Lambda の確認

デプロイの最後に出力された RESTAPIEndpoint の URL を curl で叩いて Hello lambda! が返ってきていればOK

$ curl https://XXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/
{"message":"Hello lambda!"}

デプロイされている Lambda 関数

AWS console の Lambda からデプロイされたコードを見ると JavaScript にビルドされた状態でデプロイされていることが確認できました

// index.js
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/index.ts
var src_exports = {};
__export(src_exports, {
  handler: () => handler
});
module.exports = __toCommonJS(src_exports);
var handler = async (event, context) => {
  const responseBody = {
    message: "Hello lambda!"
  };
  return {
    statusCode: 200,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(responseBody)
  };
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  handler
});

プロジェクトの削除

cdk destroy コマンドで作成したリソース群を削除できる

$ cdk destroy --profile <USER NAME>

所感

超シンプルですが、なんとか TypeScript で Lambda をデプロイすることができました。
かなり前に Serverless ってフレームワークを使って AWS 上に Lambda を作成しましたが、その際は AWS のコンソールに入って手動でバケット作ったり色々とやったのでコードベースで自動化されるのはとても良い仕組みだなと感じました。

CLI で勝手に AWS 上に何かができるの、どこに何ができているのか初学者には把握が難しい。
今回 S3 にバケットが作成されなかったが、それが正しいのかどうかも判断できてません。(コードがシンプルだったからバケット不要になったのかな?)

AWS のドキュメントにベストプラクティスとか載ってるけど、まだ理解が追いついていないので追々…


[参考]