aws lambda python で zip50MB超えの pip モジュール導入をかなり楽にする方法

# 超面倒 lambda pip モジュール導入 を楽にしてみる

## 最初に答えを記載、lambda python の subprocess で pip install する
これタイトルの通り、別の lambda 関数として python を選択してバージョンや利用CPUを選択して新しい lambdaを作成する。

と言うのも実は lambda python 環境において、裏のLiunux環境(AWS Linux)では `pipコマンド` が使える。

なので lambda python 環境では subprocessを利用して pip install を行い、そのpipのモジュールをエフェメラルストレージに出力し、それをzipにかためてS3にアップロードするpythonプログラムを実装する事で 対象 lambda python の実行バージョンの指定CPUの pypモジュールを利用できる環境を作成することができる。

次にこの pip ライブラリを利用する lambda の python に対して、プログラム実行を行い 先程のs3からpipモジュールのzipファイルをエフェメラルストレージにダウンロードして、それを解凍してから、`system.path.append` に対して対象エフェメラルストレージを設定する事で、pip モジュールが見事に利用できますって事になります。

ただこれだけ見ると「ふーんそれで？」みたいに思うのかも知れないので、これにたどり着くまでに色々と紆余曲折があったわけであり、以下興味がある人は見てやって下さい。

## その前に、この lambda python の pip インストールが何故面倒なのかを実体験を含めて記載して理解していただく

いやあ　はっきり言えば、これやめてECS導入したほうがいいんじゃねえって事で悩んだわけですが、先程記載した通り「以外と思ったより簡単に導入する手段が確立した」って事で、記事にしたいと思います。

そもそも筆者自身は、javaとか node.js(javascript) とかを主戦場にしてたので、少し python 実装できるけど、ほとんど使った事が無かったわけなんですよね。

ですが、今回のプロジェクトでは「python必須」でAWS上で、バッチ実行的に動画ファイルを生成する必要があったので、４０超えておっさんプログラマーが、本格Python実装へ！！

その中で動画ファイル時間も短いので、当初 aws lambda を利用するのが良いのでは！！って事で、それを軸に「王道的な意味でのモジュール」
- openCv
- Pillow

この辺の環境が要さそうとの事で、これらを使って動画ファイル作成対応を行うようにした。

ちなみに人生初の動画作成プログラムを作るわけで、何か「チャレンジャー過ぎる」初めてづくしだったわけですが、まあ何とか「動画ファイル作成プログラムは予定期間内に実装できて、うまくローカル環境で動作できる」までは持っていけた。

なので、次に lambda python 上でこの「ローカルプログラムを実行できるための環境づくり」を行う必要があったわけだが、そもそも openCV自身が圧縮しても70MBぐらいになるので、この場合「lambda の制限的仕様(zip=50MB, 解凍後サイズ=250MB)の上限をこえてしまう」ため、これらをインストールする方法として
1. S3にzipファイルを置いて、エフェメラルストレージ(/tmp)にダウンロード＋解凍で利用する
2. EFSを利用して利用する
3. ECRコンテナイメージを lambda利用

とあるようなんですよね。

なのでレイヤーに設定できない場合は別途上の対応方法があるが、この中において、項１が一番コスト的にも安価そうだし、AWS Consoleからの、lambda提供のエディタなどで、編集＋テストがAWS consoleでできる事のアドバンテージを含め、あと自動化も普通にできそう(circleciとかでアップロード等で対応できる)これらを鑑みた結果として、とりあえず項１これで対応しようと思い、まず対応のための準備を行ってみた次第です。

### ローカル環境が x86_64 の ubuntu環境だけど、この環境だと駄目でした

タイトルの通りですが、今利用している開発PCが ubuntu系の x86_64系なので、単純に pip でモジュールをカレントディレクトリにインストール
~~~~cmd
pip install opencv-python -t .
pip install Pillow -t .
~~~~

それを zip で固めてS3にアップロードして、
1. lambda 側から 対象のS3からファイルをエフェメラルストレージ(tmp)にダウンロード
2. エフェメラルストレージ(tmp)のzipを解凍(/tmp/modules/以下)
3. import sys で sys.path.append("/tmp/modules/") で、モジュールを利用可能に設定

これで openCVを imprt したが、エラーが発生する。
~~~cmd
OpenCV bindings requires "numpy" package.
~~~

エラー原因を色々調べると、pythonの場合 nativeライブラリ(soとかdllとか)も含まれるので、実環境に近い環境（osが同じで、pythonのバージョンも同じじゃないと、うまく利用できない場合が多い）ようで、なので別で出てきたように
- lambda python で pip モジュールで nativeライブラリがある場合は ECSでやるか ECSのDockerを lambda実行先で利用する

これら Dockerを利用した形で、Lambda環境と同じosやpythonバージョンなどを合わせた環境で pip インストールされたものを zip化 + s3アップロードと言う形が必要のようでした。

