AWSのCDKとSAMでLambda関数をローカル開発

AWSのFaaS(Function as a Service)であるLambdaは以前からよく使っているのですが、基本的にAWSコンソールの画面でコーディングするか、ローカルで普通のPythonプログラムとして作って(今回はTypeScriptで説明しますが、もともとはPythonで書くことが多いのです)、テストして、それからPythonパッケージとまとめてZipで圧縮してアップロードという方法でした。

CDKとSAMとは

今回は、AWSのCDK(Cloud Development Kit)と、SAM(Serverless Application Model)を使って、Lambda関数の開発環境をグレードアップしようと思います。

CDK

CDKはLambdaなどのAWSの各種サービスのデプロイを自動化するソリューションで、具体的にはTypeScriptなどの言語でデプロイ内容の定義と、Lambda関数自体をコーディングして、それをCloudFormationのテンプレートに変換してAWS上にデプロイするという働きをします。

AWSコンソールなどでいちいち操作してデプロイしていたのをやめて、自動化できるので、多くのデプロイ作業を人間が行う煩雑さを解消しますし、起きがちなミスをなくすことができます。いわゆるCD(Continuous Delivery)の文脈にあるツールといえるでしょう。

SAM

SAMは、その名のとおりサーバレスアプリケーションのためのフレームワークで、それ単体でLambda関数の開発からデプロイを行うことができます。開発したLambda関数をローカルで実行してテストすることも可能です。

ただ、CDKと違ってSAMはAWSの多くのサービスが使えるというわけではありません。

CDKとSAMを組み合わせる

CDKとSAMを組み合わせれば、CDKでLambda関数の開発やAWSの様々なサービスとの統合をまとめて管理しつつ、ローカルでのテストはSAMで行うということができます。

この記事は、それを試してみようというわけです。

CDKとSAMのインストール

まず、CDKをインストールします。Node.jsの環境が必要です。

npm i -g aws-cdk

次に、SAM(正確にはSAM-CLI)をインストールします。SAMでのローカルテストはDockerの環境が必要になるので、あらかじめDocker Desktopをインストールしておきます。

macOSではSAMはbrewでインストールできるのですが、私はASDFを使っているので、ASDFでインストールしました。

asdf plugin add aws-sam-cli

CDKを使い始める

プロジェクトディレクトリを作成します。

mkdir hello-cdk
cd hello-cdk

CDKプロジェクトを初期化します。今回はTypeScriptを使用します。

cdk init app --language typescript

こんな感じのファイルツリーができます。

.
├── README.md
├── bin
│   └── hello-cdk.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── hello-cdk-stack.ts
├── node_modules
│   └── (省略)
├── package-lock.json
├── package.json
├── test
│   └── hello-cdk.test.ts
└── tsconfig.json

Lambda関数の開発

lib/hello.tsを作成し、下記のようにします。POSTされたnameの値をHelloの後にくっつけるだけの簡単なプログラムです。API Gatewayにつなぐことが前提のコードになっています。

export const handler = async (event: any, context: any) => {
    try {
        let body = JSON.parse(event.body)
        let name = 'world'
        if ('name' in body) {
            name = body.name
        }
        const responseBody = {
            message: `Hello, ${name}!`
        }
        console.log('response:', responseBody)
        return {
            statusCode: 200,
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(responseBody)
        }
    } catch (error) {
        console.log('error:', error)
        let body
        if (error instanceof Error) {
            body = error.stack
        } else {
            body = JSON.stringify(error, null, 2)
        }
        return {
            statusCode: 500,
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(body)
        }
    }
}

デプロイ内容の定義

lib/hello-cdk-stack.tsを下記のように編集します。

import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
import { Runtime } from 'aws-cdk-lib/aws-lambda'
import { Duration } from 'aws-cdk-lib'
import { RestApi, LambdaIntegration } from 'aws-cdk-lib/aws-apigateway'

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

    const helloFunction = new NodejsFunction(this, 'helloFunction', {
      entry: 'lib/hello.ts',
      handler: 'handler',
      runtime: Runtime.NODEJS_18_X,
      timeout: Duration.seconds(30),
    })

    const helloApi = new RestApi(this, 'helloApi', {
      restApiName: 'Hello API',
    })
    const helloApiResource = helloApi.root.addResource('api').addResource('v1').addResource('hello')
    helloApiResource.addMethod('POST', new LambdaIntegration(helloFunction))
  }
}

CloudFormationのテンプレートに変換します。(コード変更の度に実行する必要があります。)

cdk synth

テンプレート等のファイルは、./cdk.outに作成されます。

ローカルでの実行

ここからがSAMの出番です。Lambda関数を直接実行するほか、API Gatewayのをローカルで動作させることも可能です。

Lambda関数

Lambda関数を直接実行する場合は、sam local invokeを使用します。-tオプションにCDKで作成されたCloudFormationのテンプレートを指定します。
--event -とすると、echoでパイプされた文字列(標準入力)がpayloadとして使用されます。--event ファイル名のようにすると、JSONファイルを使用することができます。

echo '{"httpMethod":"POST", "path":"/", "body":"{\"name\":\"inoue\"}"}' | sam local invoke -t ./cdk.out/HelloCdkStack.template.json --container-host 127.0.0.1 --event - helloFunction

API Gateway

sam local start-apiのようにすると、API Gatewayをローカルでテストできます。

sam local start-api -t ./cdk.out/HelloCdkStack.template.json --container-host 127.0.0.1
curl -X POST -d '{"name":"inoue"}' http://127.0.0.1:3000/api/v1/hello

AWSへのデプロイ

最後に、AWSにデプロイしてみましょう。

cdk bootstrapの実行

AWSのアカウント、リージョン毎に一度だけcdk bootstrapを実行する必要があります。(プロジェクト毎に必要なわけではありません。)

あらかじめAWS CLIをインストールして、aws configで認証情報をセットしておきましょう。

cdk bootstrap aws://<ACCOUNT>/ap-northeast-1

ACCOUNTの情報は、下記で取得することができます。

aws sts get-caller-identity

デプロイ

cdk synthした後に、cdk deployを実行します。
途中で確認メッセージが表示されるので、yと入力します。

cdk deploy

API GatewayのエンドポイントURLが表示されるので、Curlで実行してみましょう。payloadはbodyの内容だけでOKです。

curl -X POST -d '{"name":"inoue"}' https://kzr7qo04cf.execute-api.ap-northeast-1.amazonaws.com/prod/api/v1/hello

クリーンアップ

cdk destroyでクリーンアップできます。但し、cdk bootstrapで実行された内容はクリーンアップされません。(同じアカウント、リージョンでデプロイする際に利用されます。)

cdk destroy

この記事を書いた人

井上 研一

株式会社ビビンコ代表取締役、ITエンジニア/経済産業省推進資格ITコーディネータ。AI・IoTに強いITコーディネータとして活動。2018年、株式会社ビビンコを北九州市に創業。IoTソリューションの開発・導入や、画像認識モデルを活用したアプリの開発などを行っている。近著に「使ってわかった AWSのAI」、「ワトソンで体感する人工知能」。日本全国でセミナー・研修講師としての登壇も多数。