😭

[AWS SAM]Devcontainer上でAWS-SAMを使用する際に躓いたこと

2024/07/18に公開

概要

こんにちは。
インターンシップでAWSを触り始めて頭を痛めているたくみです。

現在aws-sam(以降はsamと表記)とDevcontainerを使用しています。その中で躓いた点があったのでまとめます。

環境

Ubuntu: 20.04.2 LTS(HyperV)
Docker: 20.10.7
docker-compose: 1.25.0
SAM CLI: 1.120.0
Devcontainer: v0.238.3 (色々あって古いバージョンを使っています)

躓いたこと一覧

  • その1. どうやってDevcontainer上にDockerのコンテナを立てるの?
  • その2. ゲートウェイに対してアクセスできないよ!
  • その3. アクセスすると、タイムアウトが出るよ!
  • その4. なぜlambdaコンテナ内に実行させたいファイルがボリューム付けされないの?

具体的な内容と解決方法

その1.どうやってDevcontainer上にDockerのコンテナを立てるの?

内容

実はsamさん、lambdaをDockerのコンテナ上で動かしています。

ここで問題が発生します。DevcontainerもDockerのコンテナ上で動いているものなので、Devcontainer上でsamを使用すると、Docker上でDockerを起動することになります。コンテナのイメージ図は以下の通りです。

さて、Docker上のDockerなんてあんまり聞いたこと無いですよね。図を見る限りでもコンテナの上にコンテナが乗っかっていてなんだか紛らわしいです...

解決法

解決方法としては2つ存在します。(僕は1つを試したのでもう一つがうまくいくかはわかりません)

  • 1つ目は、dind(docker-in-docker)というものです。
    これは名前の通り、Dockerの中にDockerをインストールして、ローカルのDockerとは別デーモンで動かすやり方です。まさに、上図のDevcontainer上のsamコンテナ図を再現するイメージですね。
  • 2つ目は、dood(docker-outside-of-docker)というものです。
    これは、ローカルのDockerデーモンとDevcontainer上のDockerデーモンを共有するやり方です。イメージ図は以下の通りです。

コンテナ上のコンテナだとややこしくなりそうといった理由から、僕は2つ目のdoodを採用しました。

具体的な解決

さて、doodという機能があるのはわかりました。どう実装しましょう。
ただ単純に考えればdocker-compose.yamlvolumesdocker.sockをボリューム付けすれば行けそうです。

docker-compose.yaml
volumes:
    - /var/run/docker.sock:/var/run/docker.sock

このままだと、dockerと叩くとコマンドが見つからないと怒られるため、Dockerfiledocker.iodocker-composeをインストールしてあげましょう。

Dockerfile
RUN apt-get update && apt-get install -y docker.io docker-compose

これでDevcontainer上でdockerコマンドが叩けて、docker psと入力するとローカル上に立っているコンテナの一覧が取得できるはずです。

これにて、その1は一件落着!

補足

実は、Devcontainerにはdoodを設定してくれる機能がありました。
僕の場合はバージョンが低いので試していませんが、興味ありましたら試してみてください。
https://scrapbox.io/not75743/【Devcontainer】featureでdood(docker_outside_of_docker)を試す

その2. ゲートウェイに対してアクセスできないよ!

「よし!Dockerも使用できるし完璧!!」
と思ってsam local start-apiと叩いてアクセスすると、様々なエラーが発生...
その1つ目として、「ゲートウェイに対してアクセスできない!」が有りました。

内容

samのログが動かず、TypeError: Network request failed(Next.jsの場合)が発生。
これがなぜ起こるのかというと、そもそもsamが起動しているゲートウェイにアクセスできていないため発生しています。samのログを見ると、samはホストが127.0.0.1のみの通信を受け付けており、その他のホストの通信を受け付けていません。
そのため、コンテナ外部からの通信は127.0.0.1がホストではないためアクセスできないエラーが発生しました。

解決法(具体的な解決)