筆者が別で経験した事としては、これまで lambda環境は node.js が主戦場で、この場合だと基本npmのモジュールとかは javascript で完結できるので、実際ライブラリについては、バージョン依存とかありそうな反面、OSなどの環境依存ってのを意識しなかったのですが、pythonの場合はこの辺が全く別物の扱いのようなので、非常に「どはまり」した次第でした。

この時点で ECSでやろうかって考えたのですが、一方でこの lambda の便利な
- 直接 aws console でプログラムの編集や実行テストが簡単に行える

この手軽さは捨てきれないよねってわけで、調査続行する事に至りました。

### Dockerイメージから、pip インストール + zip + s3アップロードでも駄目でした
Dockerですが、最近あんまり使う機会が無かったため、ググりながらセットアップ。

そのあと、Dockerfileを作って、以下のイメージ
- lambci/lambda:build-python3.8

これを利用して、pipモジュールをローカルファイルに出力するものを作成して、これで zip ファイルを作成してみました。

そしてこれをS3にアップロードして、実行してみるが「やっぱりエラー」が出てくる。
ただ内容は先程の「ローカル環境で作ったもの」とは違って
~~~cmd
Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.
~~~
何か「nativeライブラリ(C言語)のインポートに失敗」と言う「文言」に変わっており、最初よりは進捗した感じだけど結局は「三歩進んで二歩下がる」感じな感じ、やっぱりうまく動きませんでした。

あと、あんまり python の事を知らない筆者ですが、現行利用中のlambda pythonのバージョンが 3.13 で、ローカル環境だと 3.12 だったわけですが、一方で指定したDockerイメージは `lambci/lambda:build-python3.8` の `3.8` で、どうも python業界では
- python3 --version

で表示されるバージョンと `python3.8` などの内容は別のもののようで、なので今回 lambda pythonが 3.13 = python3.9 のようで、今回の場合は このバージョンで pip モジュールを作る必要があるようでした。

このため色々ググってDockerfileイメージ元として
- public.ecr.aws/lambda/python:3.9

これを用いて実際に pip ライブラリを 作ってみたのですが、これでも「同じエラー」でうまく行きませんでした。

結局「全く分からない」まま「諦めてもうECSで動かすようにするしかないかな」って思ってたわけですが、その間検証としてダウンロードしたpip のzipが正常にそもそも解凍できてないからおかしいのでは？って事で、この辺の確認として subprocess を使って python実装を行い、ちゃんとzipが解凍できるてるかとか、実装してたわけですが、ここでふと「ジャストアイデア」を思いついたわけなんですよね。

で、これが「大成功」したわけで、正に「瓢箪から駒」って感じの「今までの削られた心が、正にオアシス＝水を得た魚モード」で「無事解決」にいたったわけで、そしてpython特有の悩みである「lambda pythonのバージョンアップ等」も「全然楽勝モード」となったわけなんですよね。

## 単純に lambda python で subprocess経由で pip インストール + zip + s3 uploadすれば良いだけでした！！

答えがわかると簡単だったのですが、そもそも lambda 自身も裏側で AWS Linux が動いてるわけで、ただ「利用可能なコマンド」や「エフェメラルストレージ(tmp)以外にファイル出力できない」など制限があるわけなんですが、実は `lambda python` において、コマンドとしての `pipコマンド` は普通に利用できるわけなんですよ。

はじめ「ダメ元」で subprocess 経由で
~~~py
subprocess.run(["pip", "install", "opencv-python", "-t", "/tmp/modules"])
subprocess.run(["ls", "/tmp/modules/", "-la"])
~~~

