LayerにGemを格納してAWS Lambda関数を成功させる方法(2)

はじめに

先日、Layerにgemを格納してAWS Lambda関数を動かす記事を書きました。

miyohide.hatenablog.com

この記事では、Rubyで実装されたgemを使ってみたのですが、この記事ではネイティブ拡張を使ったgemを使う方法を記します。具体的には、PostgreSQLと接続するためのGemであるpg gemを使う方法を記します。

gemをzipで固めるだけではダメ?

任意の環境でbundle config set --local path 'vendor/bundle' && bundle installを実行し、zip -r my_ruby_package.zip vendorにてzipファイルを作成してLayerとして登録、関数にも紐付けして実行するとlibpq.so.5: cannot open shared object file: No such file or directoryのようなエラーメッセージが出力されます。

このエラーメッセージは、今回試したpg gemではOSが持っている共通ライブラリlibpq.so.5に依存しており、Lambda関数が動作する環境にはこれらの共通ライブラリが入っていないために発生しているのが原因です。

このため、動作環境に対してlibpq.so.5などをLD_LIBRARY_PATHに格納されるようにファイルをコピーします。具体的には、libディレクトリを作成し、libディレクトリ以下にlibpq.so.5などを格納、zip -r my_ruby_package.zip vendor libを実行してlibディレクトリもzipファイル化しておきます。

このzipファイルがLayerとして登録された場合、Lambdaの環境変数LD_LIBRARY_PATHに含まれるパス/opt/liblibpq.so.5などのファイルが格納されることになります。

docs.aws.amazon.com

ただ、この手法を続けても/lib64/libm.so.6: version 'GLIB_2.29' not foundというエラーメッセージが出て結局動きませんでした。

Layerを作るためのコンテナイメージを作る

Layerを作るには環境依存が大きいため、Layerを作るためのコンテナイメージを作ることにします。以下のようなDockerfileを用意します。

FROM public.ecr.aws/sam/build-ruby3.2:latest-x86_64

WORKDIR /var/task

RUN amazon-linux-extras install -y postgresql14
RUN yum -y install postgresql-devel postgresql
RUN gem update bundler
CMD ["bash", "make_layer.sh"]

pg gemを動かすために必要なライブラリ等をコピーするためのshell scriptmake_layer.shを以下のような中身で用意します。

bundle config --local silence_root_warning true
bundle config set clean true
bundle config set path '/var/task'

bundle install

mkdir -p /var/task/lib

cp -a /usr/lib64/libpq.so.5.14 /var/task/lib/libpq.so.5
cp -a /usr/lib64/liblber-2.4.so.2.10.7 /var/task/lib/liblber-2.4.so.2
cp -a /usr/lib64/libldap_r-2.4.so.2.10.7 /var/task/lib/libldap_r-2.4.so.2
cp -a /usr/lib64/libsasl2.so.3.0.0 /var/task/lib/libsasl2.so.3
cp -a /usr/lib64/libssl3.so /var/task/lib/libssl3.so
cp -a /usr/lib64/libsmime3.so /var/task/lib/libsmime3.so
cp -a /usr/lib64/libnss3.so /var/task/lib/libnss3.so
cp -a /usr/lib64/libnssutil3.so /var/task/lib/libnssutil3.so

cd /var/task
zip -r layer.zip .

この作り方は以下の記事を参考にしました。

www.farend.co.jp

neenet-pro.com

あとは、docker build -t ruby-lambda-layer .を実行してコンテナイメージを作成し、docker run --rm -it -v $PWD:/var/task ruby-lambda-layerを実行するとzipファイルが作成されます。

環境変数LD_PRELOADの設定

これでLayerに登録するzipファイルが作成できましたので、登録して実行してみます。すると、/usr/lib64/libnssutil3.so: version 'NSSUTIL_3.82' not foundというエラーメッセージが出ました。

このエラーメッセージの解決にかなり悩みましたが、以下のブログ記事の記載で解決しました。

carrfane.medium.com

具体的には、環境変数LD_PRELOAD/opt/lib/libnssutil3.soを設定します。

結果、単純にrequire 'pg'としただけの以下のプログラムですが無事動作しました。

動作結果。

考察

ここまできてようやくpg gemを動作することができました。動作には、Lambdaの動作環境に強く依存したライブラリのコピーが必要となり、動作させるだけでも一苦労です。また、将来的な動作環境の変更の際にまた試行錯誤するのは大変かと思います。

このため、以下の記事で書いたようにLambdaで動くコンテナイメージを作っておいた方が考えることが少なくて良さそうです。

miyohide.hatenablog.com