最近仕事で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をそのまま返すコードでも置いときます。
# frozen_string_literal: true
require 'json'
def handler(event:, context:)
{
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: JSON.generate(event)
}
end
今度はLambdaのリソースを作っていきます。すでに仕事で触っていたのでなんてことないですが、初めてのときはこちらが参考になりました。
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の設定をいろいろ書いてます。最低限 runtime
、 code
、 handler
の指定が必要です。ちなみに 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.
まずはビルドします。
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
ローカルでテストしたい
動作確認しながら作っていくとなると毎回デプロイは大変なので、ローカルでLambdaを動かしたいです。
公式ドキュメントにSAMでローカル実行する方法が記載されていたので、こちらを参考にやってみます。
SAMとDockerが必要になりますが、こちらはインストール済みとします。自分はVagrantで立てたUbuntuにDockerを入れているので、SAMはそちらに入れました。
まず、SAMで使用するためのCloudFormationテンプレートを生成します。
cdk synth --no-staging > template.yaml
生成したテンプレートから対象のLambdaの論理IDを探します。 Type
が AWS::Lambda::Function
のリソースですね。
Dockerが別ホストで実行されている場合など、Lambda本体のパスが変わってしまう場合は aws:asset:path
を必要に応じて修正します。
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でも作成して…
{
"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とても良きです。
ローカルでの動作確認の手段もちゃんと検証できたので、いろいろ作って遊んでみたいです。