1
0

More than 3 years have passed since last update.

CDKでLambda作ってSAMでローカルで動作確認したメモ

Posted at

最近仕事でCDKを触り始めたので、個人でも適当なSlack botでも作ってみようとかと思って触ってみてます。デプロイがとてもラクとはいえ、Lambdaを動作確認しながら作っていくのに毎回デプロイするのはつらみです…

調べてみると、CDKで作ったLambdaでもSAMを使ってローカルで動かす方法があるようなので、スタックの作成からLambdaのローカル実行まで一通り試してみました。

環境

  • OS
    • Windows10 WSL(Ubuntu 18.04 LTS)
    • macOS Mojave
  • CDK 1.46.0
  • SAM 0.53.0
  • Docker 19.03.11
    • VirtualBox上のUbuntu 18.04 LTS

CDKでLambdaの作成

CDKインストール

npmでインストール。

# CDKインストール
npm install -g aws-cdk
/home/hotaru/.anyenv/envs/nodenv/versions/12.16.3/bin/cdk -> /home/hotaru/.anyenv/envs/nodenv/versions/12.16.3/lib/node_modules/aws-cdk/bin/cdk
+ aws-cdk@1.46.0
added 216 packages from 186 contributors in 13.791s

テンプレート生成

まずは適当なディレクトリを作成。のちのちSlack botを作っていくリポジトリにする予定なので、それらしい名前で掘っておく。

mkdir slack-auto-stamp-bot
cd slack-auto-stamp-bot

cdk init でテンプレートを生成。オプションなしで叩くとどんなテンプレートがあるのか教えてくれます。

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|fsharp|java|javascript|python|typescript]

基本的には app を使います。言語はTypeScriptでいくので、

cdk init app -l typescript
Applying project template app for typescript
Initializing a new git repository...
Executing npm install...

(以下略)

と言った感じ。生成後のディレクトリ構成は以下の通り。

/slack-auto-stamp-bot
├── README.md
├── bin
│   └── slack-auto-stamp-bot.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── slack-auto-stamp-bot-stack.ts
├── node_modules
├── package-lock.json
├── package.json
├── test
│   └── slack-auto-stamp-bot.test.ts
└── tsconfig.json

lib 配下のファイルにリソースを定義していきます。その前に、構築したいリソース毎にパッケージが別れているので、必要なものを追加しておきます。

# LambdaとAPI Gatewayを追加
npm add @aws-cdk/aws-lambda @aws-cdk/aws-apigateway
npm WARN slack-auto-stamp-bot@0.1.0 No repository field.
npm WARN slack-auto-stamp-bot@0.1.0 No license field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.1.3 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

+ @aws-cdk/aws-apigateway@1.46.0
+ @aws-cdk/aws-lambda@1.46.0
added 17 packages from 1 contributor and audited 794 packages in 13.799s

15 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

この記事ではLambdaしか使いませんが、のちのちAPI Gatewayを使用する予定なので一緒にいれました。
CDKでLambdaを作っていく前に、Lambda本体のコードを作ります。こちらはRubyで。

mkdir -p lambda/stamp-bot-function
cd lambda/stamp-bot-function/
touch app.rb

とりあえずeventをそのまま返すコードでも置いときます。

lambda/stamp-bot-function/app.rb
# frozen_string_literal: true

require 'json'

def handler(event:, context:)
  {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.generate(event)
  }
end

今度はLambdaのリソースを作っていきます。すでに仕事で触っていたのでなんてことないですが、初めてのときはこちらが参考になりました。

lib/slack-auto-stamp-bot-stack.ts
import * as cdk from '@aws-cdk/core'
import * as lambda from '@aws-cdk/aws-lambda'

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

        const stampBotFunctionProps: lambda.FunctionProps = {
            runtime: lambda.Runtime.RUBY_2_7,
            code: lambda.Code.fromAsset('lambda/stamp-bot-function'),
            handler: 'app.handler'
        }
        const stampBotFunction = new lambda.Function(this, 'stampBotFunction', stampBotFunctionProps)
    }
}

stampBotFunctionProps のところでLambdaの設定をいろいろ書いてます。最低限 runtimecodehandler の指定が必要です。ちなみに lambda.Code.fromAsset() で渡しているパスは、プロジェクトルートからの相対パスです。
Function のコンストラクタには Stack(このクラス自身) 、 スタック内で一意となる論理ID、スタックの設定( 作成した stampBotFunctionProps )を渡します。

デプロイ

デプロイする前に、初回はアセット格納用のS3バケットが必要になるので、 cdk bootstrap を実行して作成しておきます。

cdk bootstrap
(node:18789) ExperimentalWarning: The fs.promises API is experimental
 ⏳  Bootstrapping environment aws://xxxxxxxxxxxx/ap-northeast-1...
CDKToolkit: creating CloudFormation changeset...
 0/3 | 2:18:23 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket       | StagingBucket
 0/3 | 2:18:25 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket       | StagingBucket Resource creation Initiated
 1/3 | 2:18:47 PM | CREATE_COMPLETE      | AWS::S3::Bucket       | StagingBucket
 1/3 | 2:18:49 PM | CREATE_IN_PROGRESS   | AWS::S3::BucketPolicy | StagingBucketPolicy
 1/3 | 2:18:50 PM | CREATE_IN_PROGRESS   | AWS::S3::BucketPolicy | StagingBucketPolicy Resource creation Initiated
 2/3 | 2:18:50 PM | CREATE_COMPLETE      | AWS::S3::BucketPolicy | StagingBucketPolicy
 3/3 | 2:18:52 PM | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CDKToolkit
 ✅  Environment aws://xxxxxxxxxxxx/ap-northeast-1 bootstrapped.

