0. 要約
タイトルの通りですが、
AWS Parameters and Secrets Lambda Extension は、ハンドラー関数"外"から呼び出しをすると HTTP 400 Error が出てしまいます。
そのため、ハンドラー関数"内"から呼び出しましょう!
1. はじめに...
2022年10月に、AWS Parameters and Secrets Lambda Extension が発表されました。
これまで、Lambda 関数内からパラメーターストアやシークレットマネージャーに保存した値(APIキーなど)を取得するには、Lambda 関数内からSDK を使って取得するのが一般的でした。
しかし、この Lambda Extension を活用することで、 パラメータ値を Lambda にキャッシュできるようになり、レイテンシーとコスト効率の点でメリットがあります。
2. 使ってみる...
詳しい使い方は、公式ドキュメントや、他のブログ記事に譲ります。(例えばこちら)
要点をまとめると、以下の作業をしています。
- SecretsManager にAPIキーなどを保存
- Lambda 関数に、今回の Lambda Layer を追加
- Lambda の IAMロールに権限を追加
今回は、私がハマった「コードを書く場所」に焦点を当てて記事を書きます。
※ この記事を読む方は、既に使っている方が過半数だと思いますし...
2-1. 【これはNG】Handler 関数"外"から呼び出す
以下は、ハンドラー関数"外"から、Lambda Extension 経由で、 Secrets Manager に保存した API キーを取得するコードです。
実行結果も貼り付けていますが、HTTP Error 400: Bad Request
のエラーが起き、API キーを取得できていません。
Lambda 関数のコード
import json
import os
import urllib.request
# Handler 関数"外"から、AWS Parameters and Secrets Lambda Extension 経由でシークレットの値を取得する
endpoint = 'http://localhost:2773/secretsmanager/get?secretId=SecretsManagerName'
request = urllib.request.Request(endpoint)
request.add_header('X-Aws-Parameters-Secrets-Token', os.environ['AWS_SESSION_TOKEN'])
with urllib.request.urlopen(request) as response:
body = response.read()
secret_strings = json.loads(body.decode("utf-8"))["SecretString"]
APIKEY = json.loads(secret_strings)["APIKEY"]
print(f'GET APIKEY = {APIKEY}')
# Handler 関数
def lambda_handler(event, context):
return {
'statusCode': 200,
'headers': {
'Content-Type': 'text/plain'
},
'body': f'APIKEY is :{APIKEY}'
}
実行結果
=== Function Logs [一部のみ] ===
[ERROR] HTTPError: HTTP Error 400: Bad Request
=== Response ===
{
"errorMessage": "HTTP Error 400: Bad Request",
"errorType": "HTTPError",
"requestId": "",
"stackTrace": [
" File \"/var/lang/lib/python3.10/importlib/__init__.py\", line 126, in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n",
" File \"<frozen importlib._bootstrap>\", line 1050, in _gcd_import\n",
" File \"<frozen importlib._bootstrap>\", line 1027, in _find_and_load\n",
" File \"<frozen importlib._bootstrap>\", line 1006, in _find_and_load_unlocked\n",
" File \"<frozen importlib._bootstrap>\", line 688, in _load_unlocked\n",
" File \"<frozen importlib._bootstrap_external>\", line 883, in exec_module\n",
" File \"<frozen importlib._bootstrap>\", line 241, in _call_with_frames_removed\n",
" File \"/var/task/lambda_function.py\", line 9, in <module>\n with urllib.request.urlopen(request) as response:\n",
" File \"/var/lang/lib/python3.10/urllib/request.py\", line 216, in urlopen\n return opener.open(url, data, timeout)\n",
" File \"/var/lang/lib/python3.10/urllib/request.py\", line 525, in open\n response = meth(req, response)\n",
" File \"/var/lang/lib/python3.10/urllib/request.py\", line 634, in http_response\n response = self.parent.error(\n",
" File \"/var/lang/lib/python3.10/urllib/request.py\", line 563, in error\n return self._call_chain(*args)\n",
" File \"/var/lang/lib/python3.10/urllib/request.py\", line 496, in _call_chain\n result = func(*args)\n",
" File \"/var/lang/lib/python3.10/urllib/request.py\", line 643, in http_error_default\n raise HTTPError(req.full_url, code, msg, hdrs, fp)\n"
]
}
2-2. 【こっちだとOK】Handler 関数"内"から呼び出す
Lambda のハンドラー関数"内"からであれば、Secrets Manager に保存したAPIキーを読み込めます。
Lambda 関数のコード
import json
import os
import urllib.request
# Handler 関数
def lambda_handler(event, context):
# Handler 関数"内"から、AWS Parameters and Secrets Lambda Extension 経由でシークレットの値を取得する
endpoint = 'http://localhost:2773/secretsmanager/get?secretId=SecretsManagerName'
request = urllib.request.Request(endpoint)
request.add_header('X-Aws-Parameters-Secrets-Token', os.environ['AWS_SESSION_TOKEN'])
with urllib.request.urlopen(request) as response:
body = response.read()
secret_strings = json.loads(body.decode("utf-8"))["SecretString"]
APIKEY = json.loads(secret_strings)["APIKEY"]
print(APIKEY)
return {
'statusCode': 200,
'headers': {
'Content-Type': 'text/plain'
},
'body': f'APIKEY:{APIKEY}'
}
実行結果
=== Function Logs [一部のみ] ===
GET APIKEY = hogehoge
=== Response ===
{
"statusCode": 200,
"headers": {
"Content-Type": "text/plain"
},
"body": "APIKEY is :hogehoge"
}
3. あれ? Handler 関数の "内" と "外" って何が違うの?
ここまで書くと、「そもそも、なぜ Handler 関数"外"に処理を書いたのか? 俺は常に Handler 関数"内"に処理を書いてるぞ!」という意見も出てくるかと思います。
実は、Lambda の Handler 関数 "外" と "内" では、実行時の挙動が異なります。
Handler 関数 "外" に書いたコード | Handler 関数 "内" に書いたコード | |
---|---|---|
動作 | コールドスタート時の1回だけ実行される。 | ウォームスタート時に毎回実行される。 |
用途 | 全ユーザー共通の処理や、時間のかかるライブラリの読み込み、初期化などを記載する | ユーザーごとに異なるデータがあるようなコードを記載する(DBアクセスなど) |
3.1 自分の開発を振り返ってみると...
私は開発前半、APIキーを環境変数に設定し、Handler 関数 "外" から取得していました。
これは、APIキーは長期間再利用できるため、Handler関数"外"に記載/定義するのが良いと考えたためです。(実際、Lambda関連のソースコードを見ると同じような実装をよく見かけます。)
開発後半になってから、Lambda Extension を利用する構成に作り直した際、同じく Handler 関数 "外" に Secrets Manager へのアクセスコードを書いてしまったため、うまくいかないエラーに巻き込まれました。
3.2 未解決の疑問点
Handler 関数の "内" と "外" で違いがあることはご理解いただけたと思います。
しかし、なぜ、「AWS Parameters and Secrets Lambda Extension を Handler 関数外からだと呼び出せないのか?」については、すみません。私には分かりませんでした。
もし、ご存知の方がいらっしゃっいましたら、教えていただけますと幸いです。
X. 終わりに
個人開発をしていた時に、このトラブルに遭遇しました。
私は 「400 Error」 が権限不足に起因するもの(具体的には 401 Error)だと考え、間違った調査や修正対応をしてしまい、休日を丸1日溶かしました
こちらのナレッジが、悩める皆様の休日を救えることを祈っています。
おまけ
このナレッジは、以下のアプリ(ゲーム)を開発していた際の副産物です。
技術的な工夫点も多いアプリですので、ぜひこちらもご覧ください。