
AWS LambdaでLambda Layersを使う場合、環境変数のPYTHONPATHは使っちゃダメという話
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
サーバーレス開発部@大阪の岩田です。 先日遭遇したAWS Lambdaの一風変わったエラーについてご紹介します。
結論
まず結論から。
LambdaのランタイムにPythonを選択し、かつLambda Layersの機能を利用する場合Lambdaの環境変数にPYTHONPATHを設定しないで下さい。
レイヤー内に配置したPythonのモジュールが読み込めなくなります。
処理概要と発生したエラー
今回エラーが発生したコードです。
import os
import sys
import Crypto
import json
def lambda_handler(event, context):
...略
マネジメントコンソールからテストを実行するとこんなエラーが発生しました。
{
"errorMessage": "Unable to import module 'lambda_function'"
}
どうもLambdaの初期化処理の中でCryptoモジュールが読み込めていないようです。 Cryptoモジュールはレイヤーの中に詰め込んでおり、レイヤーとLambda Functionの紐付けも正しく行えています。
なぜエラーが・・・??
原因切り分け
ここからエラーの原因を切り分けていきます。
レイヤーの作成ミスを疑う
まずレイヤーの作成ミスを疑いました。 原因切り分けのために新しく1からLambda Functionを作成し、同じレイヤーを紐付けてCryptoモジュールをimportしましたが、こちらは問題ありませんでした。 レイヤーではなくLambda Functionの方に問題がありそうです。
PYTHONPATHを疑う
Lambda Functionの設定を隅々まで見渡したところモジュールのimportに影響しそうな設定として、環境変数PYTHONPATHが指定されているのを発見しました。
試しに環境変数PYTHONPATHを削除してからテストイベントを実行すると、モジュールのimportエラーが解消されました!!
元々PYTHONPATHには/var/task/vendor:/var/runtimeが指定されていたのですが、なぜこれがレイヤーに影響を??
PYTHONPATHの確認
Lambda実行環境に自動で設定されるPYTHONPATHの値を自分で上書いているのが原因と考えました。本来はPYTHONPATHが/var/runtime:/opt/python:/opt/python/lib/python3.6/site-packagesのように設定されるべきところLambda Functionの設定で明示的に指定したことでPYTHONPATHから
- /opt/python
- /opt/python/lib/python3.6/site-packages
が欠落したのでは??という仮説です。
確認のためにLambda Functionの設定から環境変数PYTHONPATHを削除した状態だとPYTHONPATHの値がどうなるのか確認してみます。
import os
import sys
print(os.environ['PYTHONPATH'])
import Crypto
import json
def lambda_handler(event, context):
...略
環境変数PYTHONPATHの中身を出力するコードを追加しています。このコードを実行したところ、環境変数PYTHONPATHには/var/runtimeが設定されていました。正常に実行できるパターンでもPYTHONPATHにレイヤーのパスが設定されている訳ではないようです。
環境変数PYTHONPATHの有無によるsys.pathの比較
このままだと何故環境変数PYTHONPATHを削除することでエラーが解消したのか分かりません。
もう少し詳細を調査してみます。
import os
import sys
print(sys.path)
import Crypto
import json
def lambda_handler(event, context):
...略
Cryptoモジュールをimportする直前にprintでsys.pathの中身を出力するコードを追加しました。
上記のコードを実行し、環境変数PYTHONPATHありの場合と無しの場合それぞれでsys.pathの
中身を比較してみます。
PYTHONPATH無し
まずはエラーが発生しないPYTHONPATH無しバージョンです。
['/var/task', '/opt/python/lib/python3.6/site-packages', '/opt/python', '/var/runtime', '/var/runtime/awslambda', '/var/lang/lib/python36.zip', '/var/lang/lib/python3.6', '/var/lang/lib/python3.6/lib-dynload', '/var/lang/lib/python3.6/site-packages', '/opt/python/lib/python3.6/site-packages']
PYTHONPATH有り
次にエラーが発生したPYTHONPATHありバージョンのログです。
['/var/task', '/var/runtime/awslambda', '/var/runtime', '/var/lang/lib/python36.zip', '/var/lang/lib/python3.6', '/var/lang/lib/python3.6/lib-dynload', '/var/lang/lib/python3.6/site-packages']
sys.pathから
- /opt/python/lib/python3.6/site-packages
- /opt/python
が消えてしまいました!!
考察
これまでの調査結果から推測するとPythonで書いたLambda Functionの初期化処理は、環境変数PYTHONPATHがセットされていない場合に限りsys.pathのモジュール検索パスに/opt以下のパスを追加するようです。この結果レイヤーに配置したモジュールがLambda Functionから読み込めるようになるのです。
と、いうわけで、、、
Lambda Layersの機能を使う場合Lambda Functionの環境変数にPYTHONPATHを設定するのはやめましょう!!
まとめ
Lambda Layers利用時の一風変わったエラーについてご紹介しました。
なぜ環境変数PYTHONPATHをセットするとsys.pathのモジュール検索パスに/opt/以下のパスが追加されないのでしょうか?
興味のある方はLambda実行環境をハックしてLambdaのbootstrap処理を追いかけてみると面白いかもしれません。
Lambda実行環境をハックする方法は自己責任の元Googleさんにお尋ね下さい。








