Lambda MicroVMsのIdle Policy(自動サスペンド・レジューム・ターミネート)を検証してみた

Lambda MicroVMsのIdle Policy(自動サスペンド・レジューム・ターミネート)を検証してみた

AWS Lambda MicroVMsのidlePolicyパラメータを使うと、アイドル状態のMicroVMを自動サスペンド・自動ターミネートし、リクエスト着信時に自動レジュームできます。各パラメータの実際の挙動を計測して確認しました
2026.06.24

はじめに

2026年6月22日、AWS Lambda MicroVMsが発表されました。

https://aws.amazon.com/jp/about-aws/whats-new/2026/06/aws-lambda-microvms/

Lambda MicroVMsは、VM分離された実行環境と最大8時間の状態保持を提供する機能です。一方で、MicroVMが起動している間はベースラインの課金が発生します。

そこで重要になるのが、アイドル時の自動ライフサイクル管理を行う idlePolicy です。idlePolicyRunMicrovm API の lifecycleManagement 配下で指定する設定で、アイドル状態のMicroVMを自動でサスペンドし、一定時間後にターミネートできます。また、設定によってはリクエスト着信時に自動レジュームさせることもできます。

idlePolicy は、主に以下の3つのパラメータで構成されます。

パラメータ 説明 設定例
maxIdleDurationSeconds 最後のリクエスト完了から自動サスペンドまでの秒数 60
suspendedDurationSeconds サスペンドから自動ターミネートまでの秒数 120
autoResumeEnabled サスペンド中にリクエストが来た場合に自動レジュームするか true / false

状態遷移は以下のとおりです。

RUNNING ──(idle超過)──► SUSPENDED ──(suspended超過)──► TERMINATED
   ▲                        │
   └──(autoResume+リクエスト)─┘

本記事では、maxIdleDurationSecondssuspendedDurationSecondsautoResumeEnabled の3パラメータについて、実際のステータス遷移やレジューム時間を計測して挙動を確認します。

検証環境

アプリケーション

状態確認に特化したシンプルなFlaskアプリを用意しました。同一アプリをgunicornで8080/9000の2ポートに提供し、フック設定側で9000番を指定しています。

  • GET /status — 内部状態(起動時刻、uptime、リクエスト数、イベントログ)
  • POST /aws/lambda-microvms/runtime/v1/run|suspend|resume — ライフサイクルフック通知を記録
app.py(全文)
import time, logging, sys
from flask import Flask, jsonify, request

app = Flask(__name__)
logging.basicConfig(level=logging.INFO, stream=sys.stdout, force=True)

state = {"boot_time": time.time(), "request_count": 0, "last_request": None, "events": []}

def record(event):
    state["events"].append({"event": event, "time": time.strftime("%Y-%m-%dT%H:%M:%S%z")})
    print(f"[HOOK] {event} at {state['events'][-1]['time']}", flush=True)

