📌

VSCode Remote Containers で AWS CDK と AWS SAM を使いコンテナ内部でLambdaを実行する

2022/03/10に公開

必要な設定

ローカルのコンテナ内でsam local invokeコマンドを実行することを目的に、VSCode Remote Containers を使ってAWS CDK と AWS SAM を実行できる環境を構築した。
設定は以下の2ファイルです。

.devcontainer/devcontainer.json
{
  "name": "cdk-and-sam",
  "image": "mcr.microsoft.com/devcontainers/base:debian",
  "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind",
  "customizations": {
    "vscode": {
      "extensions": [
        "amazonwebservices.aws-toolkit-vscode"
      ]    
    }
  },
  "features": {
    "ghcr.io/devcontainers/features/node:1": {
      "version": "lts"
    },
    "ghcr.io/devcontainers/features/docker-from-docker:1": {
      "version": "latest"
    }
  },
  "remoteUser": "vscode",
  "workspaceFolder": "${localWorkspaceFolder}",
  "postCreateCommand": "bash ./.devcontainer/postCreateCommand.sh"
}
.devcontainer/postCreateCommand.sh
#!/bin/bash
set -ex

# install cdk
# https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/getting_started.html#getting_started_install
npm install -g aws-cdk

# install sam
# https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install-linux.html#serverless-sam-cli-install-linux-sam-cli
curl -o /tmp/aws-sam-cli-linux-x86_64.zip -L https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
cd /tmp
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
sudo ./sam-installation/install
rm -rf aws-sam-cli-linux-x86_64.zip sam-installation
cd -

この設定が入ったAWS CDKのサンプルリポジトリは以下にある。
https://github.com/nmemoto/vscode-remote-containers-cdk-and-sam

なぜ上記の設定でコンテナ内でsam local invokeコマンドを実行できるようになるかについて、特に必要だと思われる箇所について以下で説明する。

詳細説明

Dev container features

以下の設定は、Dev container features の機能を用いたものである。

  "features": {
    "ghcr.io/devcontainers/features/node:1": {
      "version": "lts"
    },
    "ghcr.io/devcontainers/features/docker-from-docker:1": {
      "version": "latest"
    }
  },

これにより、AWS CDKコマンドの実行環境のためのNode.jsと、AWS CDKやAWS SAM CLIが内部的に使用しているDockerコマンド実行のための環境構築を行っている。
ちなみに、docker-from-docker導入時の処理はこちらにあるが、いい感じにホストのDocker Engineを使用できるようにソケットファイルの設定やユーザーの権限設定をやってくれ、面倒な設定を肩代わりしてくれることがわかる。

Dev container features については公式情報だと以下に説明がある。
https://code.visualstudio.com/docs/remote/containers#_dev-container-features-preview
https://github.com/devcontainers/features

また、Dev container featuresについては、筆者も過去に以下でまとめている。
https://zenn.dev/nmemoto/articles/dev-container-features-are-useful#dev-container-features-(preview)

Workspace Volume Mount

以下の設定により、ホスト側の絶対パスと同じ絶対パスをコンテナ内にも作成、そのパスをコンテナ内のWorkspaceとして登録している。

  "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind",
  "workspaceFolder": "${localWorkspaceFolder}",

上記で使用している設定や変数については以下に説明がある。
https://code.visualstudio.com/docs/remote/devcontainerjson-reference

ではなぜこの設定が必要なのか、この設定をせずに失敗する事象から説明する。

以下のドキュメントでは、AWS CDK アプリケーションでのLambda関数のローカル実行について記載されている。
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-cdk-getting-started.html#serverless-cdk-tutorial-hello-init.title

そこで記載されているコマンドを今回のリポジトリに置き換えると以下となる。

cdk synth --no-staging
sam local invoke AppStack-Function --no-event -t ./cdk.out/AppStack.template.json

Workspace Volume Mountの設定なしだと、コンテナ内のデフォルトのworkspaceFolderは/workspace/vscode-remote-containers-cdk-and-sam/となる。
そこからの相対パスの./appにCDKプロジェクトが格納されているが、そこでcdk synth --no-stagingを実行すると、./cdk.out以下にAppStack.template.jsonが出力される。このファイルのAppStack-Functionに関係する出力は以下となった。

