0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS Lambda を深掘りする(1) Lambda の handler はどのように呼び出されるのか、Lambda Runtime とは何か、どのように動作するのか

0
Posted at

AWS Lambda は最も有名なサーバーレスサービスとして、サーバーの管理が不要でシンプルに使えることで知られています。handler コードを書いて AWS Lambda にアップロードするだけで、さまざまな機能を簡単に実現できます。
しかし、こんな疑問を持ったことはありませんか?

  • handler は具体的にどのように呼び出されるのか?
  • Lambda の各種メカニズムの裏側はどうなっているのか?
  • そして、異なるプログラミング言語の Lambda をカスタマイズすることは可能なのか?

本シリーズの記事では、基礎から応用まで段階的に Lambda の詳細を深掘りしていきます。

Lambda handler の魔法

以下は非常にシンプルな Lambda handler の例です。それぞれ異なるプログラミング言語で書かれていますが、いずれも同じ処理を行っています:'hello world' を返すことです。

NodeJS の例

// index.js
export async function handler(event) {
  return 'hello world';
};

Python の例

# lambda_function.py
def lambda_handler(event, context):
    return 'hello world'

これらの Lambda 関数を invoke すると、handler が自動的に呼び出されます。これは handler の外側で独立して handler の呼び出しを処理するメカニズムが存在することを意味します。同時に、どのプログラミング言語で書かれていても Lambda はこれらの呼び出しを正しく処理できます。これは Lambda にはすべての言語に適用される汎用的な handler 処理メカニズムがあることを意味します。

そのメカニズムとは何でしょうか?それが AWS Lambda Runtime です。

bootstrap:すべての始まり

まず、Lambda にはコールドスタートとウォームスタートの区別があることを理解しておきましょう。

Lambda コールドスタート:リクエストが初めて Lambda に到達した場合、または Lambda の instance 数が現在のリクエスト需要を満たせない場合、Lambda は新しい instance を起動してリクエストを処理し、自動的にスケールアウトします(新しいサーバーを起動するのに似ていますが、Lambda の instance は非常に軽量でほぼ瞬時に起動できます)。これが Lambda のコールドスタートです。

Lambda ウォームスタート:Lambda instance がリクエストの処理を完了してアイドル状態になると、次のリクエストを待つ状態に入ります。この時に新しいリクエストが来ると、Lambda instance はすぐにリクエストを処理でき、新しい instance を起動する必要がありません。これがウォームスタートです。

用語説明:AWS 公式ではこれを通常「実行環境」(execution environment)と呼び、「instance」とは呼びません。本記事では説明の便宜上、以降は実行環境を instance と略称し、両者は本記事の文脈では同じものを指します。

Q:Lambda がコールドスタートする際、処理フローに入るために何らかのプログラムを実行する必要がありますが、最初に何のコマンドまたはプログラムを実行するのでしょうか?
A:答えは bootstrap です。

bootstrapカスタマイズ可能な実行プログラムです。実行可能なバイナリファイル、bash スクリプト、またはその他の実行可能なプログラムのいずれでも構いません。Lambda が新しい instance を起動するたびに、直ちに bootstrap が呼び出されます。

それでは、bootstrap をカスタマイズして説明しましょう。

まず、テスト用の Lambda を作成します。作成画面の Runtime で Amazon Linux 2 を選択してください。これにより Lambda の bootstrap をカスタマイズできます。

image.png

次に、ローカルで Lambda に必要なファイルを作成します。

mkdir bootstrap-test
cd bootstrap-test
touch bootstrap
chmod +x bootstrap

そして bootstrap-test/bootstrap ファイルを開き、以下の内容を記述します。

#!/bin/sh
set -euo pipefail

echo "bootstrap is executed!"

ここで bootstrap は単なる bash スクリプトであることがわかります。作成後に chmod +x bootstrap で実行権限を付与し、実行されると bootstrap is executed! と出力するだけです。

次にディレクトリ bootstrap-testzip -r ../bootstrap-test.zip . を実行して zip パッケージを作成し、先ほど作成した Lambda 関数にアップロードします(便宜上、ここでは AWS マネジメントコンソールから直接アップロードします)。
![[Pasted image 20260212130100.png]]

コンソールの Test ボタンをクリックして Lambda 関数を実行すると、以下の実行ログが取得できます。

......

Function Logs:
bootstrap is executed!
INIT_REPORT Init Duration: 2.39 ms	Phase: invoke	Status: error	Error Type: Runtime.ExitError
START RequestId: 38bf088e-bb1d-4666-82d6-f9ad57b9ec4c Version: $LATEST
RequestId: 38bf088e-bb1d-4666-82d6-f9ad57b9ec4c Error: Runtime exited without providing a reason
Runtime.ExitError
END RequestId: 38bf088e-bb1d-4666-82d6-f9ad57b9ec4c
REPORT RequestId: 38bf088e-bb1d-4666-82d6-f9ad57b9ec4c	Duration: 59.29 ms	Billed Duration: 60 ms	Memory Size: 128 MB	Max Memory Used: 3 MB

