🏗️

Linux上に開発コンテナ + SAM でLambdaの開発環境を構築する

2023/07/26に公開

概要

AWS SAMでLambdaの関数を作成する際に開発コンテナ上に環境を構築をしたものの、躓きどころがあったため備忘録としてまとめています。

この記事で書くこと

  • 開発コンテナのターミナルで$ sam local invokeするための基本的な設定
  • 環境構築をする上でつまづいたポイントと解決方法

環境

  • ホストOS: Ubuntu22.04.2LTS
  • エディタ: VSCode
  • Lambdaランタイム: Go 1.x

devcontianerを用いた環境構築

以下、構築した手順と躓いたポイントを記載しますが、最終的な状態はリポジトリにPushしています。
https://github.com/Sut103/sam-on-devcontainer

基本的なdevcontainer.jsonの作成

最初にプロジェクトのルートディレクトリに開発コンテナを起動するための設定ファイルを作成します。
手元ではVSCodeのコマンドパレットからNew Devcontainerを選択して生成しましたが、手動で作成しても問題ありません。

作成したファイルは以下の状態です。

.devcontainer/devcontainer.json
{
	"name": "Go",
	"image": "mcr.microsoft.com/devcontainers/go:0-1",
	"features": {
		"ghcr.io/devcontainers/features/aws-cli:1": {},
		"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
		"ghcr.io/customink/codespaces-features/sam-cli:1": {}
	}
}
  • mcr.microsoft.com/devcontainers/go:0-1
  • ghcr.io/devcontainers/features/aws-cli:1
    • 開発コンテナ内にaws cliをインストール
  • ghcr.io/customink/codespaces-features/sam-cli:1
    • 開発コンテナ内にsam cliをインストール
  • ghcr.io/devcontainers/features/docker-outside-of-docker:1
    • 開発コンテナ内から兄弟コンテナを作成、操作できるdoodの設定を追加
    • sam localコマンドでのエミュレートにコンテナを使用するため

sam アプリケーションの生成

起動した開発コンテナのターミナルで$ sam initコマンドを実行し、アプリケーションのテンプレートを作成します。
ここでは単一のlambda関数で構成され、シンプルなレスポンスの返るhello-worldテンプレートを選択しました。ランタイムはgo1.xを指定しています。

$ sam init

# 〜対話インターフェース部分は省略〜

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: go1.x
    Architectures: x86_64
    Dependency Manager: mod
    Application Template: hello-world
    Output Directory: .
    Configuration file: sam-app/samconfig.toml
    
    Next steps can be found in the README file at sam-app/README.md
        

