背景
ライブラリをインストールしたLambdaレイヤーを作成したら .so ファイルが見つからないというエラーが出ることがある。
libGL.so.1: cannot open shared object file: No such file or directory
コンテナイメージを作るのが簡単だが、Lambdaレイヤーとして共有ライブラリまで含めてパッケージ化することはできるのか?
前提
ライブラリはなんでもいいのだが、opencvをPython3.9 Lambdaで使うという目標でトライしてみる。
opencv-python
ライブラリを普通にインストールしてLambdaレイヤーにすると、冒頭のエラーが出る。
opencvを使う方法は以下が考えられる。今回は3ができそうというところまで試した。
1. コンテナイメージとしてデプロイする
2. 共有ライブラリに依存しないライブラリを使う。
opencv-python
は.soファイルへの依存があったが、opencv-python-headless
は意識しないでも使えた。違いについては調べていない。
3. 依存モジュールを全部調べて明示的にレイヤーに入れる
現実的なのは1,2と思うので、3をあえてやる必要はないと思う。
そもそもできたとしてもLambdaレイヤーのサイズquotaギリギリになりそうなので、仮に今opencvでできたとしても、将来や他のパッケージでできる保証はない。
つまり今回の検証は割と無駄なことをしていると思う。
結果まとめ
上記のニュアンスからもわかる通り、できそうであるというところまで確認したが最後まではやっていない。
わかったこと
- .so ファイルをLambdaで読み取れる範囲(
LD_LIBRARY_PATH
)におくことは可能 -
yum install
したパッケージとその依存モジュールを特定のディレクトリに配置する方法 - 共有ライブラリに依存するライブラリのLambdaレイヤーを作成する方法はあまり実用的ではなさそうなこと
できなかったこと
-
opencv-python
がどの.soファイルに依存するのか正確に調べる方法がわからなかった。
エラーが出るたびにそのファイルをレイヤーに含める、ということでエラー自体は解消はできる見込みだが、明示された仕様に基づいていないため今動いたとしても突然使えなくなる可能性あり。
手順概要
参考
公式が昔サンプルを出していた。lambda-opencv
必要なモジュールと依存関係をDockerでインストールしている。
しかし最終更新が1年以上前と古いためか、このままだとビルドができなかった。少なくとも .so ファイルのインストールはしていない。サンプルはあくまでサンプルのため、動作しなくてもメンテナンスされる保証はない。
しかし大元はこのDockerfileを参考に使わせてもらった。
流れ
- opencvを指定したディレクトリにインストールする(サンプルのまんま)
- 依存モジュールを含んでいるライブラリmesa-libGLも指定したディレクトリに
yum install
- 1.2. 両方をzip化
- コンテナからホストにzipファイルを取り出す
- レイヤーにして、Lambdaで
import cv2
を試す
手順詳細
まずは .so ファイルがないエラーが出るところまで行い、流れを確認
EC2のAmazon Linux 2インスタンス起動。dockerをインストール。
先述のlambda-opencvのDockerfileを編集する。git clone
でも、新たにファイル作ってコピペでも良い。
そのままだと時間がかかった上にビルドに失敗するので、まずは以下を変更する
- FROM amazonlinux => FROM amazonlinux:2 1
- Python3.9 のところ以外は消す。
- Python3.9関連で一箇所ディレクトリにtypoがあって3.8になっているので、そこは直す
続いて、以下のコマンドを実行。
--rm
オプションはコンテナ終了時に破棄してくれるのでちょっと試したりする時とかホストへのコピーだけしたいときにとても便利。
sudo docker build --tag=lambda-layer-factory:latest .
sudo docker run --rm -it -v $(pwd):/data lambda-layer-factory cp /packages/cv2-python39.zip /data
これをレイヤーにして、Python3.9 Lambdaに追加する。Lambdaにはlambda-opencvサンプルのapp.pyのコードを貼り付ける。
ここで冒頭のエラーが出て、.soファイルが足りないことを知る。
libGL.so.1: cannot open shared object file: No such file or directory
これはAmazon Linux 2にはデフォルトで入っていないようだ。
sudo docker run --rm -ti amazonlinux:2 /bin/bash
find / -name libGL.so* ## ない
libGL.so.1が見つからないエラー自体の対処法
mesa-libGL 2というライブラリをインストールすればいいらしい。
sudo docker run --rm -ti amazonlinux:2 /bin/bash
find / -name libGL.so* ## ない
sudo yum install mesa-libGL -y
find / -name libGL.so*
## /usr/lib64/libGL.so.1
## /usr/lib64/libGL.so.1.7.0
Amazon Linux 2 のコンテナ内ならここにPython3を入れればもうimport cv2
が実行できたが、Lambdaは読み取りのパスが違うためかエラーの解消にはならない。
yumでディレクトリを指定したインストールをする
--installroot
というオプションを使うようだ。
普通に実行すると以下のようなエラーが大量に出た。
sudo docker run --rm -ti amazonlinux:2 /bin/bash
sudo yum install --installroot=/home/ec2-user/lambda libglvnd-glx
## 以下のエラー
Repository 'amzn2-core': Error parsing config: Error parsing "mirrorlist = '$awsproto://$amazonlinux.$awsregion.$awsdomain/$releasever/$product/$target/x86_64/mirror.list'": URL must be http, ftp, file or https not
...
どうも--installroot
で指定するディレクトリに以下の内容を持つyumの設定ファイルなど 3が必要なようだ4。
cat /lambda/etc/yum.repos.d/amzn2-core.repo
...
[amzn2-core]
name=Amazon Linux 2 core repository
mirrorlist=$awsproto://$amazonlinux.$awsregion.$awsdomain/$releasever/$product/$target/$basearch/mirror.list
Amazon Linux 2の場合は以下にあるので、こちらをコピーすれば良い
/etc/yum.repos.d/amzn2-core.repo
出来上がったDockerfile
Lambdaでは/opt/libというパスがLD_LIBRARY_PATHとして読み込み対象であることに注意して5、python lib が同じ階層になるようにzip化する。
結局サンプルと違うのは以下。先述の変更点3点にlibGl.soを書いただけである。
- FROM amazonlinux => FROM amazonlinux:2に変更
- Python3.9以外削除
- パスのタイポを修正
- libGl.soをインストールしzipへ含める
FROM amazonlinux:2
RUN yum update -y
RUN yum install gcc openssl-devel bzip2-devel libffi-devel wget tar gzip zip make -y
# Install Python 3.9
WORKDIR /
RUN wget https://www.python.org/ftp/python/3.9.10/Python-3.9.10.tgz
RUN tar -xzvf Python-3.9.10.tgz
WORKDIR /Python-3.9.10
RUN ./configure --enable-optimizations
RUN make altinstall
# Install Python packages
RUN mkdir /packages
RUN echo "opencv-python" >> /packages/requirements.txt
RUN mkdir -p /packages/opencv-python-3.9/python/lib/python3.9/site-packages
RUN pip3.9 install -r /packages/requirements.txt -t /packages/opencv-python-3.9/python/lib/python3.9/site-packages
# Install libGl.so
# To install to original dir, it has to have /etc/yum.repos.d/amzn2-core.repo
RUN mkdir -p /temp && \
cp -r /etc/ /temp/etc && \
yum install --installroot=/temp --releasever=2 mesa-libGL -y
RUN cp -a /temp/usr/lib64/libGL.so* /packages/opencv-python-3.9/lib
# ls /packages/opencv-python-3.9/ ==> python lib
# Create zip files for Lambda Layer deployment
WORKDIR /packages/opencv-python-3.9/
RUN zip -r9 /packages/cv2-python39.zip .
WORKDIR /packages/
RUN rm -rf /packages/opencv-python-3.9/
上記のDockerfileを使った最終結果
同じようにzipを取り出す。
sudo docker build --tag=lambda-layer-factory:latest .
sudo docker run --rm -it -v $(pwd):/data lambda-layer-factory cp /packages/cv2-python39.zip /data
Lambdaレイヤーを作成し、Lambdaを動かす(import cv2
する)と...
libGL.so.1ではなく他の共有ライブラリが見つからないというエラーになる。やったね。
"libgthread-2.0.so.0: cannot open shared object file: No such file or directory",
これを繰り返せばいつかは動くはずだが、不毛なのでここで止め。
多分下の記事にある.soファイルを全部入れれば解決するのだけど、この一覧をスマートに取得する方法がわからなかった。これはやはりエラーメッセージをみてその.soを入れて、を繰り返したようです。
https://qiita.com/suzuki-navi/items/d58d402422b784264d95
そのほか試してダメだったこと
Amazon Linux 2に含まれる.soファイルの大半をレイヤーに含める
サイズ的に無理だった。
Dockerfileに以下を追記して、/usr/lib64
ディレクトリにある.soとつくファイルを全てLambdaのLD_LIBRARY_PATHに置けば一気に必要そうなファイルを置けるのでは、と思ったがレイヤーサイズ上限に引っかかりレイヤー作成できず。
# Amazon Linux 2 image has more .so files than in Lambda's PATH
RUN mkdir -p /packages/opencv-python-3.9/lib
RUN cp -r /usr/lib64/*.so* /packages/opencv-python-3.9/lib
結構サイズでかい。全部入れるのは無理でした
du /usr/lib64 -sh
74M /usr/lib64
LD_LIBRARY_PATHをLambda実行時に書き換える
os.environ['LD_LIBRARY_PATH']
を書き換えるといけるという情報があった気がしたが、Lambdaでは通じなかった。これはPythonの実行前じゃないとダメ。。?なようで、使える状況が限られている or できないものなのかもしれない。
-
2023/06時点でamazonlinuxのlatestがAmazon Linux 2023になっているので、それで失敗するようになったようだ。 ↩
-
sudo yum install libglvnd-glx
でもこれらの.soファイルは追加できた ↩ -
amzn2-core.repo
ファイル単体をコピーしただけでは流石にダメだった ↩ -
yumdaのDockerfile(
yum install
をLambdaレイヤー用に依存関係込みで行ってくれるサードパーティ製のツール)とディレクトリ指定でyum installする記事を参考にした。 ↩ -
Lambdaで
print(os.environ['LD_LIBRARY_PATH'])
を実行してもなんとなくわかる ↩