##事の発端
AWSのLambda上にそれなりに複雑になる予定のpythonの関数を追加する事になったが、lambda上ではpipが使用できないため、外部モジュールを使用する関数を使用する場合、
[一つ一つ使用している外部モジュールを\Python〇〇\Lib\site-packages] から探し出し纏めてzip化するという賽の河原のようなおちねり作業が発生する。
当然ながら人間は信用ならない生物のため、こんな作業を毎回手作業でやろうものならパンジャンドラムとマーマイトをガンギメした某ブリテンの暴走青列車(ブルートレイン)の如く事故が発生する
事故は起こるさ? 事故は人間が起こすものなんだよ!
(※加えてこの作業にとても時間が取られる)
そこでlambda-uploaderを使用し、外部モジュールの抽出作業を任せてしまうことで効率化と事故発生率を少しでも下げようと画策した。
##作業環境
- Windows10(64bit)
- python3.8(64bitインストーラ使用)3.8なのは2020/12月末時点でlambdaがpython3.8以降に対応していないから。
- 上記に加えてpythonのインストール箇所は(元来はおすすめされないけど、端末ごと引き継ぐ可能性があるため C:\Program Files以下。本来は各c:\users~以下)
##lambda-uploaderを使用するためには
・lambda.json
→アップロードするlambda関数の概要を纏めた設定ファイル
・requirements.txt
→作ったlambda関数が使用する外部モジュールを定義するとこのファイルを参照して自動でピックアップ、zipの際にまとめてくれるみたいです。つまりpip freezeから自動でファイル生成するバッチを作れば一番リスキーな作業を自動化してくれます。
事故は起こさせない
・event.json
→テスト時に投げるPOSTリクエストの内容で、GETリクエストのテストの場合は空でも良い
ということで今回は不要。他の参考内容 等でも記載がない場合が多いので必須ファイルでは無い模様。
・〇〇.py
→aws lambdaのエントリーポイントである
def lambda_handler(event, context):~
関数の処理が記載されたpyファイル。
lambda-uploaderの手順としては上記で記載した設定ファイル達を元に、大まかに分けて以下3点を順に行う。
- build
- zip
- upload(aws lambdaに)
jsonファイルに以下の様に設定(セキュリティに関わる情報は伏せてます)して・・・
{
"name": "testFunction",
"description": "testFunction description.",
"region": "ap-northeast-1",
"handler": "lambda_function.lambda_handler",
"role": "arn:aws:iam::XXXXXXXXXXXX:role/BlogenistBlogSample",
"timeout": 30,
"memory": 128,
"runtime": "python3.8" ←明示的に指定しないとデフォルト(時代から捨てられつつあるpython2.7)でアップロードを行う。
}
イクゾー デッデッデデデデン
そして(想定外の)事故は起きた
カーン
PS C:\workspace(python)\testcode\lambda> lambda-uploader Building Package
âï¸ Unexpected error. Please report this traceback.
Uploader: 1.3.0
Botocore: 1.16.38
Boto3: 1.19.38
Traceback (most recent call last):
File "c:\program files\python38\lib\site-packages\lambda_uploader\shell.py", line 194, in main
_execute(args)
File "c:\program files\python38\lib\site-packages\lambda_uploader\shell.py", line 83, in _execute
pkg = package.build_package(pth, requirements,
File "c:\program files\python38\lib\site-packages\lambda_uploader\package.py", line 51, in build_package
pkg.build(ignore)
File "c:\program files\python38\lib\site-packages\lambda_uploader\package.py", line 79, in build
self.install_dependencies()
File "c:\program files\python38\lib\site-packages\lambda_uploader\package.py", line 151, in install_dependencies
self._build_new_virtualenv()
File "c:\program files\python38\lib\site-packages\lambda_uploader\package.py", line 177, in _build_new_virtualenv
python_exe = self._python_executable()
File "c:\program files\python38\lib\site-packages\lambda_uploader\package.py", line 195, in _python_executable
raise Exception('Unable to locate {} executable'
Exception: Unable to locate python3.8 executable
(´・ω・`)
Exception: Unable to locate python3.8 executable
(´;ω;`)ブワッ
最後の行でビルド時にpython3.8が見つからないとエラーを吐瀉(オエ)られる。
##原因を調べてみる
というわけで(最終的な実行環境のpython自体はaws上にあるのでpython.exeいる?とおもいつつ)原因調査開始。
-
PATHが通ってない?
→問題なし。[\python38] 及び [\Python38\Lib\site-packages]両方のPATHを環境変数設定で確認(というかインストール時にPATH通す設定で今まで散々コマンド使ってきているのでこの時点でPATHが原因というのはおかしい) -
マイナーバージョンも指定しないとだめ?ということでjsonのパラメータを"runtime": "python3.8.6"に修正
→Exception: Unable to locate python3.8.6 executable
(´・ω・`)(´・ω・`) -
いっそのことjsonのruntimeパラメータを削除
→Exception: Unable to locate python2.7 executable
(´・ω・`)(´・ω・`)(´・ω・`) ソリャソウダ -
lambda-uploaderモジュールのバグかも知れないので最新版を見にgitへ(pypiに反映せずgit上だけ永遠に更新されているモジュールというのは案外多い)
↓
↓
(´;ω;`)ブワッ
エラー内容で色々で調べても引っかかって来ない
ということで、Tracebackがあるため、直接lambda-uploaderのソースコードを見に行く。
変に設定をいじっていないなら \Python38\Lib\site-packages\lambda_uploaderに目的のコードが有るためササッとディレクトリ開いて最後に処理が通った箇所付近
File "c:\program files\python38\lib\site-packages\lambda_uploader\package.py", line 195, in _python_executable
raise Exception('Unable to locate {} executable'
を調査。
def _python_executable(self):
if self._pyexec is not None:
python_exe = find_executable(self._pyexec)
if not python_exe:
195行目→ raise Exception('Unable to locate {} executable'
.format(self._pyexec))
find_executable関数の処理が怪しいと目星を付け更に呼び出し元を調査。
※vs codeでpython環境建てると複雑なコードでもインテリジェンスが便利なのでおすすめ。いつか開発環境構築を記事にしたい。
def find_executable(executable, path=None):
"""Tries to find 'executable' in the directories listed in 'path'.
A string listing directories separated by 'os.pathsep'; defaults to
os.environ['PATH']. Returns the complete filename or None if not found.
"""
_, ext = os.path.splitext(executable)
if (sys.platform == 'win32') and (ext != '.exe'):
executable = executable + '.exe'
if os.path.isfile(executable):
return executable
if path is None:
path = os.environ.get('PATH', None)
if path is None:
try:
path = os.confstr("CS_PATH")
except (AttributeError, ValueError):
# os.confstr() or CS_PATH is not available
path = os.defpath
# bpo-35755: Don't use os.defpath if the PATH environment variable is
# set to an empty string
# PATH='' doesn't match, whereas PATH=':' looks in the current directory
if not path:
return None
paths = path.split(os.pathsep)
for p in paths:
f = os.path.join(p, executable)
print(f)
if os.path.isfile(f):
print(f)
# the file exists, we have a shot at spawn working
return f
return None
上記コードを順番に流し見た限り、環境変数を取得して、分割(そのままだとセミコロンでつながった1行の文字列)、末尾に何かくっつけてファイルチェックしてるなーということで
for p in paths:
f = os.path.join(p, executable)
print(f) #困った時のprint文
if os.path.isfile(f):
# the file exists, we have a shot at spawn working
return f
再び実行。
PS C:\workspace(python)\testcode\lambda> lambda-uploader
Building Package
※↓がprintで吐かせた内容
C:\Program Files\Python38\Scripts\python3.8.exe
C:\Program Files\Python38\python3.8.exe
・・・以下他の環境変数の末尾にpython3.8.exeをくっつけた文字列&Traceback
上記を見た限りそんな変なことしてないなぁと思いつつ、エラーがpython3.8が見つからないというエラーのため、該当のディレクトリを見に行ったら・・・
(ω・`) (・ω・`) (´・ω・`) (´;ω;`)ブワッ
なんで?
##それならjsonを書き換える。
ということでそもそも存在しないexeファイルを検索していたというのが原因でした。
そのため、
"runtime": "python"
と書けば動きはする。ビルドまでは。
明らかにおかしな動きなため、そのまま動かせばその後で詰まるだろうなと思いながら動かし。当然のようにアップロード処理で落ちました。
例外の内容が
botocore.errorfactory.InvalidParameterValueException: An error occurred (InvalidParameterValueException) when calling the UpdateFunctionConfiguration operation: Value python at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [java8, java11, nodejs10.x, nodejs12.x, python2.7, python3.6, python3.7, python3.8, dotnetcore2.1, go1.x, ruby2.5] or be a valid ARN
※要約するとlambda.jsonの**runtimeのパラメータにpythonバージョンを書け。**又は本当に有効なARN設定してるの?
とあり、botocoreはawsの認証周りをしてくれる箇所のため、awsのサービスを操作するパッケージがいるのかなと調べてみたり(結論から言うと見当違いでした)botocoreモジュールにも手を加えてデバッグしたり、そもそもlambda-uploaderモジュールのソースコードがSyntaxWarning: "is" with a literal. Did you mean "=="?警告を出していて、**やっぱりpython3.8では使えないのでは?**と考えているうちに思いついた下記を試してみました。
PS C:\workspace(python)\testcode\lambda> lambda-uploader Building Package
Uploading Package
Fin
aws lambda上を確認しても正常にアップロード出来ていました。
ものすごくスッキリしない解決になってしまい申し訳ありません。
しかし、python〇〇.exeなんて一体どういう流れで入ってくるのだろうか・・・?(vitualenvが先に割り込んで生成されたpython〇〇.exeを参照するのかと思ってたが中身を見てもvirtualenvより先に参照していた。)
誰か原因をご存知ならコメントでご指摘下さい。
##結論
pythonの素晴らしいところはPIPしてきたモジュールを直接書き換え、デバッグして動作確認できるところ。(終わったらもとに戻すことを忘れずに)
また、lambda-uploaderは3年近く更新がなく、既存のコードも最近のpythonの仕様変更によりトラブルを抱え始めているため、今後は使えなくなる事を想定したほうが良いかもしれません。