こんなスタックができます。
image.png

まずはビルドします。

npm run-script build

作成されるスタックの確認。 cdk diff でスタックの変更内容を確認できます。

cdk diff
Stack SlackAutoStampBotStack
IAM Statement Changes
┌───┬─────────────────────────────────────┬────────┬────────────────┬──────────────────────────────┬───────────┐
│   │ Resource                            │ Effect │ Action         │ Principal                    │ Condition │
├───┼─────────────────────────────────────┼────────┼────────────────┼──────────────────────────────┼───────────┤
│ + │ ${stampBotFunction/ServiceRole.Arn} │ Allow  │ sts:AssumeRole │ Service:lambda.amazonaws.com │           │
└───┴─────────────────────────────────────┴────────┴────────────────┴──────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬─────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                        │ Managed Policy ARN                                                             │
├───┼─────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${stampBotFunction/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴─────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter AssetParameters/81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9/S3Bucket AssetParameters81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9S3Bucket1EF80AD6: {"Type":"String","Description":"S3 bucket for asset \"81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9\""}
[+] Parameter AssetParameters/81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9/S3VersionKey AssetParameters81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9S3VersionKey462471F6: {"Type":"String","Description":"S3 key for asset version \"81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9\""}
[+] Parameter AssetParameters/81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9/ArtifactHash AssetParameters81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9ArtifactHash5F87EF54: {"Type":"String","Description":"Artifact hash for asset \"81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9\""}

Conditions
[+] Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::IAM::Role stampBotFunction/ServiceRole stampBotFunctionServiceRoleE8B84E9C
[+] AWS::Lambda::Function stampBotFunction stampBotFunction1C7A789E

Lambdaの実行に必要なIAMロールも勝手に作ってくれてます。
では、デプロイします。

# デプロイ
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 │
├───┼─────────────────────────────────────┼────────┼────────────────┼──────────────────────────────┼───────────┤
│ + │ ${stampBotFunction/ServiceRole.Arn} │ Allow  │ sts:AssumeRole │ Service:lambda.amazonaws.com │           │
└───┴─────────────────────────────────────┴────────┴────────────────┴──────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬─────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                        │ Managed Policy ARN                                                             │
├───┼─────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${stampBotFunction/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴─────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(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
SlackAutoStampBotStack: deploying...
[0%] start: Publishing 81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9:current
[100%] success: Published 81ffb93ce7b48a8e0dca23f447c0835e5b3d9832e0e2cebd6ead445127e6ddf9:current
SlackAutoStampBotStack: creating CloudFormation changeset...





 ✅  SlackAutoStampBotStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/SlackAutoStampBotStack/3be0c370-b2fc-11ea-968b-06ef10f7e39e

できました。
image.png

適当に動作確認。問題なさそうです。
image.png
image.png

ローカルでテストしたい

動作確認しながら作っていくとなると毎回デプロイは大変なので、ローカルでLambdaを動かしたいです。
公式ドキュメントにSAMでローカル実行する方法が記載されていたので、こちらを参考にやってみます。
SAMとDockerが必要になりますが、こちらはインストール済みとします。自分はVagrantで立てたUbuntuにDockerを入れているので、SAMはそちらに入れました。

まず、SAMで使用するためのCloudFormationテンプレートを生成します。

cdk synth --no-staging > template.yaml

生成したテンプレートから対象のLambdaの論理IDを探します。 TypeAWS::Lambda::Function のリソースですね。
Dockerが別ホストで実行されている場合など、Lambda本体のパスが変わってしまう場合は aws:asset:path を必要に応じて修正します。

template.yaml
  stampBotFunction1C7A789E: # <- これを確認
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket:

# (略)

    Metadata:
      aws:cdk:path: SlackAutoStampBotStack/stampBotFunction/Resource
      aws:asset:path: /home/vagrant/docker/slack-auto-stamp-bot/lambda/stamp-bot-function # <- 必要に応じてパスを修正
      aws:asset:property: Code

ここまでできたら、あとは適当にLambdaに渡すeventでも作成して…

event_sample/oshi.json
{
  "kotori": "Aya Uchida",
  "you": "Shuka Saito",
  "shizuku": "Kaori Maeda"
}

sam local invoke で実行!

sam local invoke stampBotFunction1C7A789E --event event_sample/oshi.json
Invoking app.handler (ruby2.7)

Fetching lambci/lambda:ruby2.7 Docker container image......
Mounting /home/vagrant/docker/slack-auto-stamp-bot/lambda/stamp-bot-function as /var/task:ro,delegated inside runtime container
START RequestId: a974a2ee-c7dd-1ec1-9ff2-c418511d44bf Version: $LATEST
END RequestId: a974a2ee-c7dd-1ec1-9ff2-c418511d44bf
REPORT RequestId: a974a2ee-c7dd-1ec1-9ff2-c418511d44bf  Init Duration: 372.29 ms    Duration: 3.59 ms   Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 22 MB

{"statusCode":200,"headers":{"Content-Type":"application/json"},"body":"{\"kotori\":\"Aya Uchida\",\"you\":\"Shuka Saito\",\"shizuku\":\"Kaori Maeda\"}"}

実行できました!

所感

使用する言語の知識は必要になりますが、CloudFormationのyamlよりも内容把握しやすかったりするので、個人的にはCDKとても良きです。
ローカルでの動作確認の手段もちゃんと検証できたので、いろいろ作って遊んでみたいです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0