@app.before_request
def count():
    state["request_count"] += 1
    state["last_request"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")

# --- Main API (port 8080) ---
@app.route("/status")
def status():
    return jsonify(boot_time=time.strftime("%Y-%m-%dT%H:%M:%S%z", time.localtime(state["boot_time"])),
                   uptime_seconds=round(time.time() - state["boot_time"], 1),
                   request_count=state["request_count"],
                   last_request=state["last_request"],
                   events=state["events"])

@app.route("/time")
def get_time():
    return jsonify(now=time.strftime("%Y-%m-%dT%H:%M:%S%z"), epoch=time.time())

@app.route("/")
def index():
    return jsonify(status="ok", uptime=round(time.time() - state["boot_time"], 1))

# --- Hooks (runtime API paths) ---
@app.route("/aws/lambda-microvms/runtime/v1/ready", methods=["GET", "POST"])
def ready():
    record("ready")
    return jsonify(status="ready"), 200

@app.route("/aws/lambda-microvms/runtime/v1/run", methods=["GET", "POST"])
def run():
    state["boot_time"] = time.time()
    record("run")
    return jsonify(status="running"), 200

@app.route("/aws/lambda-microvms/runtime/v1/suspend", methods=["GET", "POST"])
def suspend():
    record("suspend")
    return jsonify(status="suspending"), 200

@app.route("/aws/lambda-microvms/runtime/v1/resume", methods=["GET", "POST"])
def resume():
    record("resume")
    return jsonify(status="resumed"), 200
Dockerfile
FROM public.ecr.aws/lambda/microvms:al2023-minimal
RUN dnf install -y python3.11 python3.11-pip && dnf clean all && \
    ln -sf /usr/bin/python3.11 /usr/bin/python3 && \
    ln -sf /usr/bin/pip3.11 /usr/bin/pip3
WORKDIR /app
COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8080 9000
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8080", "--bind", "0.0.0.0:9000", "--workers", "1", "--preload", "--access-logfile", "-", "--log-level", "debug"]

MicroVM 起動設定

aws lambda run-microvm \
  --image-arn arn:aws:lambda:ap-northeast-1:123456789012:microvm-image:idle-policy-test \
  --lifecycle-management '{
    "idlePolicy": {
      "maxIdleDurationSeconds": 60,
      "suspendedDurationSeconds": 120,
      "autoResumeEnabled": true
    }
  }'

計測方法

  • get-microvm を10秒間隔でポーリングし、ステータス遷移のタイミングを観測(最大±10秒の観測誤差を含む)
  • 自動レジュームのレイテンシは curltime_total で計測

検証1: 自動サスペンド

maxIdleDurationSeconds=60 を設定し、起動後にリクエストを一切送らず放置しました。

結果

起動から約60秒後にRUNNING → SUSPENDEDへの遷移を検出しました。

経過時間 ステータス
0s RUNNING
30s RUNNING
60s SUSPENDED

設定値どおり60秒で遷移が発生しました(10秒間隔ポーリングのため、50〜60秒の間に遷移)。

検証2: アイドルタイマーリセット

同一設定(maxIdleDurationSeconds=60)で、30秒間隔でリクエストを送り続けました。

結果

30秒間隔で5回(計150秒間)リクエストを送信した結果:

[0s]   リクエスト → RUNNING
[30s]  リクエスト → RUNNING
[60s]  リクエスト → RUNNING  ← 直近リクエストによりアイドルタイマーがリセットされRUNNING維持
[90s]  リクエスト → RUNNING
[120s] リクエスト → RUNNING
[150s] リクエスト停止
[210s] → SUSPENDED          ← 停止から約60秒後

maxIdleDurationSeconds=60 の起算起点は最後のリクエスト完了時点のため、リクエストのたびにアイドルタイマーがリセットされ、RUNNINGが維持されました。リクエスト停止後は、改めて約60秒でサスペンドされました。

検証3: 自動レジューム

結果

SUSPENDED状態のMicroVMにリクエストを送信し、3回計測した結果:

試行 curl time_total
1回目 2.618s
2回目 2.613s
3回目 2.610s

今回の3回の計測ではばらつき8ms以内に収まりました。この条件では、クライアント側のタイムアウトを5秒程度に設定しておけばレジューム完了まで待てる結果でした。

今回確認した範囲では、レジューム後もアプリのメモリ上の状態(リクエストカウンタ、イベントログ)が保持されていました。

$ curl https://xxxxx.lambda-url.ap-northeast-1.on.aws/status
{
  "boot_time": "2026-06-24T01:15:00+0900",
  "uptime_seconds": 312.5,
  "request_count": 8,
  "events": [
    {"event": "ready", "time": "2026-06-24T01:14:55+0900"},
    {"event": "run", "time": "2026-06-24T01:15:00+0900"},
    {"event": "suspend", "time": "2026-06-24T01:16:00+0900"},
    {"event": "resume", "time": "2026-06-24T01:18:35+0900"}
  ]
}

request_count がサスペンド前の値を引き継ぎ、events 配列にもサスペンド前のイベントが残っています。メモリ上のプロセス状態がサスペンド前から引き継がれており、状態が保存・復元される挙動が確認できます。

