Open13

🦕 deno-lambda触ってみる

リポジトリはこちら

https://github.com/hayd/deno-lambda

ここのQUICK-STARTからSARアプリケーションを立ててみる

リンクからdeno SAR Applicationのページへ飛び、Deployボタンを押す

右下のボタンからDeployする

Create CompleteしたらCloudFormationを見に行く

「出力」タブにLayerArnの値が出ているのでこれをコピー

Lambdaの関数の作成に飛び、「一から作成」で関数名を適当に入れ、ランタイムは「ユーザー独自」とする
(日本語訳はもう少しどうにかならんかったんか)

Lambda functionが追加されたら、画面最下部からレイヤーを追加

控えておいたARNの値を設定

deno-lambdaのリリースページから最新のdeno-lambda-example.zipをダウンロードし、コードソースにアップロード

適当にログを出すよう書き換えてDeploy

Testを実行すると良い感じのResponseが帰ってくる🦕

「モニタリング」タブから「CloudWatchのログを見る」でログを確認、こちらにもちゃんと表示されている

続いてServerless Frameworkの使用を試みる serverlessはbrewで導入済み

❯ sls create --template aws-nodejs --path sls-hello-deno
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/kawarimidoll/ghq/github.com/kawarimidoll/sls-hello-deno"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v2.46.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"

❯ cd sls-hello-deno

❯ ls -A1
.npmignore
handler.js
serverless.yml 

コンパイルするためにserverless-scriptable-pluginを導入する
sls plugin install --name serverless-scriptable-pluginするとserverless.ymlに自動で追記してくれるが、これを使うとpackage.jsonが作られてしまうのでやめておく(denoのプロジェクトにpackage.jsonを置きたくない…)

❯ yarn global add serverless-scriptable-plugin
yarn global v1.22.10
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
warning "serverless-scriptable-plugin@1.2.1" has no binaries
✨  Done in 3.30s.

serverless.ymlの設定を追加 コメントはすべて省略

serverless.yml
service: sls-hello-deno
frameworkVersion: "2"

provider:
  name: aws
-   runtime: node-12.x
+   runtime: provided.al2
  lambdaHashingVersion: 20201221

+ package:
+   exclude:
+     - .deno_dir/gen/file

functions:
  hello:
    handler: handler.hello
    events:
      - http:
-           path: /users/create
+           path: /hello
          method: get

+ plugins:
+   - serverless-scriptable-plugin
+ custom:
+   scriptHooks:
+     before:package:createDeploymentArtifacts: DENO_DIR=.deno_dir deno cache api/hello.ts && cp -R .deno_dir/gen/file/$PWD/ .deno_dir/LAMBDA_TASK_ROOT

しかしsls printしたらエラー

❯ sls print          
 
 Serverless Error ----------------------------------------
 
  Serverless plugin "serverless-scriptable-plugin" not found. Make sure it's installed and listed in the "plugins" section of your serverless config file.

pluginが見えていない…?

brewではなくyarn globalで入れ直したら実行できた

❯ brew uninstall serverless
Uninstalling /opt/homebrew/Cellar/serverless/2.46.0... (20,301 files, 124.7MB)
❯ yarn global add serverless serverless-scriptable-plugin 
yarn global v1.22.10
[1/4] 🔍  Resolving packages...
(略)
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
warning "serverless > @serverless/components > inquirer-autocomplete-prompt@1.3.0" has unmet peer dependency "inquirer@^5.0.0 || ^6.0.0 || ^7.0.0".
[4/4] 🔨  Building fresh packages...
success Installed "serverless@2.46.0" with binaries:
      - serverless
      - sls
warning "serverless-scriptable-plugin@1.2.1" has no binaries
✨  Done in 52.98s.

また、serverless.ymlのpackage.includeとpackage.excludeはdeprecatedらしいのでpackage.patternsに切り替える

❯ sls print 
service:
  name: sls-hello-deno
frameworkVersion: '2'
provider:
  stage: dev
  region: us-east-1
  name: aws
  runtime: provided.al2
  lambdaHashingVersion: '20201221'
  versionFunctions: true
package:
  patterns:
    - '!.deno_dir/gen/file'
functions:
  hello:
    handler: handler.hello
    events:
      - httpApi:
          path: /hello
          method: get
    name: sls-hello-deno-dev-hello
plugins:
  - serverless-scriptable-plugin
custom:
  scriptHooks:
    before:package:createDeploymentArtifacts: >-
      DENO_DIR=.deno_dir deno cache api/hello.ts && cp -R .deno_dir/gen/file/$PWD/
      .deno_dir/LAMBDA_TASK_ROOT

