AWS Lambda MicroVMs で軽量なコード検証環境を作ってみた
2026-09-02 | Author : 岡本 秀高 (CircleCI 合同会社)
はじめに
AI が生成したコードや、CI に渡す前のコードを、手元とは分離した環境で実行して検証したい場面があります。microVM を利用したサンドボックス環境を作ることで、高速かつ安全な形でコードを実行する環境が作成できます。
この記事では、2026 年 6 月に発表された AWS Lambda MicroVMs を使って、「コードを渡すと lint・型チェック・テストを実行して結果を返す検証環境」を作ります。単に一度動かすだけでなく、コードを書き換えては検証し直す反復を、イメージの作り直しなしに数秒で回せる構成まで、AWS CLI で手を動かして確認します。
builders.flash メールメンバー登録
builders.flash メールメンバー登録で、毎月の最新アップデート情報とともに、AWS を無料でお試しいただけるクレジットコードを受け取ることができます。
利用するサービス
この記事では AWS Lambda MicroVMs を利用します。これは Firecracker による仮想マシンレベルの分離環境を、スナップショットからの高速な起動とともに提供するコンピューティング基盤です。この記事を読むと、環境 (ランタイムや依存) をイメージに固定し、頻繁に変わる検証対象のコードは起動後に送り込むという分離によって、反復検証を高速化する構成を作れるようになります。
なお AWS Lambda MicroVMs は Arm64 アーキテクチャで動作します。検証用アプリは node:24-alpine をベースに用意しました。
検証用アプリを用意する
コードを受け取って実行するため、POST /exec でコマンドを受け取り、終了コードと標準出力・標準エラーを返す最小限の HTTP サーバー (server.js) を用意します。後でソースを送り込むため、tar.gz を受け取って /app に展開する POST /sync も追加します。
import { exec, spawn } from "node:child_process";
import http from "node:http";
const server = http.createServer((req, res) => {
// コマンドを実行して結果を返す
if (req.method === "POST" && req.url === "/exec") {
let body = "";
req.on("data", (c) => { body += c; });
req.on("end", () => {
const { command } = JSON.parse(body);
exec(command, { cwd: "/app", timeout: 600000 }, (error, stdout, stderr) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
exitCode: error && typeof error.code === "number" ? error.code : 0,
stdout, stderr,
}));
});
});
return;
}
// tar.gz を受け取り /app に展開する
if (req.method === "POST" && req.url === "/sync") {
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
const tar = spawn("tar", ["xzf", "-", "-C", "/app"]);
tar.stdin.end(Buffer.concat(chunks));
tar.on("close", (code) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ exitCode: code, stdout: "", stderr: "" }));
});
});
return;
}
res.writeHead(200);
res.end(JSON.stringify({ status: "ok" }));
});
server.listen(8080, () => console.log("listening on 8080"));
ビルド用の IAM ロールを用意する
イメージ作成時、AWS Lambda がビルドのために Amazon Simple Storage Service (Amazon S3) からコードを取得し、ログを Amazon CloudWatch Logs に書き込みます。この操作を Lambda に許可する IAM ロールを 1 つ用意します。
# 信頼ポリシー trust-policy.json(Lambda がこのロールを引き受けられるようにする)
{ "Version": "2012-10-17", "Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"] }] }
権限ポリシー build-policy.json(Amazon S3 取得と Amazon CloudWatch Logs 書き込みを許可)
# 権限ポリシー build-policy.json(Amazon S3 取得と Amazon CloudWatch Logs 書き込みを許可)
{ "Version": "2012-10-17", "Statement": [
{ "Effect": "Allow", "Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::<your-bucket-name>/*" },
{ "Effect": "Allow",
"Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],
"Resource": "arn:aws:logs:*:*:*" }] }
Microvm ビルド用 IAM ロールの作成とポリシー設定
aws iam create-role --role-name MicrovmBuildRole \
--assume-role-policy-document file://trust-policy.json
aws iam put-role-policy --role-name MicrovmBuildRole \
--policy-name BuildPolicy --policy-document file://build-policy.json
イメージを作って起動する
環境 (Node.js・biome・TypeScript・vitest・依存パッケージ) と server.js だけを含む Dockerfile を用意します。コードを変更するたびにイメージをビルドしなおすのは非効率なため、検証対象のソースは含めません。
FROM node:24-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY biome.json tsconfig.json server.js ./
EXPOSE 8080
CMD ["node", "server.js"]
アプリケーションの ZIP 圧縮と Amazon S3 へのアップロード
zip のルートに Dockerfile が来るように固め、Amazon S3 にアップロードします。この際サブディレクトリに入るとビルドが通らないことに注意しましょう。
zip -r app.zip . -x 'node_modules/*' -x '.git/*'
aws s3 cp app.zip s3://<your-bucket-name>/app.zip
Lambda MicroVM イメージのビルド
create-microvm-image でイメージを作りましょう。ビルドは非同期のため、get-microvm-image で状態が CREATED になるまで確認します。後続のコマンドに渡す --image-identifier には、作成時の名前ではなく応答に含まれる ARN を使います。もし名前を渡すと Invalid ARN format エラーが発生します。
aws lambda-microvms create-microvm-image \
--name agent-sandbox \
--code-artifact '{"uri":"s3://<your-bucket-name>/app.zip"}' \
--base-image-arn arn:aws:lambda:us-east-1:aws:microvm-image:al2023-1 \
--build-role-arn arn:aws:iam::<account-id>:role/MicrovmBuildRole
aws lambda-microvms get-microvm-image --image-identifier <imageArn> --query 'state'
Lambda MicroVM の起動
CREATED になったら run-microvm で起動します。--idle-policy には一定時間アイドルで自動的にサスペンドし、次のリクエストで自動復帰する設定を指定しました。起動直後の状態は PENDING で、この時点ではまだリクエストは通りません。get-microvm で RUNNING になるまで確認します。今回の計測では、RUNNING への到達は初回で約 33 秒、同じイメージからの 2 回目以降は 7 ~ 8 秒でした。
aws lambda-microvms run-microvm \
--image-identifier <imageArn> \
--idle-policy '{"autoResumeEnabled":true,"maxIdleDurationSeconds":900,"suspendedDurationSeconds":300}'
aws lambda-microvms get-microvm --microvm-identifier <microvmId> --query 'state'
MicroVM 接続用認証トークンの発行
応答に含まれる HTTPS エンドポイントへのリクエストには、認証トークンが必要です。create-microvm-auth-token で発行し、X-aws-proxy-auth ヘッダーに載せます。--allowed-ports は "port=8080" の形で指定します(8080 単体では受け付けられません)。
aws lambda-microvms run-microvm \
--image-identifier <imageArn> \
--idle-policy '{"autoResumeEnabled":true,"maxIdleDurationSeconds":900,"suspendedDurationSeconds":300}'
aws lambda-microvms get-microvm --microvm-identifier <microvmId> --query 'state'
これで、MicroVM 内で任意のコマンドを実行し、結果を終了コード付きで受け取れる状態になりました。
コードを起動後に同期する
環境だけをセットアップしたイメージを起動し、/exec で ls /app/src を実行すると src は空でした。ここに、ローカルで固めた tar.gz を先ほどの /sync へ直接 POST してソースを送り込みます。-C agent-sandbox はプロジェクトのルートを指すので、実行場所に応じて読み替えます。
tar czf - -C agent-sandbox --exclude=node_modules --exclude=.git src/ | \
curl -s "https://<endpoint>/sync" \
-H "X-aws-proxy-auth: <token>" \
-H "Content-Type: application/octet-stream" \
--data-binary @-
Content-Type: application/octet-stream を付けた HTTP ボディとして送ると、gzip バイナリも問題なく通り、/app/src に展開されました。今回の計測では、2 ファイル程度のプロジェクトの同期は約 0.9 ~ 1.1 秒、10 MB の tar.gz でも約 1 秒でした (10 MB を超えるサイズは確認していません)。
検証を実行し、反復させる
同期した /app に対して /exec 経由で検証コマンドを実行しました。npx biome ci . (lint)、npx tsc --noEmit (型チェック)、npx vitest run (テスト) は、いずれも終了コード 0 で完了しました。テストの期待値を意図的に壊したソースを同期すると vitest run は終了コード 1 で失敗を返し、戻して再同期すると 0 に復帰しました。結果は終了コードと出力の構造化された形で返ります。 そのうえで、「ソースを変更して再同期し、再検証する」を 3 回連続で実行し、1 回あたりの所要時間を計測しました。
|
回
|
再同期
|
再検証 (vitest run)
|
合計
|
|---|---|---|---|
|
1
|
1071 ms |
1378 ms |
2449 ms |
|
2
|
948 ms |
1540 ms |
2488 ms |
|
3
|
911 ms |
1396 ms |
2307 ms |
いずれも MicroVM の再起動やイメージの再ビルドなしで、約 2.3 ~ 2.5 秒で 1 回分が完了しました。コードを焼き込む方式では変更のたびに約 191 秒かかっていた反復が、環境とコードを分離することで約 2.4 秒になりました。変わるのは tar.gz で送る数百バイト ~ 数 MB のソースだけになるためです。
リソースの削除
検証後に料金が発生し続けないよう、作成したリソースを削除します。MicroVM は稼働中に課金が発生するため、まず終了します。
# MicroVM を終了する
aws lambda-microvms terminate-microvm --microvm-identifier <microvmId>
# MicroVM イメージを削除する
aws lambda-microvms delete-microvm-image --image-identifier <imageArn>
# S3 のオブジェクトとバケットを削除する
aws s3 rm s3://<your-bucket-name>/app.zip
aws s3 rb s3://<your-bucket-name>
# IAM ロールを削除する(先にインラインポリシーを削除する)
aws iam delete-role-policy --role-name MicrovmBuildRole --policy-name BuildPolicy
aws iam delete-role --role-name MicrovmBuildRole
料金はリージョンや利用状況によって異なり、また改定される場合があります。最新の料金は AWS Lambda の料金ページ をご確認ください。
まとめ
この記事では、AWS Lambda MicroVMs で、環境をイメージに固定し、検証対象のコードを起動後に tar.gz で同期する構成を作りました。ソースまでイメージに焼き込む方式では変更のたびに約 191 秒かかっていた反復検証が、環境とコードを分離することで 1 回あたり約 2.4 秒になりました。読者の皆さんは、この分離によって、コードを書き換えながら検証を繰り返す作業を、イメージの作り直しなしに進められるようになります。
なお、各 API のクォータや制限は 2026 年 7 月時点のもので、変更される場合があります。最新の情報は AWS Lambda の公式ドキュメント をご確認ください。
筆者プロフィール
岡本 秀高
CircleCI 合同会社
Senior Field Engineer
AWS や Cloudflare 上へのサーバーレスなアプリ開発を得意とする開発者。
元 Stripe Developer Advocate / AWS Samurai 2017 など、サービスの使い方や活用 Tips を紹介するコンテンツ作成や登壇などを得意とする。
和太鼓・打楽器プレイヤーのヒカセン。