1
1

More than 3 years have passed since last update.

AWS CDK + SAMでローカル実行しているLambdaにattachしてデバッグ実行したメモ

Last updated at Posted at 2021-03-25

概要

前回はローカルでMongoを動かした。
今回はローカルで実行しているlambdaにデバッグ実行を試してみる。
ソースコード

環境

  • Windows 10
  • aws-cdk 1.95.1
  • SAM CLI. 1.20.0
  • VSCode 1.54.3

やること

  • attachしてデバッグ実行
  • launch.jsonの自動生成

フォルダ階層

- .vscode
  - launch.json       # VSCodeのデバッガ設定
- cdk                 # cdk用の設定フォルダ
  - lib
    - sample-stack.ts # デバッグ用の設定
  - cdk.out
    - assets.hoge
      - index.js
      - index.map.js
    - template.yaml
  - cdk.json
  - package.json
- src
  - handlers
- tsconfig.json

準備

ソースマップ

デバッグ実行でTypescriptとバンドルされたjsを紐づけるためのソースマップが必要。

cdk/lib/sample-stack.ts
    const echoFunction = new NodejsFunction(this, 'echo', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: `${entryHandlerDir}/get-echo.ts`,
      functionName: 'get-echo',
      handler: 'echoHandler',
+      bundling: { sourceMap: true },
    });
    new NodejsFunction(this, 'test', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: `${entryHandlerDir}/test.ts`,
      functionName: 'kotatest',
      bundling: {
+        sourceMap: true,
        externalModules: [
          'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
          'date-fns', // Layrerに入れておきたいモジュール
        ],
        define: { // Replace strings during build time
          'process.env.API_KEY': JSON.stringify(`\\"${'xxx-xxx'}\\"`), // バグってそう.エスケープしないとInvalid define valueのエラー
        },

      },
      layers: [nodeModulesLayer],
    });

デバッガ設定

dockerのコンテナ上のソースと、ローカルに出力したソースマップの紐づけがデバッガに必要。
cdk synth > cdk.out/template.yamlコマンドでソースを出力した後、紐づけの設定を行う。
以下はAPI用に一つ、invokeで呼び出すように一つ、合計2つの設定をしている状態

.vscode/lauch.json
{
  "version": "0.2.0",
  "configurations": [
      {
          "type": "node",
          "request": "attach",
          "name": "api echo ",
          "port": 5858,
          "address": "localhost",
          "localRoot": "${workspaceFolder}/cdk/cdk.out/asset.eb94322643dddde1077f91e199ef7ddd442fb0064b028e1c9196f8b5130ec4e1",
          "remoteRoot": "/var/task",
          "protocol": "inspector",
      },
      {
        "type": "node",
        "request": "attach",
        "name": "invoke test ",
        "port": 5858,
        "address": "localhost",
        "localRoot": "${workspaceFolder}/cdk/cdk.out/asset.ff62102abf256369f4067436df86939e80532114601e39c34faa7edd1c438306",
        "remoteRoot": "/var/task",
        "protocol": "inspector",
    }
  ]
}

デバッグ実行設定

-d ポート番号オプションでデバッグ実行ができるので、スクリプトにしておく。

cdk/package.json
    "localinvoke": "cd cdk.out && sam.cmd local invoke testAF53AC38 --no-event",
+    "debuginvoke": "cd cdk.out && sam.cmd local invoke -d 5858 testAF53AC38 --no-event",
    "dev-serve": "cd cdk.out && sam.cmd local start-api -n ../env.json",
+    "debug-serve": "cd cdk.out && sam.cmd local start-api -d 5858 -n ../env.json"

ブレークポイント設定

確認したい箇所にブレークポイントを入れておく。

実行

invoke

npm run debuginvokeで起動。

image.png

サイドバーのデバッグから、「invoke test」を選択。実行ボタン|>を押す。

image.png

最初はjsファイルで止まるので、続行してやる。
image.png

ブレークポイントで止めることができた。 ここからステップ実行可能となる。

image.png

API

APIも同様。
npm debug-serve
デバッグしたいAPI(今回の場合はecho)を叩くと、For help, see: https://nodejs.org/en/docs/inspectorが表示されるので、「api echo」を選択。実行ボタン|>を押す。

image.png

アタッチされるとjsファイルでいったん止まる。続行する。

image.png

ブレークポイントで止めることができた。
image.png

ソースコード

launch.jsonの自動作成

毎回、assetsのパスを確認するのは大変。
なので、バンドルと同時にlaunch.jsonを更新するようにした。

cdk/bin/sample-index.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { SampleStack } from '../lib/sample-stack';
import { bundleNpm } from '../lib/process/setup';
+ import { createLaunch } from '../lib/process/after';

// pre-process
bundleNpm();

// // create app
const app = new cdk.App();
const stack = new SampleStack(app, 'SampleStack2021');
+ createLaunch(stack)
  • 愚直にtemplate.jsonから得られる情報をlaunch.jsonに書き込んでいる。
