はじめに
AWS Lambda でサードパーティ製ライブラリを利用する場合、通常は Layer を作成 するのが推奨されています。
しかし、Layer には以下のような制限があります。
- 1つの Layer は最大 50MB(圧縮時)まで
- Lambda に設定できる Layer の合計サイズは 250MB まで
scikit-learn や pandas のような重量級ライブラリを使う場合、容量制限にひっかかることも少なくありません。
そこで今回は S3 にライブラリ一式を置いて Lambda 実行時に /tmp
に展開して使う方法 を紹介します。
この方法を使えば 50MB 制限を回避でき、必要なライブラリを自由に扱えるようになります。
手順
1. AWS公式の Lambda 環境をクローン
まずは AWS が公開している Lambda のベースイメージを利用します。
git clone https://github.com/aws/aws-lambda-base-images.git
cd aws-lambda-base-images
git checkout python3.12 # 利用環境のバージョンに合わせてください
2. Dockerfile に追記し、必要なライブラリをインストール
Dockerfile.python3.12 を編集し、pip install を追記します。
FROM scratch
ADD x86_64/214c39c8cf002eeeafedbbf3d9eea26835911f69c1c2e85d915322904aa971c7.tar.xz /
ADD x86_64/5348a9630c8b3ba93173487c1a4b5adf6870c16ef653ab7238d538089e39af2d.tar.xz /
ADD x86_64/ba278819ec56a8af670fd42e84172e233aad6a5b8ff8b3a75ce62b9fa02afcd1.tar.xz /
ADD x86_64/bc53fe7cd91383999f362c80316863ea6443c6a3e84abeeec6e2e60882b4308f.tar.xz /
ADD x86_64/cdf4e651a6374c619af769fb233514305e3142168be97059029cb6b52c96ac72.tar.xz /
ADD x86_64/eadc3b32f0f94e591a686ee6b9447e9cdac2557a9a1509018b3a6f10ae4e794b.tar.xz /
ENV LANG=en_US.UTF-8
ENV TZ=:/etc/localtime
ENV PATH=/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin
ENV LD_LIBRARY_PATH=/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib
ENV LAMBDA_TASK_ROOT=/var/task
ENV LAMBDA_RUNTIME_DIR=/var/runtime
WORKDIR /var/task
# ここ追加に追加
RUN pip install --no-cache-dir scikit-learn pyyaml -t /var/lang/lib/python3.12/site-packages
ENTRYPOINT ["/lambda-entrypoint.sh"]
3. Docker イメージをビルド
/aws-lambda-base-images> wsl
/aws-lambda-base-images$ docker build -t python3.12:local -f Dockerfile.python3.12 .
下記エラーが出る場合は、sudoを付けて管理者権限で実行してください。
ERROR: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Head "http://%2Fvar%2Frun%2Fdocker.sock/_ping": dial unix /var/run/docker.sock: connect: permission denied
4. コンテナを立ち上げ、site-packages をホスト環境(windows)にコピー
ビルド後のイメージの中にライブラリが展開されています。
下記のコマンドでコンテナを立ち上げ、ライブラリをホスト側にコピーしてください。
# コンテナの起動
/aws-lambda-base-images$ sudo docker run -d --name py312-container python3.12:local
# site-packagesをコピー
/aws-lambda-base-images$ sudo docker cp py312-container:/var/lang/lib/python3.12/site-packages ./site-packages
この site-packages 配下のファイルを zip 化します。
/aws-lambda-base-images$ zip -r ./site-packages.zip ./site-packages -x "*.dist-info*" "*__pycache__*"
.dist-info, _pycache_ は不要なので除外します。
zipコマンドが無ければインストールしてください。
/aws-lambda-base-images$ sudo apt install zip
補足:
.dist-info とは?
Python パッケージを pip でインストールすると、パッケージの情報が .dist-info フォルダとして作られます
内容例:
- METADATA → パッケージ名やバージョン情報
- RECORD → パッケージに含まれるファイル一覧
- INSTALLER → pip でインストールされた情報
これらはLambda 実行には不要 なので zip から除外すると軽量化できる
pycache とは?
- Python が .py ファイルを実行すると自動で生成する コンパイル済みバイトコード (.pyc) の保存フォルダ
- 実行速度向上のためだけのキャッシュであり、Lambda にデプロイする場合は不要
こちらも除外すると zip が軽くなる
5. S3 にアップロード
s3://my-bucket/site-packages.zip
6. Lambda 関数内で /tmp に展開
Lambda 関数の中で S3 からダウンロードし、/tmp に展開します。
import boto3
import zipfile
import sys
import os
s3 = boto3.client('s3')
def lambda_handler(event, context):
# S3からzipを/tmpにダウンロード
s3.download_file('my-bucket', 'site-packages.zip', '/tmp/site-packages.zip')
# zipを展開
with zipfile.ZipFile('/tmp/site-packages.zip', 'r') as zip_ref:
zip_ref.extractall('/tmp')
# sys.pathに追加
sys.path.insert(0, '/tmp/site-packages')
# インポートして利用
import sklearn
import yaml
return {
'statusCode': 200,
'body': f'scikit-learn version: {sklearn.__version__}'
}
{
"errorMessage": "An error occurred (403) when calling the HeadObject operation: Forbidden",
"errorType": "ClientError",
"requestId": "c2f48c85-3e0b-44c3-9beb-4615dc655698",
"stackTrace": [
" File \"/var/task/lambda_function.py\", line 10, in lambda_handler\n s3.download_file('my-bucket', 'site-packages.zip', '/tmp/site-packages.zip')\n",
" File \"/var/lang/lib/python3.12/site-packages/botocore/context.py\", line 123, in wrapper\n return func(*args, **kwargs)\n",
" File \"/var/lang/lib/python3.12/site-packages/boto3/s3/inject.py\", line 223, in download_file\n return transfer.download_file(\n",
" File \"/var/lang/lib/python3.12/site-packages/boto3/s3/transfer.py\", line 406, in download_file\n future.result()\n",
" File \"/var/lang/lib/python3.12/site-packages/s3transfer/futures.py\", line 111, in result\n return self._coordinator.result()\n",
" File \"/var/lang/lib/python3.12/site-packages/s3transfer/futures.py\", line 287, in result\n raise self._exception\n",
" File \"/var/lang/lib/python3.12/site-packages/s3transfer/tasks.py\", line 272, in _main\n self._submit(transfer_future=transfer_future, **kwargs)\n",
" File \"/var/lang/lib/python3.12/site-packages/s3transfer/download.py\", line 355, in _submit\n response = client.head_object(\n",
" File \"/var/lang/lib/python3.12/site-packages/botocore/client.py\", line 602, in _api_call\n return self._make_api_call(operation_name, kwargs)\n",
" File \"/var/lang/lib/python3.12/site-packages/botocore/context.py\", line 123, in wrapper\n return func(*args, **kwargs)\n",
" File \"/var/lang/lib/python3.12/site-packages/botocore/client.py\", line 1078, in _make_api_call\n raise error_class(parsed_response, operation_name)\n"
]
}
こういうエラーがでたら、lambdaの実行ロールにS3にアクセスする権限が付与されていないことが原因なので、下記のように権限を付与してください。
※正確にはs3:GetObjectしかいらないので、jsonでポリシーを設定する場合は、そのように設定したほうが安全
また、下記のようなエラーが出た場合
{
"errorType": "Sandbox.Timedout",
"errorMessage": "RequestId: d4751fdf-0c1e-430a-abad-f0e9fbc5a2e6 Error: Task timed out after 3.00 seconds"
}
lambdaのタイムアウトによる強制終了なので、下記のようにタイムアウトの設定時間を延ばしてください。
また、この手法でライブラリを使用する場合、デフォルトのメモリ128MBでは、まず動かないと思うので拡張してください。
※絶対10分も必要ないです。私がテストした時は20秒で実行できました。
なぜこんな回りくどいことが必要なのか?
Lambda の実行環境は Amazon Linux 系の特殊な環境 で動いています。
例えば私たちの手元(windows や macOS, Ubuntu)で
pip install scikit-learn -t .
zip -r lambda.zip .
としてS3にアップロードし、Lambdaで使おうとすると、よくこんなエラーに出会います。
Unable to import module 'lambda_function':
ImportError: /lib64/libc.so.6: version `GLIBC_2.27' not found
つまり、
- ネイティブライブラリ(C/C++など)の依存関係が Amazon Linux とズレる
- Windows/macOSでビルドしたwheelが Amazon Linux 上で動かない
- glibcやlibstdc++のバージョン違いで ImportError が出る
といった問題が起きるのです。
そこで Amazon Linux ベースの Docker イメージ上で pip install して、環境に合ったバイナリを含む site-packages を作る 必要があります。
今回紹介した方法は、この「Amazon Linux 環境に合わせる」という下準備をクリアするためのものです。
メリット・デメリット
メリット
- Lambda Layer を作らなくても容量制限を回避できる
- 巨大ライブラリ(scikit-learn, opencv など)も利用可能
- 必要に応じて S3 上でライブラリをバージョン管理できる
デメリット
- 毎回 /tmp に展開するため初回実行が遅い
- /tmp の容量はlambdaのエフェメラルストレージ( 512MB )に制限されているので注意
まとめ
- Layer では容量制限にひっかかる場合がある
- S3 + /tmp 解凍方式なら 50MB 制限を突破可能
- 実行速度・安定性を考えると Layer がベストだが、裏技的に使える方法
「軽量化(不要ファイル削除、--no-cache-dir 指定)」なども組み合わせると、より効率的に使えると思います。
最後に
皆さんはlambdaでサードパーティ製ライブラリを使用する際、どのように対応されていますか?
今回紹介した方法は、結構荒い方法なので、もっとスマートに対応できるようになりたいと思ってます。
良い方法があればコメントで教えてください。