かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

外部パッケージの Pillow と独自モジュール(.py ファイル)を Lambda Layer に配置する(後編)

概要

記事一覧はこちらです。

外部パッケージの Pillow と独自モジュール(.py ファイル)を Lambda Layer に配置する(前編) からの続きです。

参照したサイト・書籍

  1. PyCharm and PYTHONPATH
    https://stackoverflow.com/questions/28326362/pycharm-and-pythonpath

  2. Install a Python package into a different directory using pip?
    https://stackoverflow.com/questions/2915471/install-a-python-package-into-a-different-directory-using-pip

  3. AWS: Delete lambda layer still retains layer version history
    https://stackoverflow.com/questions/60824745/aws-delete-lambda-layer-still-retains-layer-version-history

目次

  1. IntelliJ IDEA 上でユニットテストが成功するように設定する
  2. コマンドプロンプトから python -m unittest -v を実行してユニットテストが成功するように設定する
  3. Docker コンテナから python -m unittest -v を実行してユニットテストが成功するように設定する
  4. IntelliJ IDEA からユニットテストを実行する時に Docker コンテナで実行する(docker-compose 版)
  5. deploy する
  6. 画像をアップロードしてサムネイル画像を生成してみる
  7. 1度 Lambda を実行してから Lambda Layer 側の内容を変更して再度実行すると変更は反映されるのか?
  8. 最後に

手順

IntelliJ IDEA 上でユニットテストが成功するように設定する

今の状態だとモジュール検索のパスに my_module_layer/python が追加されていないので resize_service/handler.py に追加した from image_lib import resize_utils に赤波線が表示されます。

f:id:ksby:20200617231806p:plain

IntelliJ IDEA でどうやって環境変数 PYTHONPATH を設定すればよいのか調べてみたのですが、my_module_layer/pythonコンテキストメニューを表示してから「Mark Directory as」-「Sources Root」を選択して、

f:id:ksby:20200617232232p:plain

Source Folder(青色のフォルダーアイコンになります)にすれば、

f:id:ksby:20200617232630p:plain

resize_service/handler.py の from image_lib import resize_utils から赤波線が消えました。

f:id:ksby:20200617232831p:plain

これで tests/test_resize.py で「Run 'Unittests for test_r...'」を選択してテストを実行すると、

f:id:ksby:20200617233020p:plain

テストが成功しました。breakpoint を設定して debug 実行することも出来ました。

f:id:ksby:20200617233220p:plain

コマンドプロンプトから python -m unittest -v を実行してユニットテストが成功するように設定する

venv/Scripts/activate.bat、venv/Scripts/deactivate.bat 内で環境変数 PYTHONPATH に my_module_layer/python絶対パス(自分の環境では D:\project-serverless\ksbysample-serverless\python-lambda-layer-project\my_module_layer\python)をセットします。

venv/Scripts/activate.bat の最後に以下の記述を追加します。

if defined PYTHONPATH (
    set _OLD_PYTHONPATH=%PYTHONPATH%
)
set PYTHONPATH=%PYTHONPATH%;D:\project-serverless\ksbysample-serverless\python-lambda-layer-project\my_module_layer\python

venv/Scripts/deactivate.bat の最後に以下の記述を追加します。

set PYTHONPATH=
if defined _OLD_PYTHONPATH (
    set PYTHONPATH=%_OLD_PYTHONPATH%
)
set _OLD_PYTHONPATH=

これでコマンドプロンプトから python -m unittest -v を実行してテストが成功するようになります。

f:id:ksby:20200618064915p:plain f:id:ksby:20200618065108p:plain

ただし IntelliJ IDEA の Terminal からは成功しませんでした。(venv) の文字が表示されていますが、venv/Scripts/activate.bat で activate している訳ではないようです。

f:id:ksby:20200618072704p:plain

Settings ダイアログの「Tools」-「Terminal」の「Environment Variables」に PYTHONPATH=D:\project-serverless\ksbysample-serverless\python-lambda-layer-project\my_module_layer\python をセットすれば成功しましたが、あちこち設定するのは好みではないので Terminlal には設定せず pip install 専用にしようと思います。

f:id:ksby:20200618073206p:plain f:id:ksby:20200618073411p:plain