とりあえず公式のhello.tsを参考にファイルを作成

deps.ts
import {
  APIGatewayProxyEventV2,
  APIGatewayProxyResultV2,
  Context,
} from "https://deno.land/x/lambda/mod.ts";

export type { APIGatewayProxyEventV2, APIGatewayProxyResultV2, Context };
api/hello.ts
import {
  APIGatewayProxyEventV2,
  APIGatewayProxyResultV2,
  Context,
} from "../deps.ts";

// deno-lint-ignore require-await
export async function handler(
  event: APIGatewayProxyEventV2,
  _context: Context,
): Promise<APIGatewayProxyResultV2> {
  return {
    statusCode: 200,
    headers: { "content-type": "text/html;charset=utf8" },
    body: JSON.stringify(
      {
        message: `Welcome to deno ${Deno.version.deno} 🦕`,
        input: event,
      },
      null,
      2,
    ),
  };
}

いきなりデプロイするのではなくdocker-lambdaを使ってみる

https://github.com/lambci/docker-lambda

deno-lambdaのリリースページからdeno-lambda-layer.zipをダウンロードして展開、今回のプロジェクトディレクトリに配置する

❯ tree
.
├── .gitignore
├── api
│  └── hello.ts
├── deno-lambda-layer
│  ├── .deno_dir
│  │  ├── deps
│  │  │  └── https
│  │  │     └── deno.land
│  │  │        └── x
│  │  │           └── lambda
│  │  │              ├── mod.ts
│  │  │              └── types.d.ts
│  │  ├── gen
│  │  └── LAMBDA_TASK_ROOT
│  │     ├── hello.ts.buildinfo
│  │     ├── hello.ts.js
│  │     ├── hello.ts.meta
│  │     ├── mod.ts.js
│  │     └── mod.ts.meta
│  ├── bin
│  │  └── deno
│  ├── bootstrap
│  └── hello.ts
├── deps.ts
└── serverless.yml

READMEのdocker-lambdaの項目にあるサンプルスクリプトを利用し、今回の構成に沿うようLAYER_DIRおよびhandlerの部分を調整して実行

❯ docker run -it --rm -v "$PWD":/var/task:ro,delegated -v "$PWD"/deno-lambda-layer:/opt:ro,delegated lambci/lambda:provided.al2 api/hello.handler '{}'
(略)

{"statusCode":200,"headers":{"content-type":"text/html;charset=utf8"},"body":"{\n  \"message\": \"Welcome to deno 1.11.0 🦕\",\n  \"input\": {}\n}"}

api/hello.tsで定義したhandler()を実行できた👍

"stay-open" API modeも試してみる

❯ docker run -e DOCKER_LAMBDA_STAY_OPEN=1 -p 9001:9001 -it --rm -v "$PWD":/var/task:ro,delegated -v "$PWD"/deno-lambda-layer:/opt:ro,delegated lambci/lambda:provided.al2 api/hello.handler
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
Lambda API listening on port 9001...

別ターミナルを開いてaws-cliからリクエスト実行
なおaws-cliがバージョン2の場合は--cli-binary-format raw-in-base64-outオプションが必要になるので注意

❯ aws lambda invoke --endpoint http://localhost:9001 --cli-binary-format raw-in-base64-out --no-sign-request --function-name deno-func --payload '{"message":"hello from aws-cli"}' output.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

❯ cat output.json
{"statusCode":200,"headers":{"content-type":"text/html;charset=utf8"},"body":"{\"message\":\"Welcome to deno 1.11.0 🦕\",\"input\":{\"message\":\"hello from aws-cli\"}}"}

ローカルでの挙動は問題なさそう✨

ではネットの海へ