実行結果:
~~~
WARNING: The directory '/home/sbx_user1051/.cache/pip' or its parent directory is not owned or is not writable by the current user. The cache has been disabled. Check the permissions and owner of that directory. If executing pip with sudo, you should use sudo's -H flag.
Collecting opencv-python
Downloading opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (20 kB)
Collecting numpy>=1.21.2 (from opencv-python)
Downloading numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (62 kB)
Downloading opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (50.0 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 50.0/50.0 MB 154.5 MB/s eta 0:00:00
Downloading numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (16.1 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.1/16.1 MB 210.1 MB/s eta 0:00:00
Installing collected packages: numpy, opencv-python
Successfully installed numpy-2.2.3 opencv-python-4.11.0.86
[notice] A new release of pip is available: 24.3.1 -> 25.0.1
[notice] To update, run: pip install --upgrade pip
WARNING: The directory '/home/sbx_user1051/.cache/pip' or its parent directory is not owned or is not writable by the current user. The cache has been disabled. Check the permissions and owner of that directory. If executing pip with sudo, you should use sudo's -H flag.
Collecting Pillow
Downloading pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl.metadata (9.1 kB)
Downloading pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl (4.5 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5/4.5 MB 108.5 MB/s eta 0:00:00
total 36
drwxrwxr-x  9 ****** ******   4096 Mar 15 11:14 .
drwxrwxr-x  6 ****** ******   4096 Mar 15 11:14 ..
drwxrwxr-x  2 ****** ******   4096 Mar 15 11:14 bin
drwxrwxr-x 26 ****** ******   4096 Mar 15 11:14 cv2
drwxrwxr-x 24 ****** ******   4096 Mar 15 11:14 numpy
drwxrwxr-x  2 ****** ******   4096 Mar 15 11:14 numpy-2.2.3.dist-info
drwxrwxr-x  2 ****** ******   4096 Mar 15 11:14 numpy.libs
drwxrwxr-x  2 ****** ******   4096 Mar 15 11:14 opencv_python-4.11.0.86.dist-info
drwxrwxr-x  2 ****** ******   4096 Mar 15 11:14 opencv_python.libs
~~~

を実行してみたら、普通に実行結果に、明らかに動作した事を示すstdout結果が出力されていたし、インストール結果も出力されていた。

あれ？これで問題解決なんじゃねえ・・・って思ったわけですが、全てコマンドで何とかなるわけじゃなく当然
- mkdir
- zip
- aws(cli)

などのコマンドはlambdaにはインストールされて無いため、これらはそれぞれ pythonプログラムで実行
- mkdir: os.makedirs
- zip: shutil.make_archive
- aws(cli): boto3

させてやる事で無事 pip インストールモジュール内容を zip 化したものを s3 の当該バケットにアップロードする事ができた。

そしてこれを、lambda python実行時に s3ダウンロード + zip解凍 + カレントディレクトリ設定 をする事で `import cv2` でエラー等が出なくなった！！バンザーイ!!ちゃんちゃん!!!とか、思ってたわけですが、いざ!!動画作成プログラムを実行じゃい！！ってしてみると・・・案の定うまくいかないわけですが、とりあえず第一関門突破って感じでした。

## 第二関門: open-cv だとエラー `libGL.so.1` が出る問題

pip ライブラリが利用できたので、気分がよくなり、次に実際やりたい動画作成プログラムを実行！！って感じで実行してみると・・・駄目でした。

理由はタイトルの通り `libGL.so.1` このネイティブライブラリないエラーが出てしまう事。

まあこれは `opencv-python` から
- opencv-python-headless

にする事で、このエラーが出なくなり、また事前にローカル環境で試してみても、問題なく動作する。

これについては「問題なくなった」が、しかし「第三関門」が出てくるわけで、ちょっともう面倒で「ECSにしようかなあ」って本気で考えた次第です。

## 第三関門: Pillowでエラー `libraqm.so.0` が出る
これは、Pillowを動画作成で利用している中で、テキスト描画をしていて、ここで(language="japanese")と入れてると、このライブラリが必要のようで、特に問題がないようなので、外した事で、問題なく動作しました。

ただ、ここで「pip 以外でインストールできない nativeライブラリ系」は通常だと
- apt
- yml
- brew

等を使ってインストールするわけだけど、今回の pip 的な手法が使えないので、この nativeライブラリの問題で動かすことができないとなると、ECSのDockerイメージで lambda実行するとかになるのかってわけで、いやあこの辺非常に「使いづらいなあ」って感じました。

## 一方で pipのバージョン問題とかは、この方法で回避できる事がわかった

ちなみにこの環境(python3.13)で、pipライブラリをS3アップロードする環境を(3.12)で動かしてみると、見事にエラーが発生して、 pip モジュールのエラーとなりました。

なので実行バージョンと同じバージョン＋同じCPUタイプじゃないと、動かないようなので、今回の 同じ環境の lambda python から pip実行で作成された `pip モジュールを使う` 方法によって導入・運用コストも下げられるかと言えます。

あと、実際には pip モジュール作成用の lambda python 関数を作っておいて、これと同じ pythonバージョン、同じ CPUタイプの、本番環境を作る形としておけば、運用的にも本番のバージョンを変える場合は、pipモジュール作成 lambda のバージョンも合わせて変更の上で、実行で終わりとなるので、運用も「超お手軽」になりますね。

# いかがでしょうか？

後付で native ライブラリを入れる必要があるケース以外の場合、普通にこの条件で利用できるものと言えます。

ただもう１つささいな問題があるのですが lambda では 
- def lambda_handler(event, context):

これが実行されるまでは `init実行＝１０秒でタイムアウト` の制限があるらしく、lambdaがコールドスタートにおいては、openCV自身が、巨大なライブラリで、そしてboto3なども読み込む事になるので `init timeout` が発生するケースが発生してしまう事があります。

そうするとせっかく init 時間で「s3からpip モジュールダウンロード」されたが、これが init timeout 後にまったく同じく実行されてしまうわけで、はっきり言えば「init timeout で無駄に２度 pip モジュールダウンロード＋zip解凍 を行ってしまいます。

そのためこれらは一旦 エフェメラルストレージ にダウンロードされて解凍される場合は、何か init timeout しても、再実行されないような仕組みを入れる必要があります。

これら色々と「頑張る」事で、lambda python でも 50MB を超える zip pip モジュールの利用が `ECSDocerイメージ lambda` でなくても実現できるので、試していただければ幸いです。