/workspace/vscode-remote-containers-cdk-and-sam/app/cdk.out/AppStack.template.json
・・・
    "AppStackFunctionA0C4729X": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        ・・・・・・・
        "FunctionName": "AppStack-Function",
        "Handler": "index.handler",
        "Runtime": "nodejs16.x",
        "Timeout": 3
      },
      "DependsOn": [
        "AppStackFunctionServiceRoleB5E28B8A"
      ],
      "Metadata": {
        "aws:cdk:path": "AppStack/AppStack-Function/Resource",
        "aws:asset:path": "/workspace/vscode-remote-containers-cdk-and-sam/app/lambda/my_function",
        "aws:asset:is-bundled": false,
        "aws:asset:property": "Code"
      }
・・・

またsam local invoke AppStack-Function --no-event -t ./cdk.out/AppStack.template.jsonの出力は以下となった。

・・・・・
docker.errors.APIError: 500 Server Error: Internal Server Error ("b'Mounts denied: \nThe path /workspaces/vscode-remote-containers-cdk-and-sam/app/lambda/my_function is not shared from the host and is not known to Docker.\nYou can configure shared paths from Docker -> Preferences... -> Resources -> File Sharing.\nSee https://docs.docker.com/desktop/mac for more info.'")
[1310] Failed to execute script __main__

これは、AppStack.template.json内のAppStack-Functionという名前の関数の"Metadata.aws:asset:path"にあるパスをsam local invoke コマンドがlambda関数のファイルを見つけられず発生している。
言い換えると、コンテナ内に存在する/workspaces/vscode-remote-containers-cdk-and-sam/app/lambda/my_functionというディレクトリが、sam local invokeコマンドでコンテナを立てるホストのDocker Engineから見ると存在しない(つまり、ホストのディレクトリに存在しない)ためにこの事象が起こっている。

つまり、コンテナ内でsam local invokeコマンド実行時に、ホストのDocker Engineから見て存在するパスにLambda関数のファイルが存在しなくてはならない。
これは、ホストのソースコードを配置しているディレクトリとコンテナ内のソースコードを配置しているディレクトリを同じ構成にしてしまえば実現でき、この設定をWorkspace Volume Mount の設定で行っている。

sam local invoke 実行時に必要なオプション

当初は上記だけでうまくいくと思っていたが、実際にはsam local invokeコマンドで--container-hostオプションが必要で、筆者のDocker for Mac上のコンテナの場合、以下のコマンドで実行する必要があった。

sam local invoke AppStack-Function --no-event -t ./cdk.out/AppStack.template.json --container-host host.docker.internal

以下のsam local invokeコマンドのオプション説明に、上記に関して記載があった。
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html

これを指定しないと以下のように接続ができずにタイムアウトの出力が出てしまう。

Invoking index.handler (nodejs16.x)
Skip pulling image and use local one: public.ecr.aws/sam/emulation-nodejs16.x:rapid-1.56.0-x86_64.

Mounting /Users/nmemoto/ghq/github.com/nmemoto/vscode-remote-containers-cdk-and-sam/app/lambda/my_function as /var/task:ro,delegated inside runtime container
Timed out while attempting to establish a connection to the container. You can increase this timeout by setting the SAM_CLI_CONTAINER_CONNECTION_TIMEOUT environment variable. The current timeout is 20.0 (seconds).

まとめ

冒頭に挙げた必要な設定を施した、以下のリポジトリをRemote Containersで開く。
https://github.com/nmemoto/vscode-remote-containers-cdk-and-sam

(筆者のDocker for Mac環境では、)CDKプロジェクトのあるディレクトリで、以下コマンド実行することでコンテナ内でLambdaのテスト実行ができる。

$ cdk synth --no-staging

$ sam local invoke AppStack-Function --no-event -t ./cdk.out/AppStack.template.json --container-host host.docker.internal
Invoking index.handler (nodejs16.x)
Image was not found.
Removing rapid images for repo public.ecr.aws/sam/emulation-nodejs16.x
Building image...........................................................................
Skip pulling image and use local one: public.ecr.aws/sam/emulation-nodejs16.x:rapid-1.56.0-x86_64.

Mounting /Users/nmemoto/ghq/github.com/nmemoto/vscode-remote-containers-cdk-and-sam/app/lambda/my_function as /var/task:ro,delegated inside runtime container
"Hello from SAM and the CDK!"END RequestId: e402ec93-bbcd-4e73-aa7e-e170105b3b36
REPORT RequestId: e402ec93-bbcd-4e73-aa7e-e170105b3b36  Init Duration: 0.41 ms  Duration: 107.41 ms     Billed Duration: 108 ms Memory Size: 128 MB       Max Memory Used: 128 MB
GitHubで編集を提案

Discussion