解決方法としては、samのコマンドライン引数に--host 0.0.0.0を追加すればよいです。これは0.0.0.0(全てのホスト)からのアクセスを受け付けるという意味です。こうすればコンテナ外部からでもどこからでもアクセスすることができます。

sam local start-api --host 0.0.0.0

その3.アクセスすると、タイムアウトが出るよ!

内容

samのログが動いて、

Lambda function '...' is already running
Timed out white 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が起動している部分はどこの部分でしょう!
Devcontainerの部分ですよね。では、samはどこにコンテナを探しに行くでしょう。おそらくデフォルトではDevcontainer上にコンテナが立っていると信じ込んで探しそうですよね。
あれ?でも実際にコンテナが立っているのはローカル上ですよね。
そうなんです。Devcontainer上のコンテナをどんだけ探してもコンテナは存在しません。
そのため、タイムアウトエラーが出ました。

解決法

解決法としては、ローカル上のコンテナを見に行ってあげるように修正すればよいでしょう。
samには、

  • コンテナのホストを指定するcontainer-host
  • バインドするネットワークインターフェースのIPアドレスを指定するcontainer-host-interface

というコマンドライン引数が存在します。
container-hostには、ローカルのホスト名を、container-host-interfaceには、全てのネットワークインターフェースにバインドするように0.0.0.0を指定すればよいでしょう。

具体的な解決

「さて、やることはわかった。どうやってホストのIPアドレスをDevcontainer内で指定しよう...」
そこら辺はちゃんとDockerさんに考えがありました。docker-compose.yamlに、以下を追加すれば、host.docker.internalでローカルのホスト名を使用できます。

docker-compose.yaml
extra_hosts:
  - host.docker.internal:host-gateway

準備は完了したので、あとはコマンドライン引数に与えてあげるだけです。

sam local start-api --host 0.0.0.0 --container-host host.docker.internal --container-host-interface 0.0.0.0

その4. なぜlambdaコンテナ内に実行させたいファイルがボリューム付けされないの?

やっとここまでこれました。コンテナも実行できて、もうエラーなんて無いよねと思ったそこのあなた。爪が甘いですね😏

内容

samがlambdaコンテナにアクセスしたときに、

"Error: Cannot find module 'index'

と表示されてしまいました。なんでだ...ちゃんと.aws/buildの中にファイルがあるはずなのに。
samはlambdaコンテナの/var/taskに実行ファイルをボリューム付けするらしいです。それを見てみると、ボリューム付けされているはずなのにファイルが存在しません...

解決法

これは、Devcontainerのworkspace(例えば/app)と、ローカルのworkspaceが一致していないためボリューム付けされてもファイルが存在しませんでした。
「どういうこと??」
となっているでしょう。
今回動かしているDockerはローカルのものです。したがって、lambdaコンテナなどはローカルのコンテナになります。さて、今/var/taskとボリューム付けされているディレクトリってどれになるのでしょうか。
Devcontainerのworkspaceになります。
ということはlambdaコンテナにボリューム付けされているディレクトリは/appになるはずですよね。でもDockerはローカルで動いているので、/appと指定されてしまうとDevcontainer上ではなく、ローカル上で/appを探してしまいますよね。ローカル上での/appは全くの別物になってしまうため、モジュールが見つからないエラーが発生しました。
そのため、Devcontainerのworkspaceとローカルのworkspaceをあわせてあげることで治りそうです。

具体的な解決

devcontainer.jsonでworkspaceをローカルのworkspaceに指定します。

devcontainer.json
"workspaceFolder": "${localWorkspaceFolder}",

そして、docker-compose.yamlを編集してDevcontainerのworkspaceとローカルのworkspaceをボリューム付けします。

docker-compose.yaml
volumes:
    - $PWD:$PWD

以上で動くようになると思います!

最後に

以上でDevcontainer上でaws-samが実行できるようになったと思います。
ちなみに、このエラーを解消するために結構時間かけたのは内緒🤫

よいDevcontainer & samライフを!

ユニフォームネクスト株式会社

Discussion