検証4: 自動ターミネート

SUSPENDED状態のまま放置し、suspendedDurationSeconds=120 の自動ターミネートを確認しました。

結果

SUSPENDEDを検出してから約116秒後にTERMINATEDへの遷移を検出しました。

経過時間(SUSPENDED後) ステータス
0s SUSPENDED
60s SUSPENDED
116s TERMINATED

設定値120秒とほぼ一致しています。TERMINATEDになったMicroVMに対するリクエストはエラーとなり、以降は復帰できません。

検証5: autoResumeEnabled=false

autoResumeEnabled=false で新しいMicroVMを起動し、SUSPENDED状態でのリクエスト挙動を確認しました。

結果

SUSPENDED状態のMicroVMにリクエストを送信したところ、HTTP 502 (Bad Gateway) が即座に返却されました。

$ curl -o /dev/null -s -w "%{http_code} %{time_total}s\n" https://xxxxx.lambda-url.ap-northeast-1.on.aws/status
502 0.058s

タイムアウトではなく即時に502が返り、MicroVMはSUSPENDEDのまま自動レジュームは発生しませんでした。

手動で resume-microvm を実行したところ、ステータスがRUNNINGに復帰し、リクエストが正常に処理されることを確認しました。

$ aws lambda resume-microvm --microvm-id microvm-xxxxx
$ curl https://xxxxx.lambda-url.ap-northeast-1.on.aws/status
{"boot_time":"2026-06-24T01:30:00+0900","request_count":5,...}

注意事項・Tips

フックパスは full path が必要だった

今回の構成では、フック設定で短縮パス(/ready/run 等)を指定してもフック呼び出しが成功せず、以下のfull pathを指定する必要がありました。

/aws/lambda-microvms/runtime/v1/ready
/aws/lambda-microvms/runtime/v1/run
/aws/lambda-microvms/runtime/v1/suspend
/aws/lambda-microvms/runtime/v1/resume

フックポートへの接続挙動

gunicornのアクセスログを確認したところ、フックポート(9000番)に対してまずTLS接続を試行し、失敗後にプレーンHTTPで接続される挙動を観測しました。アプリ側でTLS対応していなくても動作します。

ただし、TLS接続リトライ分の遅延が加わる可能性もあります。

readyフック必須

run / suspend / resume フックを使う場合、ready フックも合わせて設定する必要があります。ready フックなしではMicroVMイメージのビルドが完了しません。

まとめ

今回の検証では、Lambda MicroVMsの idlePolicy により、自動サスペンド・自動ターミネートがおおむね設定値に沿って動作することを確認できました。自動レジュームも、3回の計測では約2.6秒で復帰し、大きなばらつきは見られませんでした。

idlePolicy は、「使っていない間は止めて、リクエストが来たら復帰する」というコスト最適化パターンを、業務ロジックの変更なく実現できる仕組みです。採用時は、サスペンド状態からの初回リクエストで発生するレジューム時間をユーザー体験として許容できるかが判断ポイントになります。今回の検証条件では約2.6秒だったため、これを許容しにくい場合は、maxIdleDurationSeconds を大きくしてRUNNING状態を維持する、または定期的にリクエストを送ってアイドルタイマーをリセットする構成が代替手段になります。

参考リンク

https://docs.aws.amazon.com/lambda/latest/dg/lambda-microvms-guide.html

https://aws.amazon.com/blogs/aws/run-isolated-sandboxes-with-full-lifecycle-control-aws-lambda-introduces-microvms


コスト最適化、打ちっぱなしで元通りになっていませんか

タグ付けも不要リソースの棚卸しも、施策は打てる。でも続ける仕組みがなければ、コストは数か月でじわじわ戻る。一度きりで終わらせず、FinOpsを組織に定着させる=CCoEの役割。最適化を回し続ける進め方を、無料資料にまとめました。

CCoE総合支援

FinOpsを定着させる資料をもらう

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事