Commands you can use next
=========================
[*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-app && sam validate
[*] Test Function in the Cloud: cd sam-app && sam sync --stack-name {stack-name} --watch


SAM CLI update available (1.93.0); (1.92.0 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

躓きどころと変更した点

この時点では開発コンテナからlambdaのローカル実行ができませんでした。以下、ローカル実行ができるまでに変更した点を記載します。

変更1: host.docker.internal

生成したテンプレートをbuildしてlambdaを実行してみます。

$ cd sam-app/
$ sam build
$ sam local invoke

samはlambdaのエミュレートのためにコンテナへ接続しますが、以下の通りタイムアウトになりました。

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).       

samはデフォルトで、自身が実行されている環境(開発コンテナ)のlocalhostでコンテナに接続しようとします。しかし、コンテナの操作にdoodを用いているため実際にコンテナが実行されるのは開発コンテナのホストされているOS側であり、そちらに接続させる必要がありました。

まず、開発コンテナからホストOSのIPアドレスを簡単に扱えるようにhost.docker.internalを設定します。開発コンテナの作成時にhost.docker.internalを追加するようにdevcontainer.jsonにdockerコマンドの--add-hostオプションを追加しました。

devcontainer.json
{
	"name": "Go",
	"image": "mcr.microsoft.com/devcontainers/go:0-1",
	"features": {
		"ghcr.io/devcontainers/features/aws-cli:1": {},
		"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
		"ghcr.io/customink/codespaces-features/sam-cli:1": {}
	},

	"runArgs": ["--add-host=host.docker.internal:host-gateway"]
}

開発コンテナのリビルド後に/etc/hostshost.docker.internalとしてホストOSのIPアドレスが追加されている事が確認できます。

$ cat /etc/hosts | grep host.docker.internal
172.17.0.1      host.docker.internal

変更2: workspace

改めて$ sam local invokeを実行します。
ここでは変更1で追加したhost.docker.internalをオプションで指定して実行しています。

$ cd sam-app/
$ sam local invoke --container-host host.docker.internal --container-host-interface host.docker.internal

タイムアウトは発生せず、コンテナに接続できているように見えますが新たにエラーが発生しています。

        SAM CLI now collects telemetry to better understand customer needs.

        You can OPT OUT and disable telemetry collection by setting the
        environment variable SAM_CLI_TELEMETRY=0 in your shell.
        Thanks for your help!

        Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

Invoking hello-world (go1.x)                                                                                                                                                                                                
Local image is up-to-date                                                                                                                                                                                                   
Using local image: public.ecr.aws/lambda/go:1-rapid-x86_64.                                                                                                                                                                 
                                                                                                                                                                                                                            
Mounting /workspaces/sam-on-devcontainer/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container                                                                                      
START RequestId: 067df06e-cb09-488b-ad53-2fa21df8d20b Version: $LATEST
fork/exec /var/task/hello-world: no such file or directory: PathError
null
END RequestId: 067df06e-cb09-488b-ad53-2fa21df8d20b
REPORT RequestId: 067df06e-cb09-488b-ad53-2fa21df8d20b  Init Duration: 0.11 ms  Duration: 5.40 ms       Billed Duration: 6 ms   Memory Size: 128 MB     Max Memory Used: 128 MB
{"errorMessage":"fork/exec /var/task/hello-world: no such file or directory","errorType":"PathError"}
SAM CLI update available (1.93.0); (1.92.0 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

samはbuildした実行ファイルをコンテナにマウントして実行しますが、これがうまく行っていませんでした。doodでボリュームをマウントする際、マウント元は開発コンテナではなくホストOSになるようです。
samは開発コンテナ上のパスである/workspaces/sam-on-devcontainer/sam-app/.aws-sam/build/HelloWorldFunctionをマウントしようとしていますが、ホストOS上ではパスが異なるため空のディレクトリが作成されてマウントされていました。
開発コンテナとホストOSでのパスを同一にすることで問題を回避することができるそうなので、workspaceのパスを変更して対処します。

{
	"name": "Go",
	"image": "mcr.microsoft.com/devcontainers/go:0-1",
	"features": {
		"ghcr.io/devcontainers/features/aws-cli:1": {},
		"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
		"ghcr.io/customink/codespaces-features/sam-cli:1": {}
	},

	"runArgs": ["--add-host=host.docker.internal:host-gateway"],

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

開発コンテナのリビルド後、改めて$ sam local invokeすると正常なレスポンスが確認できました。

$ cd sam-app/
$ sam local invoke --container-host host.docker.internal --container-host-interface host.docker.internal

        SAM CLI now collects telemetry to better understand customer needs.

        You can OPT OUT and disable telemetry collection by setting the
        environment variable SAM_CLI_TELEMETRY=0 in your shell.
        Thanks for your help!

        Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

Invoking hello-world (go1.x)                                                                                                                                                                                                
Local image is up-to-date                                                                                                                                                                                                   
Using local image: public.ecr.aws/lambda/go:1-rapid-x86_64.                                                                                                                                                                 
                                                                                                                                                                                                                            
Mounting /home/sut103/dev/sam-on-devcontainer/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container                                                                                 
START RequestId: a8671a52-7529-4f26-8e19-342c69fc8527 Version: $LATEST
END RequestId: a8671a52-7529-4f26-8e19-342c69fc8527
REPORT RequestId: a8671a52-7529-4f26-8e19-342c69fc8527  Init Duration: 0.15 ms  Duration: 13.10 ms      Billed Duration: 14 ms  Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode":200,"headers":null,"multiValueHeaders":null,"body":"Hello, world!\n"}
SAM CLI update available (1.93.0); (1.92.0 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

おわりに

本記事ではシンプルな開発コンテナとsamの基本的な環境構築について記載しました。
手元ではさらにdocker-composeの使用やdynamodb-localとの接続も試していたため、そちらも別途まとめたいと思います。

参考など

https://zenn.dev/v2n48gv92hp6n/scraps/f0796c23aee1b6
https://zenn.dev/kun432/scraps/72063f97575b71#comment-3e60a1f24774f5
https://zenn.dev/saitom_tech/articles/aws_sam_devcontainer_local
https://kazuhira-r.hatenablog.com/entry/2022/05/21/152825

Discussion