AWS Serverless Application Model(SAM)のローカル実行時のコンテナ実行環境として、mac上のLinux仮想環境にインストールしたDocker エンジンを使用する手順を検証します。
なぜ、このような環境が必要か
- 手元mac端末でvscodeとAmazon Q develoerを使ったlambda開発がしたい
- サクサク開発/デバッグするためSAMのローカル実行を使用したい
- SAMのローカル実行にはコンテナエンジンが必要だが諸事情によりmacで直接稼働するDocker(Docker Desktop)は使用したくない
環境
- 手元mac端末
- m1 macbook air sonoma 14.7.1
- 仮想環境
- UTM 4.5.4
- 仮想環境用 Linux
- ubuntu 24.04.1 LTS(ARM64)
- Docker
- Docker CE 5.27.4.0
- SAM CLI
- 1.125.0
実現したいアーキテクチャ
全体イメージ図です。
今回検証するのは「macのターミナルでsam invokeを実行すると、仮想環境LinuxのDockerでLambdaが実行される」の部分だけです。
図中に「sam code」とある箱の部分ですが、後述するようにmacでSAMを実行して作成されるファイルを、Dockerエンジンが稼働するLinuxから直接参照できるようにする必要があります。NFSやSambaなど参照のやり方はいろいろありますが、今回はUTMの共有ディレクトリ機能を使用しています。
検証手順
SAM CLI、UTMのインストール
SAM CLIのインストール、UTMのインストールに関しては一般的な話のため詳細割愛します。
UTMはhomebrewでインストールしました。
UTM仮想マシン作成
母艦となるmacのディスク残量が厳しいため、ubuntuを最小構成でインストールします。Dockerを動かすだけなのでDesktop環境など追加のパッケージは不要です。
UTMの仮想マシンは「仮想化」で作成します。インストールするLinuxはARM版が必要です。
後段でsamの実行ディレクトリとなるディレクトリを含むようにパスを指定します。
私はvscodeのワークスペース用に作ったフォルダを指定しました。
Ubuntu 導入
導入オプションとしてUbuntu server(minimized)を選択します。
ネットワークやディスク構成はデフォルトのまま進めます。
ログインユーザとしては"user01"を作成しました
パスワードなどは適宜設定します。
インストーラで選択できるDockerはインストールしませんでした。Dockerは個別にインストールします
インストールが完了したらインストールイメージのISOをドライブから外して再起動します。
Linuxの初期導入が完了しました。
Linuxの設定
Docker CEインストール
Docker CEをインストールします。手順は公式手順に従います。
https://docs.docker.jp/engine/installation/linux/docker-ce/ubuntu.html
ログインユーザuser01のDokcerグループへの追加
$ usermod -aG docker user01
設定ファイル編集用にvimインストール
$ apt-get -y install vim
Dockerの設定
Dockerにリモートのmacから接続できるように設定します。設定ファイルは/usr/lib/systemd/system/docker.service
です。
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecStart
の行に-H tcp://(linux仮想マシンのIPアドレス)
を追記します。
私の環境では192.168.64.12でした。
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://192.168.64.12 --containerd=/run/containerd/containerd.sock
カーネルモジュールの設定
UTMの共有フォルダは9pという仕組みを使っているようです。
https://docs.getutm.app/guest-support/linux/#virtfs
9pのマウントに必要な機能はUbuntuの場合カーネルモジュールになっていました。
UTMの共有フォルダを自動マウントできるように、カーネルモジュールの自動ロード設定をします。
設定ファイルは/etc/modules-load.d/modules.confです。
# /etc/modules is obsolete and has been replaced by /etc/modules-load.d/.
# Please see modules-load.d(5) and modprobe.d(5) for details.
#
# Updating this file still works, but it is undocumented and unsupported.
9pnet_virtio
9p
9pnet_virtio
、9p
を追記します。
UTMの共有フォルダ自動マウント
UTMの共有フォルダのマウントパスを作成します。ここは何でもよいですが、/mnt/utmshareとしました。
$ mkdir /mnt/utmshare
OS起動時の自動マウントを設定します。
以下を/etc/fstab
に追記します。
share /mnt/utmshare 9p trans=virtio,version=9p2000.L,rw,_netdev,nofail 0 0
設定が終わったら再起動します。
設定確認
dockerdが設定した引数(-H tcp://192.168.64.12
)で起動していることを確認します。
user01@ubuntu01:~$ ps -ef | grep docker
root 706 1 0 03:41 ? 00:00:00 /usr/bin/dockerd -H fd:// -H tcp://192.168.64.12 --containerd=/run/containerd/containerd.sock
user01 1043 807 0 03:41 pts/0 00:00:00 grep --color=auto docker
UTMの共有ディレクトリ/mnt/utmshare
がマウントされていることを確認します
user01@ubuntu01:~$ df
Filesystem 1K-blocks Used Available Use% Mounted on
tmpfs 200340 1036 199304 1% /run
efivarfs 256 25 232 10% /sys/firmware/efi/efivars
/dev/mapper/ubuntu--vg-ubuntu--lv 5813344 2639876 2857260 49% /
tmpfs 1001684 0 1001684 0% /dev/shm
tmpfs 5120 0 5120 0% /run/lock
/dev/vda2 1768056 100636 1559288 7% /boot
/dev/vda1 549804 6508 543296 2% /boot/efi
share 239362432 192441088 46921344 81% /mnt/utmshare
tmpfs 200336 12 200324 1% /run/user/1000
macの設定
Linux側の準備ができたので、mac側の設定を行います。
DOCKER_HOSTの設定
まずSAM CLIを実行するターミナルで以下を設定します。
export DOCKER_HOST=(Linux仮想マシンのIPアドレス):2375
私の環境ではLinux仮想マシンのIPアドレスは192.168.64.12
なので、以下のように設定します。
% export DOCKER_HOST=192.168.64.12:2375
SAMのテンプレート作成
samで使用するディレクトリで以下を実行し、SAMの必要ファイルを作成します。
m1 macのarmアーキテクチャの仮想環境で動かすので、-a arm64
が必要になります。
% sam init -a arm64
今回は検証なので、Hello Worldのサンプルを使用します。言語はPythonとし、サンプルのコードも特に変更しません。
sam init実行結果
% sam init -a arm64
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - DynamoDB Example
16 - Machine Learning
Template: 1
Use the most popular runtime and package type? (Python and zip) [y/N]:
Which runtime would you like to use?
1 - dotnet8
2 - dotnet6
3 - go (provided.al2)
4 - go (provided.al2023)
5 - graalvm.java11 (provided.al2)
6 - graalvm.java17 (provided.al2)
7 - java21
8 - java17
9 - java11
10 - java8.al2
11 - nodejs20.x
12 - nodejs18.x
13 - nodejs16.x
14 - python3.9
15 - python3.8
16 - python3.12
17 - python3.11
18 - python3.10
19 - ruby3.3
20 - ruby3.2
21 - rust (provided.al2)
22 - rust (provided.al2023)
Runtime: 16
What package type would you like to use?
1 - Zip
2 - Image
Package type: 1
Based on your selections, the only dependency manager available is pip.
We will proceed copying the template using pip.
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]:
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]:
Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]:
Project name [sam-app]:
-----------------------
Generating application:
-----------------------
Name: sam-app
Runtime: python3.12
Architectures: arm64
Dependency Manager: pip
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ビルド実行
sam build
を実行します
% sam build
(略)
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
"Built Artifacts"のディレクトリ配下にテンプレートやアプリケーションコード(app.py)が出力されます。このパスをローカル実行時の引数で使用します。
% ls .aws-sam/build
HelloWorldFunction template.yaml
% ls .aws-sam/build/HelloWorldFunction
__init__.py charset_normalizer-3.4.0.dist-info requirements.txt
app.py idna urllib3
certifi idna-3.10.dist-info urllib3-2.2.3.dist-info
certifi-2024.8.30.dist-info requests
charset_normalizer requests-2.32.3.dist-info
SAM ローカル実行
ローカル実行のコマンドを実行します
sam local invoke --container-host 192.168.64.12 --container-host-interface 0.0.0.0 --docker-volume-basedir /mnt/utmshare/test01/sam-app/.aws-sam/build
--docker-volume-basedir
には 「Linux仮想マシン側から見えるBuilt Artifactsのディレクトリ」 を指定します。samを実行するmac側のディレクトリではないことに注意です。
--container-host
と--container-host-interface
はローカルマシンで稼働していないDockerを使用する場合に必要なようです。
% sam local invoke --container-host 192.168.64.12 --container-host-interface 0.0.0.0 --docker-volume-basedir /mnt/utmshare/test01/sam-app/.aws-sam/build
Invoking app.lambda_handler (python3.12)
Local image was not found.
Removing rapid images for repo public.ecr.aws/sam/emulation-python3.12
Building image................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Using local image: public.ecr.aws/lambda/python:3.12-rapid-arm64.
Mounting /mnt/utmshare/test01/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
START RequestId: 12113ae4-b112-4d6a-853d-0e9564d2bfed Version: $LATEST
END RequestId: 21972317-d3d8-41c6-abf8-b1ec673ee401
REPORT RequestId: 21972317-d3d8-41c6-abf8-b1ec673ee401 Init Duration: 0.02 ms Duration: 54.31 ms Billed Duration: 55 ms Memory Size: 128 MB Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}
無事仮想環境LinuxのDockerを使ってローカル実行のLambdaが実行されました。
Linux側のdocker ps
でコンテナが実行されていることが確認できます。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1c4533c23445 public.ecr.aws/lambda/python:3.12-rapid-arm64 "/var/rapid/aws-lamb…" 1 second ago Up Less than a second 0.0.0.0:6708->8080/tcp distracted_joliot
最後に
本稿では、macからリモートのDockerを使用してSAMローカル実行を行う手順を検証しました。
今回は検証なので簡易に実装できる方法として仮想環境のLinuxを使用しましたが
同様の手順で別物理PCで稼働しているDocker環境を仕様し、コードの共有にsamba/NFSを使ったSAMローカル実行も可能と考えます。
同じようなケースで困っていることは少ないかもしれませんが、ぜひお役立てください。