Docker コンテナから python -m unittest -v を実行してユニットテストが成功するように設定する

今回は volume で指定したいディレクトリが2ヶ所あるので docker-compose で実行します。

まずはプロジェクトのルートディレクトリ直下に Dockerfile を新規作成し、以下の内容を記述します。

FROM lambci/lambda:build-python3.8

COPY shared_package_layer/requirements.txt /tmp/requirements.txt
RUN pip install --upgrade pip
RUN pip install -r /tmp/requirements.txt
RUN pip install moto

ENV PYTHONPATH "${PYTHONPATH}:/opt/python"

次にプロジェクトのルートディレクトリ直下に docker-compose.yml を新規作成し、以下の内容を記述します。

# docker-compose build
# docker-compose run --rm python-unittest

version: '3'

services:
  python-unittest:
    build:
      context: .
    image: lambci/lambda:build-python3.8-python-lambda-layer-project
    container_name: python-unittest
    volumes:
      - .:/var/task
      - ./my_module_layer/python:/opt/python
    command: python -m unittest -v

コマンドプロンプトを起動して docker-compose build を実行し Docker Image を作成します。

f:id:ksby:20200619045944p:plain f:id:ksby:20200619050041p:plain f:id:ksby:20200619050138p:plain f:id:ksby:20200619050232p:plain

最後に docker-compose run --rm python-unittest を実行すると Docker コンテナで python -m unittest -v が実行されてテストが成功します。

f:id:ksby:20200619050535p:plain

IntelliJ IDEA からユニットテストを実行する時に Docker コンテナで実行する(docker-compose 版)

IntelliJ IDEA のメインメニューから「File」-「Project Structure...」を選択して「Project Structure」ダイアログを表示します。

画面左側で「SDKs」を選択した後、中央で「+」-「Add Python SDK...」を選択します。

f:id:ksby:20200619051224p:plain

「Add Python Interpreter」ダイアログが表示されます。画面左側で「Docker Compose」を選択した後、右側の設定を以下の画像のようにしてから「OK」ボタンをクリックします。

f:id:ksby:20200619051325p:plain

「Name」に長い名前が設定されるので Remote Python 3.8.3 Docker Compose (python-lambda-layer-project) に変更して「OK」ボタンをクリックしダイアログを閉じます。

f:id:ksby:20200619051657p:plain

IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurations」ダイアログを表示します。

画面左側で「Templates」-「Python tests」-「Unittests」を選択し、以下の内容を設定して「OK」ボタンをクリックします。

f:id:ksby:20200619052511p:plain

  • Python interpreter」で「Use specified interpreter」を選択した後「Remote Python 3.8.3 Docker Compose (python-lambda-layer-project)」を選択します。
  • 「Working directory」に D:\project-serverless\ksbysample-serverless\python-lambda-layer-project を入力します。

設定は以上で完了です。エディタから「Debug 'Unittests for test_r...'」を選択して1回 debug 実行します。

f:id:ksby:20200619052918p:plain

この時テストは成功しますが、まだ docker-compose で実行されていません。

再度「Run/Debug Configurations」ダイアログを表示すると左側に「Python tests」-「Unittests for test_resize.TestResizeService.test_resize」が追加されているので、「Python interpreter」を「Use SDK of module」→「Use specified interpreter」に変更します。

f:id:ksby:20200619055335p:plain

再度エディタから「Debug 'Unittests for test_r...'」を選択して debug 実行します。

Console に以下のように表示されて、

f:id:ksby:20200619055831p:plain

「Step Into」「Step Over」で進めると my_module_layer/python/image_lib/resize_utils.py の resize_image 関数まで進めることができます。

f:id:ksby:20200619060002p:plain

Services Tool Window を見ると python-unittest コンテナが実行されており、

f:id:ksby:20200619060419p:plain

Debug Tool Window で「Resume Program」ボタンを押してテストを最後まで実行すると成功します。

f:id:ksby:20200619060520p:plain

Services Tool Window を見ると pycharm_helptest_IU コンテナが残っているのは docker コマンドで実行した時と同じですが、python-unittest コンテナまで残っています。。。

f:id:ksby:20200619061502p:plain