Request ID: 38bf088e-bb1d-4666-82d6-f9ad57b9ec4c

Lambda には具体的な機能を実装していないため実行は失敗しましたが、ログの最初に出力されている bootstrap is executed! に注目してください。カスタマイズした bootstrap が instance 起動後に最優先で呼び出されたことがわかります。
大まかなフローは以下の通りです。

Untitled diagram-2026-02-12-155128.png

ここまでで bootstrap が呼び出されることがわかりました。では、次にリクエストデータをどのように受け取り処理するのでしょうか?
ここで Lambda Runtime API が関わってきます。

Lambda Runtime API

AWS Runtime API は本質的に非常にシンプルないくつかの API であり、bootstrap がこれらの API を呼び出すことで Lambda の実行を処理・制御します。
上記の bootstrap スクリプトの例を続けます。

#!/bin/sh
set -euo pipefail
echo "bootstrap is executed!"

RESPONSE=$(curl -s "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
echo "The event data is: $RESPONSE"

今回の bootstrap スクリプトでは curl を使って特別な API を呼び出し、そのレスポンスを出力しています。

  • http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next
    この API の機能は非常にシンプルで、呼び出すとユーザーが今回 Lambda にリクエストした event データを取得できます。

このファイルを再度パッケージ化して zip -r ../bootstrap-test.zip . Lambda にアップロードし、実行してみます。今回も便宜上、コンソールで直接テストします。

image.png

保存してから Test をクリックすると、以下の Lambda 実行ログが取得できます。

bootstrap is executed!
START RequestId: 0b2613f3-49b4-470f-b2f0-20ec523a40f1 Version: $LATEST
The event data is: {"user-data":"hello, I'm jack!"}
RequestId: 0b2613f3-49b4-470f-b2f0-20ec523a40f1 Error: Runtime exited without providing a reason
Runtime.ExitError
END RequestId: 0b2613f3-49b4-470f-b2f0-20ec523a40f1
REPORT RequestId: 0b2613f3-49b4-470f-b2f0-20ec523a40f1	Duration: 13.69 ms	Billed Duration: 42 ms	Memory Size: 128 MB	Max Memory Used: 25 MB	Init Duration: 27.34 ms

まず bootstrap is executed! が出力され、bootstrap が実行されたことが確認できます。
続いてログに The event data is: {"user-data":"hello, I'm jack!"} と出力されています。
ログ中の {"user-data":"hello, I'm jack!"}/2018-06-01/runtime/invocation/next API の戻り値であり、ユーザーの今回の呼び出しの event を返しています。これが Lambda Runtime API の最も重要な API の機能です。

AWS_LAMBDA_RUNTIME_API とは何か

引き続き呼び出した API を観察しましょう:"http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next"
${AWS_LAMBDA_RUNTIME_API} という環境変数を使用していることに気づくでしょう。これは Lambda が管理する環境変数で、Lambda API サーバーを指しています。具体的な値が何で、どこを指しているのか気になるかもしれません。bootstrap を引き続き修正してみましょう。

#!/bin/sh
set -euo pipefail
echo "bootstrap is executed!"

echo "AWS_LAMBDA_RUNTIME_API value: ${AWS_LAMBDA_RUNTIME_API}"
RESPONSE=$(curl -s "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
echo "The event data is: $RESPONSE"

以下のようなログが得られます。

....
AWS_LAMBDA_RUNTIME_API value: 127.0.0.1:9001
...

なんと、AWS_LAMBDA_RUNTIME_API のサーバーは Lambda のローカル環境にあります。このポートがどのプロセスによって開かれているのか気になりませんか?bootstrap を引き続き修正して、システムのプロセス一覧と 9001 ポートに対応するプロセスを直接出力してみましょう。

Lambda 環境には ps などのコマンドが含まれていないため、/proc/proc/net/tcp にアクセスして情報を取得する必要があります。

プロセスと特定ポートを取得するスクリプトはやや複雑ですので、詳細を読み込む必要はありません。直接結果を見てみましょう。

#!/bin/sh
set -euo pipefail
echo "bootstrap is executed!"

echo "AWS_LAMBDA_RUNTIME_API value: ${AWS_LAMBDA_RUNTIME_API}"

# list all process
echo "--- Process List ---"
for pid in /proc/[0-9]*; do
    p=${pid##*/}
    cmd=$(tr '\0' ' ' < "$pid/cmdline")
    echo "PID $p: $cmd"
done

# check which process is providing port 9001
PORT=9001; \
HEX_PORT=$(printf ':%04X' $PORT); \
# 1. Extract Inodes (Supports both IPv4 and IPv6)
INODES=$(awk -v port="$HEX_PORT" '$2 ~ port {print $10}' /proc/net/tcp /proc/net/tcp6 | sort -u); \
\
if [ -z "$INODES" ]; then \
    echo "Port $PORT is not in use."; \
else \
    for INODE in $INODES; do \
        echo "--- Searching for Inode: $INODE ---"; \
        # 2. Iterate through fds to find the matching Socket Inode
        for FD in /proc/[0-9]*/fd/*; do \
            if [ -L "$FD" ] && [ "$(readlink $FD 2>/dev/null)" = "socket:[$INODE]" ]; then \
                # 3. Extract PID from the path
                PID=$(echo "$FD" | cut -d'/' -f3); \
                echo "[MATCH FOUND]"; \
                echo "PID: $PID"; \
                # 4. Fetch process info directly from /proc (Zero dependencies)
                echo -n "Process Name: "; cat /proc/$PID/comm; \
                echo -n "Command Line: "; tr '\0' ' ' < /proc/$PID/cmdline; echo -e "\n"; \
            fi \
        done \
    done \
fi

RESPONSE=$(curl -s "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
echo "The event data is: $RESPONSE"

上記の bootstrap を Lambda に更新して実行すると、以下のようなログが取得できます。

--- Process List ---
PID 1: /var/runtime/init --enable-extensions --logs-egress-api fluxpump --disable-tracing
PID 8: /bin/sh /var/task/bootstrap
--- Searching for Inode: 4608 ---
[MATCH FOUND]
PID: 1
Process Name: init
Command Line: /var/runtime/init --enable-extensions --logs-egress-api fluxpump --disable-tracing

Process List では 2 つのプロセスが確認できます。

  • PID1 /var/runtime/init
  • PID8 /bin/sh /var/task/bootstrap
    前者の PID は 1 であり、システムで最初に起動されたプロセスであることを意味します。もう一方のプロセスはカスタマイズした bootstrap スクリプトのプロセスです。

注意:bootstrap プロセスの PID は 2 ではなく 8 です。これは /var/runtime/init が起動後にいくつかの他のコマンドを実行して初期化を行った後に bootstrap を実行していることを意味します。具体的に何を実行しているかは Lambda がブラックボックスであるため知ることはできません。

次にポート情報を観察すると、9001 は実際には /var/runtime/init によって提供されていることがわかります。これは外部の Lambda サービスと通信し、Lambda instance 環境内の他のプログラムに対して API ポート情報を提供しています。

以上の調査により、Lambda の起動フローがより明確になりました。整理し直してみましょう。

Untitled diagram-2026-02-12-155111.png

Response API で処理結果を返す

Lambda の起動方法を理解したところで、先ほどカスタマイズした bootstrap を改めて見直してみましょう。

#!/bin/sh
set -euo pipefail
echo "bootstrap is executed!"

RESPONSE=$(curl -s "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
echo "The event data is: $RESPONSE"

これは /next API からユーザーリクエストの情報を取得していますが、その後何もせずに終了しています。そのため Lambda の呼び出しは実際には成功しておらず、呼び出し元のユーザーも戻り値を受け取れていません。
ユーザーのリクエストを取得した後、ユーザーに戻り値を返す必要があります。ここで別の Runtime API を使用します。

#!/bin/sh
set -euo pipefail

echo "bootstrap is executed!"

HEADERS_FILE=$(mktemp)
RESPONSE=$(curl -sS -D "$HEADERS_FILE" "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
# Get request id from next API header
REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS_FILE" | tr -d '[:cntrl:]' | awk '{print $2}')

echo "The event data is: $RESPONSE"
echo "The Request ID is: $REQUEST_ID"


LAMBDA_RESPONSE="{\"message\": \"Hello from bootstrap!\", \"echo\": $RESPONSE}"
curl -s -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$LAMBDA_RESPONSE"

echo "Response sent successfully!"

この bootstrap では以下の変更を行いました。

  • /next API の返却結果を一時ファイルに書き出し、後でレスポンスヘッダーを解析できるようにしました。
  • /next API のレスポンスヘッダーから Lambda-Runtime-Aws-Request-Id ヘッダーを取得しました。これにはユーザーの今回のリクエストに対応する request-id が格納されています。
  • レスポンスボディ LAMBDA_RESPONSE を構築し、Runtime API http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response を呼び出して送信しました。

上記の内容を Lambda に更新してテスト実行すると、今回は Lambda の呼び出しが成功します。
今回の呼び出し時に送信した event は以下の通りです。

{
	"user-data": "hello, I'm jack!"
}

Lambda を呼び出すと、期待通り以下の戻り値が得られます。

{
  "message": "Hello from bootstrap!",
  "echo": {
    "user-data": "hello, I'm jack!"
  }
}

これで Runtime API の動作メカニズムを完全に理解できました。Lambda の呼び出しフローをまとめると、以下のフロー図が得られます。

Untitled diagram-2026-02-12-155046.png

bootstrap でウォームスタートと instance の再利用を実現する

上記では bootstrap を使ってユーザーの呼び出しを受け取り結果を返すことを実現しましたが、実行完了後に bootstrap はそのまま終了してしまいます。

上記の bootstrap の Lambda 実行ログを確認してみましょう。

bootstrap is executed!
START RequestId: 368fec15-8cb0-4973-9bb0-dbd844edfce4 Version: $LATEST
The event data is: {"user-data":"hello, I'm jack!"}
The Request ID is: 368fec15-8cb0-4973-9bb0-dbd844edfce4
{"status":"OK"}
Response sent successfully!
RequestId: 368fec15-8cb0-4973-9bb0-dbd844edfce4 Error: Runtime exited without providing a reason
Runtime.ExitError
END RequestId: 368fec15-8cb0-4973-9bb0-dbd844edfce4
REPORT RequestId: 368fec15-8cb0-4973-9bb0-dbd844edfce4	Duration: 131.66 ms	Billed Duration: 132 ms	Memory Size: 128 MB	Max Memory Used: 4 MB

bootstrapResponse sent successfully! というログを出力した後、Lambda にエラーログ Error: Runtime exited without providing a reason Runtime.ExitError が記録されています。
これは bootstrap が終了し、それを /var/runtime/init が検知したためです。
bootstrap の終了は Lambda instance 全体の終了を引き起こすわけではありませんが、以下の問題を発生させます。

  • Lambda が実行異常を検知してエラーを報告し、正常率の監視に影響を与えます。
  • bootstrap の起動プロセスは複雑になる可能性があります(この記事の bootstrap は極めてシンプルですが、実際に運用される bootstrap は比較的「重い」ものです)。Lambda の呼び出しごとに bootstrap を再起動すると、パフォーマンスに大きな影響を与えます。

この問題を解決するために、bootstrap を改造して終了しないようにしましょう。

#!/bin/sh
set -euo pipefail

echo "bootstrap is executed!"

HEADERS_FILE=$(mktemp)

while true; do
  RESPONSE=$(curl -sS -D "$HEADERS_FILE" "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
  # Get request id from next API header
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS_FILE" | tr -d '[:cntrl:]' | awk '{print $2}')

  echo "The event data is: $RESPONSE"
  echo "The Request ID is: $REQUEST_ID"

  LAMBDA_RESPONSE="{\"message\": \"Hello from bootstrap!\", \"echo\": $RESPONSE}"
  curl -s -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$LAMBDA_RESPONSE"

  echo "Response sent successfully!"
done

前のバージョンと比較して、この bootstrap には無限ループの while true を追加し、/next API へのリクエスト処理をその中に包みました。
/next API には非常に特殊な特性があります:ユーザーからのリクエストがない場合、/next API は次のユーザー呼び出しがあるまでブロックし続けます。
この例では、bootstrap が起動して while の無限ループに入った後、まず /next API にリクエストします。Lambda instance のコールドスタートは通常、実際のユーザーリクエストが入ってきたために発生するため、この時点では /next API はユーザーのリクエスト情報を即座に返します。
bootstrap が /response API を呼び出して処理結果を返した後、再度 /next API にリクエストします。この時点ではユーザーリクエストがないため、/next API はブロックされます(つまり /next API のリクエスト段階で直接停止します)。ユーザーから次のリクエストが入ると、/next API のブロックが解除され、次の処理ループに入ります。
これが Lambda の最もコアとなる Runtime API のイベント処理ループです。どのプログラミング言語環境の Lambda であっても、この /next/response API を繰り返し呼び出すループの上に構築されています。

最後に Lambda の実行フロー図を再描画し、無限ループを追加した最終版を示します。

Untitled diagram-2026-02-12-155008.png

まとめ

ここまでで、Lambda の実行について大まかに理解できました。bootstrap とは何か、Runtime API とは何かを知ることができました。
しかし、これだけではまだ十分ではありません。例えば、

  • エラーハンドリング:エラーハンドリングは興味を引きにくい話題ですが、Lambda にとっては非常に重要です。次回はこの内容を取り上げます。
  • では結局 Runtime とは何なのか、公式の各言語の Lambda はどのように実装されているのか。Runtime API については理解できましたが、Runtime の概念はまだ曖昧であり、公式の各言語の Lambda がどのように実装されているかも気になります。今後の記事でこの点を掘り下げます。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?