cdk/lib/process/after.ts
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';
import * as fs from 'fs';
import * as path from 'path'
import { SampleStack } from '../sample-stack';
const launchFilePath = path.resolve(process.cwd(), '..', '.vscode', 'launch.json');
const assetIndex = 1;
const debugPort = 5858;
const configuration = {
  "type": "node",
  "request": "attach",
  "name": "api echo ",
  "port": debugPort,
  "address": "localhost",
  "localRoot": "${workspaceFolder}/cdk/cdk.out/ASSET_PATH",
  "remoteRoot": "/var/task",
  "protocol": "inspector",
}
export const createLaunch = async (stack: SampleStack) => {
  const confs = stack.node.children.filter((item): item is NodejsFunction => item instanceof NodejsFunction)
    .map((item: NodejsFunction) => ({
      ...configuration,
      // @ts-ignore
      name: `lambda ${item.physicalName} ${item.permissionsNode.uniqueId.replace(stack.stackName, '')}`, // uniqueIdはdeprecatedだが現在templateに出力されているので使う
      // @ts-ignore
      localRoot: configuration.localRoot.replace('ASSET_PATH', item.node.children[assetIndex].assetPath)
    }))
  const launch = {
    "version": "0.2.0",
    "configurations": confs
  }
  fs.writeFileSync(launchFilePath, JSON.stringify(launch))
}

作成されたlaunch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "lambda kotahello hello25C2F4BF",
      "port": 5858,
      "address": "localhost",
      "localRoot": "${workspaceFolder}/cdk/cdk.out/asset.005313309c1a04502205e8341511c4f2d861dd96521f4d707c2b9b49b9c9cd81",
      "remoteRoot": "/var/task",
      "protocol": "inspector"
    },
    {
      "type": "node",
      "request": "attach",
      "name": "lambda kotatest test2F9CF384",
      "port": 5858,
      "address": "localhost",
      "localRoot": "${workspaceFolder}/cdk/cdk.out/asset.ff62102abf256369f4067436df86939e80532114601e39c34faa7edd1c438306",
      "remoteRoot": "/var/task",
      "protocol": "inspector"
    },
    {
      "type": "node",
      "request": "attach",
      "name": "lambda get-echo echoD896FE0C",
      "port": 5858,
      "address": "localhost",
      "localRoot": "${workspaceFolder}/cdk/cdk.out/asset.eb94322643dddde1077f91e199ef7ddd442fb0064b028e1c9196f8b5130ec4e1",
      "remoteRoot": "/var/task",
      "protocol": "inspector"
    },
    {
      "type": "node",
      "request": "attach",
      "name": "lambda get-user getUserD5985AD3",
      "port": 5858,
      "address": "localhost",
      "localRoot": "${workspaceFolder}/cdk/cdk.out/asset.d8b09a14625edbffe651afd290e80d8e761c109eb9f36bf78118877a0887ef4c",
      "remoteRoot": "/var/task",
      "protocol": "inspector"
    },
    {
      "type": "node",
      "request": "attach",
      "name": "lambda post-user postUser43EF2B65",
      "port": 5858,
      "address": "localhost",
      "localRoot": "${workspaceFolder}/cdk/cdk.out/asset.b7ec805956444f4fc600442e05c20ccdb9d8eb8d075babcb68381db04d468b3e",
      "remoteRoot": "/var/task",
      "protocol": "inspector"
    }
  ]
}

ソースコード

リファクタリング

uniqueIdがdeprecateのため、jsonから必要な情報を読み込むようにリファクタリング

cdk/lib/process/after.ts
import * as fs from 'fs';
import * as path from 'path'
import { SampleStack } from '../sample-stack';
const launchFilePath = path.resolve(process.cwd(), '..', '.vscode', 'launch.json');
const assetPlaceHolder = 'ASSET_PATH';
const debugPort = 5858;
const configuration = {
  "type": "node",
  "request": "attach",
  "name": "",
  "port": debugPort,
  "address": "localhost",
  "localRoot": "${workspaceFolder}/cdk/cdk.out/" + assetPlaceHolder,
  "remoteRoot": "/var/task",
  "protocol": "inspector",
}
type Config = typeof configuration
export const createLaunch = async (stack: SampleStack) => {
  const buf = fs.readFileSync(path.join(process.cwd(), 'cdk.out', stack.templateFile))
  const template = JSON.parse(buf.toString());
  const resources = template.Resources;
  const confs: Config[] = [];

  for (const prop in resources) {
    if (resources[prop].Type !== 'AWS::Lambda::Function') continue

    confs.push({
      ...configuration,
      name: `lambda ${prop}`,
      localRoot: configuration.localRoot.replace(assetPlaceHolder, resources[prop].Metadata['aws:asset:path'])
    })
  }

  const launch = {
    "version": "0.2.0",
    "configurations": confs
  }
  fs.writeFileSync(launchFilePath, JSON.stringify(launch))
}

エラー

前のローカル実行のゴミが残っていたりすると以下のエラーになることがある。

requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: http+docker://localnpipe/v1.35/containers/9a7c6cc76fd0736fe91333a05070a69c4c92b6cd52343a438fd7e626af7d1a5e/start

以下のように、いったんdockerを消してやるとよい。

docker stop $(docker ps -q)

# 全てのdockerコンテナを削除
docker rm -f $(docker ps -aq)

参考

AWS - Step-through debugging Node.js functions locally
AWS - サーバーレスアプリケーションをデバッグするための設定オプション
Stack Over Flow - How to debug in VS Code a local AWS Lambda function with API Gateway written in TypeScript?

msdn - Visual Studio で JavaScript アプリまたは TypeScript アプリをデバッグする
AWS - sam local start-api
3step
設定なしでデバッグ実行
esbuild

1
1
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
1