「Run/Debug Configurations」ダイアログから docker-compose 実行時のオプションを確認すると --rm オプションがありません。「Command and options」で設定しようとしましたが、何かエラーが出て設定できませんでした。どうやったら設定できるのだろう?

f:id:ksby:20200619061722p:plain

手動で残っているコンテナを削除することにします。自動で削除できないなら docker-compose ではなく docker コマンドで実行する方式に変更した方が良さそうです。

deploy する

resize_service を delploy します。

f:id:ksby:20200619062817p:plain f:id:ksby:20200619062916p:plain

マネジメントコンソールで deploy された resize-service-dev-resize を見ると shared-package-layer と my-module-layer の2つの Lambda Layer が使用されていることが分かります。

f:id:ksby:20200619063233p:plain

画像をアップロードしてサムネイル画像を生成してみる

ksbysample-upload-bucket に sample2.jpg をアップロードすると、

f:id:ksby:20200619063801p:plain f:id:ksby:20200619063900p:plain

ksbysample-resize-bucket に sample2_thumb.jpg が生成されています。

f:id:ksby:20200619064026p:plain f:id:ksby:20200619064106p:plain

npx sls logs -f resize を実行してログを見ても特にエラーは出ていませんでした。

f:id:ksby:20200619064233p:plain

1度 Lambda を実行してから Lambda Layer 側のみ内容を変更して再度実行すると変更は反映されるのか?

my_module_layer/python/image_lib/resize_utils.py で thumbnail_size の値をログに出力するよう変更してから、

import logging

from PIL import Image

logger = logging.getLogger()
logger.setLevel(logging.INFO)

thumbnail_size = 320, 180


def resize_image(image_path, resized_path):
    logger.info(thumbnail_size)

    with Image.open(image_path) as image:
        image.thumbnail(thumbnail_size)
        image.save(resized_path)

thumbnail_size = 320, 180thumbnail_size = 160, 90 に変更して my_module_layer のみ deploy したら変更が反映されるのか確認します。

まずは念の為 resize_service → my_module_layer → shared_package_layer の順に remove して、逆の順番で deploy し直します。thumbnail_size = 320, 180 の状態です。

sample.jpg をアップロードしてログを確認すると (320, 180) と出力されており、初回なので Init Duration も出力されています。

f:id:ksby:20200620090133p:plain

thumbnail_size = 160, 90 に変更して my_module_layer のみ deploy してから sample.jpg をアップロードすると、

f:id:ksby:20200620090520p:plain

ログに出力される値は (320, 180) のまま変わらず、既にインスタンスが生成されて再利用されているので Init Duration は出力されません。

生成済みのインスタンスが破棄されたら変更された Lambda Layer の内容が反映されるのか確認したいので、20分程度何もせずに放置します。

約20分後に sample.jpg をアップロードすると、

f:id:ksby:20200620093133p:plain

Init Duration が出ているにもかかわらず (320, 180) のままでした。。。

原因は、マネジメントコンソールで my-module-layer のバージョンを見ると現在 12 なのですが、

f:id:ksby:20200620093428p:plain

resize-service-dev-resize 関数が参照している my-module-layer のバージョンは 11 で、最新の 12 に更新されていないためのようです。latest のような指定が出来ないのかも見てみましたが、設定できるところが見当たりませんでした。

f:id:ksby:20200620093650p:plain

ちなみに resize_service を何も変更せずに delploy すると、関数が参照している my-module-layer のバージョンは 12 に更新されて、

f:id:ksby:20200620094059p:plain

sample.jpg をアップロードすると今度は (160, 90) が出力されました。

f:id:ksby:20200620094252p:plain

Lambda Layer を deploy した場合には、それを利用する関数側も deploy しないと反映されない、という結果でした。latest で指定できるようにならないかな。。。

最後に

まとめてみると、

  • Python + Serverless Framework で Lambda Layer を使うなら serverless-python-requirements プラグインの「Lambda Layer」の説明を見ること。Serverless Framework のドキュメントの AWS - Layers ではない。
  • Lambda Layer を deploy したら、それを利用する関数も deploy し直すこと。そうしないと更新された Layer の最新バージョンを参照してくれない。
  • Lambda Layer は Python のサイズの大きなパッケージを配置する程度に留めた方がよいのかもしれない(deploy 時間短縮のため)。

履歴

2020/06/20
初版発行。