❯ sls deploy -v
Running command: DENO_DIR=.deno_dir deno cache api/hello.ts && cp -R .deno_dir/gen/file/$PWD/ .deno_dir/LAMBDA_TASK_ROOT
Download https://deno.land/x/lambda/mod.ts
Warning Implicitly using latest version (1.11.0) for https://deno.land/x/lambda/mod.ts
Download https://deno.land/x/lambda@1.11.0/mod.ts
Download https://deno.land/x/lambda@1.11.0/types.d.ts
Check file:///Users/kawarimidoll/ghq/github.com/kawarimidoll/sls-hello-deno/api/hello.ts
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
(略)
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service sls-hello-deno.zip file to S3 (32.69 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
(略)
Serverless: Stack update finished...
Service Information
service: sls-hello-deno
stage: dev
region: us-east-1
stack: sls-hello-deno-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://xxxxx.execute-api.us-east-1.amazonaws.com/hello
functions:
  hello: sls-hello-deno-dev-hello
layers:
  None

Stack Outputs
(略)

デプロイには成功

しかしLayerが設定されていないので実行できない…

serverless.ymlを修正した

serverless.yml
service: sls-hello-deno
frameworkVersion: "2"

provider:
  name: aws
  runtime: provided.al2
  lambdaHashingVersion: 20201221

package:
  patterns:
    - "!.deno_dir/gen/file"

functions:
  hello:
    handler: api/hello.handler
+     layers:
+       - !GetAtt Deno.Outputs.LayerArn
+     memorySize: 128
+     description: Say hello and show deno version
    events:
      - httpApi:
          path: /hello
          method: get

+ resources:
+   Transform: AWS::Serverless-2016-10-31
+   Resources:
+     Deno:
+       Type: AWS::Serverless::Application
+       Properties:
+         Location:
+           ApplicationId: arn:aws:serverlessrepo:us-east-1:390065572566:applications/deno
+           SemanticVersion: 1.11.0

plugins:
  - serverless-scriptable-plugin
custom:
  scriptHooks:
    before:package:createDeploymentArtifacts: DENO_DIR=.deno_dir deno cache api/hello.ts && cp -R .deno_dir/gen/file/$PWD/ .deno_dir/LAMBDA_TASK_ROOT

resourcesでCloudFormationの設定を行う
これは公式のサンプルそのまま
特にTransform: AWS::Serverless-2016-10-31はアプリケーションごとのバージョニングかと思ったらAWS SAMのおまじないだった
この辺はSAM用のテンプレートとも同じ書き方になっている

+ resources:
+   Transform: AWS::Serverless-2016-10-31
+   Resources:
+     Deno:
+       Type: AWS::Serverless::Application
+       Properties:
+         Location:
+           ApplicationId: arn:aws:serverlessrepo:us-east-1:390065572566:applications/deno
+           SemanticVersion: 1.11.0

これでzipファイルを使った場合と同じようにCloudFormationでArnが出力される

そしてそれを!GetAttしてfunctionのレイヤーに適用している

functions:
  hello:
    handler: api/hello.handler
+     layers:
+       - !GetAtt Deno.Outputs.LayerArn

これでsls deployするとレイヤーが作成される

sls deployの結果に出たエンドポイントへアクセスするとちゃんとレスポンスが帰ってきた!

❯ curl https://xxx.execute-api.us-east-1.amazonaws.com/hello
{"message":"Welcome to deno 1.11.0 🦕","input":{"version":"2.0","routeKey":"GET /hello", ...

いったん基本は完成したので記録

ディレクトリ構成はこんな感じ
提供されているdocker用のzipファイルにはいろいろ入っているがこれ以外は不要ぽい

❯ tree
.
├── .gitignore
├── api
│  └── hello.ts
├── deno-lambda-layer
│  ├── bin
│  │  └── deno
│  └── bootstrap
├── deps.ts
├── README.md
└── serverless.yml

tsファイルの内容は以下の通り

deps.ts
import {
  APIGatewayProxyEventV2,
  APIGatewayProxyResultV2,
  Context,
} from "https://deno.land/x/lambda@1.11.0/mod.ts";

export type { APIGatewayProxyEventV2, APIGatewayProxyResultV2, Context };
api/hello.ts
import {
  APIGatewayProxyEventV2,
  APIGatewayProxyResultV2,
  Context,
} from "../deps.ts";

// deno-lint-ignore require-await
export async function handler(
  event: APIGatewayProxyEventV2,
  _context: Context,
): Promise<APIGatewayProxyResultV2> {
  console.log("[hello] got request!");
  return {
    statusCode: 200,
    headers: { "content-type": "text/html;charset=utf8" },
    body: JSON.stringify({
      message: `Welcome to deno ${Deno.version.deno} 🦕`,
      input: event,
    }),
  };
}

Postmanでも疎通確認

今回はeventをそのまま返してしまったけど現実的にはevent.body.xxxを取り出して使う感じかな

pushしてから気づいたけどdeno-lambda-layerのdeno実行ファイルはサイズが大きくてgithubから怒られるのでgitignoreしたほうが良さげ

❯ zip -r deno-lambda-layer.zip deno-lambda-layer

❯ echo deno-lambda-layer/ >> .gitignore

❯ git  rm --cached -r deno-lambda-layer/
rm 'deno-lambda-layer/bin/deno'
rm 'deno-lambda-layer/bootstrap'

ログインするとコメントできます