Spring Boot + npm + Geb で入力フォームを作ってテストする ( その92 )( http-proxy-middleware の createProxyMiddleware 関数の引数 context には Proxy させない URI を後に書く )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その85 )( Node.js を 10.15.3 → 12.16.3 へ、npm を 6.9.0 → 6.14.5 へバージョンアップする ) で http-proxy-middleware を 0.19.1 → 1.0.3 にバージョンアップして createProxyMiddleware 関数を使うように変更したのですが、createProxyMiddleware 関数の引数 context に記述する URI の順番が今のままではダメなことに今更ながら気づきました。
Proxy させる URI を先に書いて Proxy させない URI をその後に書かないと、Proxy させないはずの URI も Proxy されていました。
期待した動作になるよう修正します。
参照したサイト・書籍
目次
- 今の記述だと Proxy させない URI も Proxy されてしまう
- createProxyMiddleware 関数の引数 context に Proxy させない URI を後に記述するよう修正する
手順
今の記述だと Proxy させない URI も Proxy されてしまう
bs-springboot-config.js の createProxyMiddleware 関数の記述は現在以下のように Proxy させない URI を先に、Proxy させる URI を後に記述していますが、
const {createProxyMiddleware} = require("http-proxy-middleware"); const proxy = createProxyMiddleware( [ // /css, /js, /vendor と *.html は Tomcat に転送しない "!/css/**/*", "!/js/**/*", "!/vendor/**/*", "!/**/*.html", "/**/*" ], {target: "http://localhost:8080"} ); ..........
npm run browser-sync:springboot
コマンドを実行して Browsersync だけ起動した後(Tomcat は起動しません)、
http://localhost:9080/css/common.css にアクセスすると "!/css/**/*"
の設定により Proxy されず /css/common.css が取得できると思っていましたが、Error occured while trying to proxy to: localhost:9080/css/common.css
のエラーメッセージが表示されます。
Browsersync から Tomcat に Proxy しようとして Tomcat にアクセスできないのでエラーになっているようです。
createProxyMiddleware 関数の引数 context に Proxy させない URI を後に記述するよう修正する
この問題は "/**/*"
の記述を一番最初にすることで解決できました。
bs-springboot-config.js を以下のように修正し、
const {createProxyMiddleware} = require("http-proxy-middleware"); const proxy = createProxyMiddleware( [ // /css, /js, /vendor と *.html は Tomcat に転送しない "/**/*", "!/css/**/*", "!/js/**/*", "!/vendor/**/*", "!/**/*.html" ], {target: "http://localhost:8080"} );
Browsersync を再起動してから http://localhost:9080/css/common.css にアクセスすると、今度は /css/common.css の内容が返ってきました。
Tomcat を起動して http://localhost:8080/inquiry/input/01 にアクセスすると入力画面1も表示されたので、問題ないようです。
履歴
2020/06/21
初版発行。
外部パッケージの Pillow と独自モジュール(.py ファイル)を Lambda Layer に配置する(後編)
概要
記事一覧はこちらです。
外部パッケージの Pillow と独自モジュール(.py ファイル)を Lambda Layer に配置する(前編) からの続きです。
参照したサイト・書籍
PyCharm and PYTHONPATH
https://stackoverflow.com/questions/28326362/pycharm-and-pythonpathInstall a Python package into a different directory using pip?
https://stackoverflow.com/questions/2915471/install-a-python-package-into-a-different-directory-using-pipAWS: Delete lambda layer still retains layer version history
https://stackoverflow.com/questions/60824745/aws-delete-lambda-layer-still-retains-layer-version-history
目次
- IntelliJ IDEA 上でユニットテストが成功するように設定する
- コマンドプロンプトから
python -m unittest -v
を実行してユニットテストが成功するように設定する - Docker コンテナから
python -m unittest -v
を実行してユニットテストが成功するように設定する - IntelliJ IDEA からユニットテストを実行する時に Docker コンテナで実行する(docker-compose 版)
- deploy する
- 画像をアップロードしてサムネイル画像を生成してみる
- 1度 Lambda を実行してから Lambda Layer 側の内容を変更して再度実行すると変更は反映されるのか?
- 最後に
手順
IntelliJ IDEA 上でユニットテストが成功するように設定する
今の状態だとモジュール検索のパスに my_module_layer/python が追加されていないので resize_service/handler.py に追加した from image_lib import resize_utils
に赤波線が表示されます。
IntelliJ IDEA でどうやって環境変数 PYTHONPATH を設定すればよいのか調べてみたのですが、my_module_layer/python でコンテキストメニューを表示してから「Mark Directory as」-「Sources Root」を選択して、
Source Folder(青色のフォルダーアイコンになります)にすれば、
resize_service/handler.py の from image_lib import resize_utils
から赤波線が消えました。
これで tests/test_resize.py で「Run 'Unittests for test_r...'」を選択してテストを実行すると、
テストが成功しました。breakpoint を設定して debug 実行することも出来ました。
コマンドプロンプトから 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
を実行してテストが成功するようになります。
ただし IntelliJ IDEA の Terminal からは成功しませんでした。(venv)
の文字が表示されていますが、venv/Scripts/activate.bat で activate している訳ではないようです。
Settings ダイアログの「Tools」-「Terminal」の「Environment Variables」に PYTHONPATH=D:\project-serverless\ksbysample-serverless\python-lambda-layer-project\my_module_layer\python
をセットすれば成功しましたが、あちこち設定するのは好みではないので Terminlal には設定せず pip install 専用にしようと思います。
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 を作成します。
最後に docker-compose run --rm python-unittest
を実行すると Docker コンテナで python -m unittest -v
が実行されてテストが成功します。
IntelliJ IDEA からユニットテストを実行する時に Docker コンテナで実行する(docker-compose 版)
IntelliJ IDEA のメインメニューから「File」-「Project Structure...」を選択して「Project Structure」ダイアログを表示します。
画面左側で「SDKs」を選択した後、中央で「+」-「Add Python SDK...」を選択します。
「Add Python Interpreter」ダイアログが表示されます。画面左側で「Docker Compose」を選択した後、右側の設定を以下の画像のようにしてから「OK」ボタンをクリックします。
「Name」に長い名前が設定されるので Remote Python 3.8.3 Docker Compose (python-lambda-layer-project)
に変更して「OK」ボタンをクリックしダイアログを閉じます。
IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurations」ダイアログを表示します。
画面左側で「Templates」-「Python tests」-「Unittests」を選択し、以下の内容を設定して「OK」ボタンをクリックします。
- 「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 実行します。
この時テストは成功しますが、まだ docker-compose で実行されていません。
再度「Run/Debug Configurations」ダイアログを表示すると左側に「Python tests」-「Unittests for test_resize.TestResizeService.test_resize」が追加されているので、「Python interpreter」を「Use SDK of module」→「Use specified interpreter」に変更します。
再度エディタから「Debug 'Unittests for test_r...'」を選択して debug 実行します。
Console に以下のように表示されて、
「Step Into」「Step Over」で進めると my_module_layer/python/image_lib/resize_utils.py の resize_image 関数まで進めることができます。
Services Tool Window を見ると python-unittest コンテナが実行されており、
Debug Tool Window で「Resume Program」ボタンを押してテストを最後まで実行すると成功します。
Services Tool Window を見ると pycharm_helptest_IU コンテナが残っているのは docker コマンドで実行した時と同じですが、python-unittest コンテナまで残っています。。。
「Run/Debug Configurations」ダイアログから docker-compose 実行時のオプションを確認すると --rm
オプションがありません。「Command and options」で設定しようとしましたが、何かエラーが出て設定できませんでした。どうやったら設定できるのだろう?
手動で残っているコンテナを削除することにします。自動で削除できないなら docker-compose ではなく docker コマンドで実行する方式に変更した方が良さそうです。
deploy する
resize_service を delploy します。
マネジメントコンソールで deploy された resize-service-dev-resize を見ると shared-package-layer と my-module-layer の2つの Lambda Layer が使用されていることが分かります。
画像をアップロードしてサムネイル画像を生成してみる
ksbysample-upload-bucket に sample2.jpg をアップロードすると、
ksbysample-resize-bucket に sample2_thumb.jpg が生成されています。
npx sls logs -f resize
を実行してログを見ても特にエラーは出ていませんでした。
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, 180
→ thumbnail_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
も出力されています。
thumbnail_size = 160, 90
に変更して my_module_layer のみ deploy してから sample.jpg をアップロードすると、
ログに出力される値は (320, 180)
のまま変わらず、既にインスタンスが生成されて再利用されているので Init Duration
は出力されません。
生成済みのインスタンスが破棄されたら変更された Lambda Layer の内容が反映されるのか確認したいので、20分程度何もせずに放置します。
約20分後に sample.jpg をアップロードすると、
Init Duration
が出ているにもかかわらず (320, 180)
のままでした。。。
原因は、マネジメントコンソールで my-module-layer のバージョンを見ると現在 12 なのですが、
resize-service-dev-resize 関数が参照している my-module-layer のバージョンは 11 で、最新の 12 に更新されていないためのようです。latest のような指定が出来ないのかも見てみましたが、設定できるところが見当たりませんでした。
ちなみに resize_service を何も変更せずに delploy すると、関数が参照している my-module-layer のバージョンは 12 に更新されて、
sample.jpg をアップロードすると今度は (160, 90)
が出力されました。
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
初版発行。
外部パッケージの Pillow と独自モジュール(.py ファイル)を Lambda Layer に配置する(前編)
概要
記事一覧はこちらです。
前々々回、前々回、前回の記事で作成した resize-image-app-project プロジェクト をベースに別プロジェクトを作成して、外部パッケージの Pillow と独自モジュール(.py ファイル)を Lambda Layer に配置するサンプルを作成します。
長いので2回に分けます。
尚、動作させてみて分かりましたが、Lambda Layer を更新した場合、利用している Lambda 関数の方も deploy し直さないと新しいバージョンの Lambda Layer を利用してくれません。利用する Lambda Layer は latest ではなくバージョン固定で指定されていて(マネジメントコンソールで AWS Lambda の関数を見るとどのバージョンを利用しているのか表示されます)、Lambda Layer を更新しても Lambda 関数が参照する Layer のバージョンは変わらないからです。
Pillow のようなパッケージなら delploy 時間を短縮するために Lambda Layer に配置するのはありだと思いますが、ちょっとした独自の共有モジュール程度なら serverless-package-external でいいんじゃないかなという気がします。
参照したサイト・書籍
AWS - Layers
https://www.serverless.com/framework/docs/providers/aws/guide/layers/Serverless Python Requirements
https://www.serverless.com/plugins/serverless-python-requirements/- 「Lambda Layer」の記述を参照しました。
Getting started with AWS Lambda Layers for Python
https://medium.com/@adhorn/getting-started-with-aws-lambda-layers-for-python-6e10b1f9a5dHow to publish and use AWS Lambda Layers with the Serverless Framework
https://www.serverless.com/blog/publish-aws-lambda-layers-serverless-framework/Reference CloudFormation Outputs
https://www.serverless.com/framework/docs/providers/aws/guide/variables#reference-cloudformation-outputs
目次
- python-lambda-layer-project プロジェクトを作成する
- resize-image-app-project プロジェクトから resize_service、tests ディレクトリをコピーし、boto3、Pillow、moto をインストールする
- 外部パッケージの Pillow を配置する Lambda Layer 用の shared_package_layer サブプロジェクトを作成する
- 独自モジュール(.py ファイル)を配置する Lambda Layer 用の my_module_layer サブプロジェクトを作成する
- resize_service ディレクトリ内のファイルを変更する
- 後編に続く
手順
python-lambda-layer-project プロジェクトを作成する
以下の手順で python-lambda-layer-project プロジェクトを作成します。具体的な手順は IntelliJ IDEA+Node.js+npm+serverless framework+Python の組み合わせで開発環境を構築して AWS Lambda を作成してみる 参照。
- python-lambda-layer-project の Empty Project を作成する。
- Python の仮想環境を作成する。
- Serverless Framework をローカルインストールする。
- .envrc を作成する。
npm install --save-dev serverless-python-requirements
resize-image-app-project プロジェクトから resize_service、tests ディレクトリをコピーし、boto3、Pillow、moto をインストールする
resize-image-app-project プロジェクトから resize_service、tests ディレクトリをコピーします。resize_service の下の .serverless、pycache ディレクトリは削除します。
IDEA の Terminal を起動して boto3、Pillow、moto をインストールします。
pip install boto3
pip install Pillow
pip install moto
コマンドラインと IntelliJ IDEA からユニットテストが成功することを確認します。
deploy が成功することも確認します(キャプチャは省略)。
外部パッケージの Pillow を配置する Lambda Layer 用の shared_package_layer サブプロジェクトを作成する
まずは外部パッケージの Pillow を Lambda Layer に配置します。ポイントは、Serverless Framework のドキュメントの AWS - Layers の設定は一切使わず、serverless-python-requirements プラグインのドキュメント の「Lambda Layer」に記載されている設定を使う点です。これ、分からなくて結構悩みました。。。
プロジェクトのルートディレクトリの下で npx sls create --template aws-python3 --path shared_package_layer
を実行して shared_package_layer サブプロジェクトを作成します。
serverless.yml を以下の内容に変更します。
service: shared-package-layer plugins: - serverless-python-requirements custom: pythonRequirements: dockerizePip: true # Lambda Layer の定義は pythonRequirements の下に記述する layer: name: shared-package-layer description: 共通パッケージ用 Lambda Layer provider: name: aws runtime: python3.8 stage: dev region: ap-northeast-1 resources: Outputs: # 他の Stack から Lambda Layer を参照できるようにする # Value に記載している "PythonRequirementsLambdaLayer" はこの文字列固定である SharedPackageLayer: Value: Ref: PythonRequirementsLambdaLayer
以下のファイルを削除・移動します。
- handler.py は不要なので削除します。
- resize_service/requirements.txt を shared_package_layer ディレクトリの下に移動します。
ここまででディレクトリ構成は以下のようになります。
deploy します。
マネジメントコンソールで確認すると Lambda Layer が作成されており(バージョンが 1 でないのは何度も試しているからです)、
「ダウンロード」ボタンをクリックして zip ファイルの中身を確認すると、
python ディレクトリの下に Pillow のパッケージが入っていました。
独自モジュール(.py ファイル)を配置する Lambda Layer 用の my_module_layer サブプロジェクトを作成する
外部パッケージだけでなく自分で作成したモジュール(.py ファイル)も Lambda Layer に配置してみます。ポイントは以下の2点です。
- こちらは Serverless Framework のドキュメントの AWS - Layers の設定を使用する。
- python ディレクトリの下に配置する。
プロジェクトのルートディレクトリの下で npx sls create --template aws-python3 --path my_module_layer
を実行して my_module_layer サブプロジェクトを作成します。
serverless.yml を以下の内容に変更します。
service: my-module-layer provider: name: aws runtime: python3.8 stage: dev region: ap-northeast-1 package: include: - ./python/** layers: # こちらの名称には "Layer" は付けない # "my-module-layer" の前半の "my-module" だけ取り出して MyModule にする MyModule: path: . name: my-module-layer description: 独自モジュール用 Lambda Layer compatibleRuntimes: - python3.8 resources: Outputs: # How to publish and use AWS Lambda Layers with the Serverless Framework # https://www.serverless.com/blog/publish-aws-lambda-layers-serverless-framework/ # # こちらの名称には "Layer" を付ける # "my-module-layer" から MyModuleLayer にする # 他の Stack から参照する時のキーになる MyModuleLayer: Value: # layers に書いた "MyModule" + "LambdaLayer" の文字列で参照する Ref: MyModuleLambdaLayer
handler.py は不要なので削除し、my_module_layer ディレクトリの下に python ディレクトリを作成します。
独自モジュール(.py ファイル)は my_module_layer/python の下に image_lib ディレクトリを作成し、その下に resize_utils.py を作成して、resize_service/handler.py の中に定義していた resize_image 関数を持ってくることにします。
my_module_layer/python/image_lib/resize_utils.py に以下の内容を記述します。
from PIL import Image thumbnail_size = 320, 180 def resize_image(image_path, resized_path): with Image.open(image_path) as image: image.thumbnail(thumbnail_size) image.save(resized_path)
ここまででディレクトリ構成は以下のようになります。
deploy します。
マネジメントコンソールで確認すると Lambda Layer が作成されており(バージョンが 1 でないのは何度も試しているからです)、
「ダウンロード」ボタンをクリックして zip ファイルの中身を確認すると、python ディレクトリの下に image_lib/resize_utils.py が入っていました。
resize_service ディレクトリ内のファイルを変更する
serverless.yml の以下の点を変更します。
service: resize-service provider: name: aws runtime: python3.8 stage: dev region: ap-northeast-1 iamRoleStatements: - Effect: "Allow" Action: - "s3:GetObject" Resource: - "arn:aws:s3:::ksbysample-upload-bucket/*" - Effect: "Allow" Action: - "s3:PutObject" Resource: - "arn:aws:s3:::ksbysample-resize-bucket/*" functions: resize: handler: handler.resize events: # Using existing buckets # https://www.serverless.com/framework/docs/providers/aws/events/s3#using-existing-buckets - s3: ksbysample-upload-bucket layers: # Stack名はマネジメントコンソールの CloudFormation > スタック で確認する - ${cf:shared-package-layer-dev.SharedPackageLayer} - ${cf:my-module-layer-dev.MyModuleLayer} resources: Resources: KsbysampleResizeBucket: Type: AWS::S3::Bucket Properties: BucketName: ksbysample-resize-bucket
- serverless-python-requirements プラグインを使用しないので plugins、custom の定義を削除します。
- functions - resize の下に layers を追加し、以下の2行を追加します。他の Stack の Outputs を参照する方法は Reference CloudFormation Outputs に記載があります。
- ${cf:shared-package-layer.SharedPackageLayer}
- ${cf:my-module-layer.MyModuleLayer}
handler.py の以下の点を変更します。
import logging import os import re import uuid from urllib.parse import unquote_plus import boto3 from image_lib import resize_utils logger = logging.getLogger() logger.setLevel(logging.INFO) def resize(event, context): s3_client = boto3.client('s3') for record in event['Records']: bucket = record['s3']['bucket']['name'] key = unquote_plus(record['s3']['object']['key']) # ディレクトリの場合には何もしない if key.endswith('/'): return tmpkey = key.replace('/', '') download_path = '/tmp/{}{}'.format(uuid.uuid4(), tmpkey) resized_path = '/tmp/resized-{}'.format(tmpkey) filename = key.split('/')[-1] dirname = re.sub(filename + '$', '', key) basename, ext = os.path.splitext(filename) resized_key = '{}{}_thumb{}'.format(dirname, basename, ext) s3_client.download_file(bucket, key, download_path) resize_utils.resize_image(download_path, resized_path) s3_client.upload_file(resized_path, "ksbysample-resize-bucket", resized_key) logger.info('サムネイルを生成しました({})'.format(resized_key))
- my_module_layer/python/image_lib/resize_utils.py に移動した resize_image 関数を削除します。
- 以下の記述も削除します。
from PIL import Image
thumbnail_size = 320, 180
- 以下の記述を追加します。
from image_lib import resize_utils
resize_image(download_path, resized_path)
→resize_utils.resize_image(download_path, resized_path)
に変更します。
後編に続く
履歴
2020/06/20
初版発行。
resize-image-app-project プロジェクトで作成した AWS Lambda のユニットテストを Docker コンテナ上で動作させる
概要
記事一覧はこちらです。
resize-image-app-project プロジェクトで作成した AWS Lambda のユニットテストを作成する(local動作版) でユニットテストを作成しましたが、Pillow は OS 依存のバイナリがあるので lambci/lambda:build-python3.8 の Docker Image 上でユニットテストを実行して動作確認する方法を調べてみます。
最初 lambci/lambda:python3.8 を利用してユニットテストを動かそうとしたのですが、
- lambci/lambda:python3.8 で Lambda の関数(resize_service/handler.resize)を呼び出す方法は分かったのですが、
python -m unittest -v
コマンドを実行する方法が全然分かりません。 - lambci / docker-lambda の Build Examples を見ると
docker run -it lambci/lambda:build-python3.8 bash
と記述されており、これで bash を起動するとpython -m unittest -v
コマンドを実行することが出来ました。
という理由で lambci/lambda:build-python3.8 の Docker Image を利用することにしました。
参照したサイト・書籍
lambci / docker-lambda
https://github.com/lambci/docker-lambdaIn Git for windows “git bash”, how to “print working directory” in Windows path format that is usable by cmd and Windows explorer?
https://stackoverflow.com/questions/44842275/in-git-for-windows-git-bash-how-to-print-working-directory-in-windows-path/48777179Configure an interpreter using Docker
https://www.jetbrains.com/help/pycharm/using-docker-as-a-remote-interpreter.html
目次
- lambci/lambda:build-python3.8 をベースに Pillow、moto をインストールしたカスタム Docker Image を作成する
- Docker コンテナでユニットテストを実行する
- IntelliJ IDEA からユニットテストを debug 実行する時にコードが Docker コンテナで動くようにする
- (メモ書き)lambci/lambda:python3.8 をベースにカスタム Docker Image を作成する
手順
lambci/lambda:build-python3.8 をベースに Pillow、moto をインストールしたカスタム Docker Image を作成する
プロジェクトのルートディレクトリ直下に Dockerfile を作成して以下の内容を記述します。
FROM lambci/lambda:build-python3.8 COPY resize_service/requirements.txt /tmp/requirements.txt RUN pip install --upgrade pip RUN pip install -r /tmp/requirements.txt RUN pip install moto
コマンドプロンプト(今回は git-bash)で docker build . -t lambci/lambda:build-python3.8-resize-image-app-project
を実行してカスタム Docker Image を作成します。
Docker コンテナでユニットテストを実行する
docker run --rm -it -v ``pwd -W``:/var/task:rw,delegated lambci/lambda:build-python3.8-resize-image-app-project bash
(pwd -W 前後の ` は1つだけに変更すること)を実行して Docker コンテナで bash を起動します。
/var/task
にプロジェクトのルートディレクトリがマウントされていることが確認できます。
pwd -W
の部分は Web の記事を見ると $(pwd)
と書かれていますが、
- Windows 上の git-bash だと
$(pwd)
は/d/project-serverless/ksbysample-serverless/resize-image-app-project
を返す。 - Windows で docker コマンドの -v オプションにマウント元を渡す時には
/d/...
ではなくD:\...
で渡さないとマウントされない(エラーになる)。
という理由で pwd -W
に変えました。試しに git-bash で実行してみると以下のようになります。
python -m unittest -v
を実行するとテストが1件成功しました。
exit コマンドで Docker コンテナから抜けます。
IntelliJ IDEA からユニットテストを debug 実行する時にコードが Docker コンテナで動くようにする
IntelliJ IDEA のメインメニューから「File」-「Project Structure...」を選択して「Project Structure」ダイアログを表示します。
画面左側で「SDKs」を選択した後、中央のリストの上部から「+」-「Add Python SDK...」を選択します。
「Add Python Interpreter」ダイアログが表示されます。画面左側で「Docker」を選択してから、画面右側の「Image name」で作成したカスタム Docker Image(lambci/lambda:build-python3.8-resize-image-app-project)を選択して「OK」ボタンをクリックします。
「Project Structure」ダイアログに戻ると「Remote Python 3.8.3 Docker (lambci/lambda:build-python3.8-resize-image-app-project)」が追加されています。「OK」ボタンをクリックしてダイアログを閉じます。
IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurationis」ダイアログを表示します。
前回作成した Python tests の設定で以下の点を変更します。
- 「Python initerpreter」を「Use SDK of module」→「Use specified interpreter」に変更した後、「Remote Python 3.8.3 Docker (lambci/lambda:build-python3.8-resize-image-app-project)」を選択します。
次に「Docker container settings」で右側のフォルダアイコンをクリックします。
「Edit Docker Container Settings」ダイアログが開くのでマウント先の Container Path を「/opt/project」→「/var/task」に変更します。
「Run/Debug Configurationis」ダイアログに戻ってから「Docker container settings」の設定が以下の画像のようになっていることを確認した後「OK」ボタンをクリックしてダイアログを閉じます。
以上で設定は完了です。次は debug 実行してみます。
まず IntelliJ IDEA の Services Tool Window で Docker コンテナが1つも存在しないことを確認してから、
IntelliJ IDEA のエディタ上で handler.resize(event, None)
の行に breakpoint を設定して「Debug 'Unittests for test_r...'」を選択します。
Console に以下の画像のように表示された後、breakpoint で止まります。
Services Tool Window を見ると debug 用に起動していると思われるコンテナ(実行中)と /pycharm_helpers_IU...
から始まるコンテナ(停止している)がありました。
Debugger で「Step Info」「Step Over」で処理を進めることができることも確認できます。
debug 実行を終了させると、
コンテナは /pycharm_helpers_IU...
から始まるもののみ残っていました。こちらは自動で削除されないようです。
(メモ書き)lambci/lambda:python3.8 をベースにカスタム Docker Image を作成する
今回は作成しませんでしたが build-
が付かない lambci/lambda:python3.8 をベースにカスタム Docker Image を作成する時は Dockerfile の書き方が少し異なるのでメモ書きとして残しておきます。
FROM に lambci/lambda:python3.8
を記述した Dockerfile を作成し、
FROM lambci/lambda:python3.8 COPY resize_service/requirements.txt /tmp/requirements.txt RUN pip install --upgrade pip RUN pip install -r /tmp/requirements.txt RUN pip install moto
docker build . -t lambci/lambda:python3.8-resize-image-app-project
を実行すると ERROR が出て Docker Image を作成できません。
Dockerfile に USER root
を追加します。
FROM lambci/lambda:python3.8 USER root COPY resize_service/requirements.txt /tmp/requirements.txt RUN pip install --upgrade pip RUN pip install -r /tmp/requirements.txt RUN pip install moto
再度 docker build . -t lambci/lambda:python3.8-resize-image-app-project
を実行すると今度は Docker Image が作成できます。
(..........途中省略..........)
履歴
2020/06/13
初版発行。
resize-image-app-project プロジェクトで作成した AWS Lambda のユニットテストを作成する(local動作版)
概要
記事一覧はこちらです。
S3 にアップロードされた画像ファイルから Lambda でサムネイル画像を生成してみる で作成した AWS Lambda のユニットテストを作成してみます。
前回 deploy するための外部ライブラリを収集するのに lambci/lambda:build-python3.8 という Docker Image を利用したので、おそらく Docker 上で動作確認することもできると思いますが、Testing Serverless Services という Web ページを見つけて local で動作させることもできるようなので試してみます。
参照したサイト・書籍
Testing Serverless Services
https://towardsdatascience.com/testing-serverless-services-59c688812a0dPython実践入門 ── 言語の力を引き出し、開発効率を高める WEB+DB PRESS plus
- unittest モジュールの使い方が記載されていて参考にしました。
spulec / moto
https://github.com/spulec/motoWhat is the fastest way to empty s3 bucket using boto3?
https://stackoverflow.com/questions/43326493/what-is-the-fastest-way-to-empty-s3-bucket-using-boto3
目次
- ディレクトリ名を resize-service → resize_service に変更する
- プロジェクトのルートディレクトリ直下に tests ディレクトリを作成してテストのサンプルを作成・実行する
- moto モジュールをインストールする
- test_resize.py にテストを実装して実行してみる。。。が NoCredentialsError が発生する
- NoCredentialsError のエラーを解消する
- IntelliJ IDEA から debug 実行してみる
- 最後にディレクトリ構成を記載する
手順
ディレクトリ名を resize-service → resize_service に変更する
プロジェクト名に -
(ハイフン) が含まれているとテストのファイルから AWS Lambda のファイルを import できません。IDEA で import 文を書いた時に赤波線が表示されて気づきました。
PEP 8 -- Style Guide for Python Code の Package and Module Names には all-lowercase names
にするか Underscores
が使用できると記載されていますが、ディレクトリ名を _
(アンダースコア) に変更して serverless.yml の service 名も同じに変更すると今度は deploy が成功しません。
The stack service name "resize_service-dev" is not valid. A service name should only contain alphanumeric (case sensitive) and hyphens. It should start with an alphabetic character and shouldn't exceed 128 characters.
のエラーが出ます。
ディレクトリ名は _
(アンダースコア) に変更し(resize_service)、serverless.yml の service 名は -
(ハイフン)のままにします(resize-service)。
service: resize-service ..........
ちなみに npx sls create --template aws-python3 --path xxxxxxxx_service
と create コマンドに渡すディレクトリ名に _
(アンダースコア)を含めた場合、
serverless.yml の service 名は -
(ハイフン)になりました。
プロジェクトのルートディレクトリ直下に tests ディレクトリを作成してテストのサンプルを作成・実行する
プロジェクトのルートディレクトリ直下に tests ディレクトリを作成し、その下に __init__.py
というファイルを作成します(中身は空)。
test_resize.py というファイルも作成し以下の内容を記述してから、
import unittest class TestResizeService(unittest.TestCase): def test_resize(self): None
コマンドプロンプトを起動して以下のコマンドを実行すると、unittest モジュールにテストのファイルが認識されて1件成功します。
venv\Scripts\activate
python -m unittest -v
テストを実行すると tests ディレクトリの下に __pycache__
というディレクトリが作成されますが git で管理する必要のないディレクトリなので .gitignore に __pycache__/
の記述を追加して無視されるようにします。
.......... # Ignore Python unittest __pycache__/ ..........
ちなみに __init__.py
というファイルがないと unittest モジュールにテストが認識されません。最初テストのファイルだけ作成して認識されなくて少し悩みました。。。
moto モジュールをインストールする
AWS Service をモック化してくれる moto モジュールをインストールします。
pip install moto
を実行します。依存するモジュールがいろいろインストールされるので少し時間がかかります。
test_resize.py にテストを実装して実行してみる。。。が NoCredentialsError が発生する
tests ディレクトリの下にテストで使用する sample.jpg をコピーし、s3_event.json というファイルを新規作成して S3 のイベントの JSON を記述します。
{ "Records": [ { "eventVersion": "2.1", "eventSource": "aws:s3", "awsRegion": "ap-northeast-1", "eventTime": "2020-06-10T00:45:25.995Z", "eventName": "ObjectCreated:Put", "userIdentity": { "principalId": "AWS:XXXXXXXXXXXXXXXXXXXXX" }, "requestParameters": { "sourceIPAddress": "xxx.xxx.xxx.xxx" }, "responseElements": { "x-amz-request-id": "11F4BEB2A6E4D924", "x-amz-id-2": "k2RJ6yeU8sk5tRp7ntfyM8QDmR58rF1TIdacJimdExhLOOSQ3N6pOtIAWXyk6pGjTeBGOBx995ELqaK53zhdAo6Ky/PZb08e" }, "s3": { "s3SchemaVersion": "1.0", "configurationId": "66e09001-2a37-4900-9a98-06da126cc896", "bucket": { "name": "ksbysample-upload-bucket", "ownerIdentity": { "principalId": "XXXXXXXXXXXXXX" }, "arn": "arn:aws:s3:::ksbysample-upload-bucket" }, "object": { "key": "sample.jpg", "size": 669286, "eTag": "febe0aaf4d817457776b6be293973442", "sequencer": "005EE02D28714EE2DC" } } } ] }
test_resize.py にテストを実装します。
import json import unittest import boto3 from moto import mock_s3 from resize_service import handler @mock_s3 class TestResizeService(unittest.TestCase): UPLOAD_BUCKET = 'ksbysample-upload-bucket' RESIZE_BUCKET = 'ksbysample-resize-bucket' def setUp(self): s3_client = boto3.client('s3') s3_client.create_bucket(Bucket=TestResizeService.UPLOAD_BUCKET) s3_client.create_bucket(Bucket=TestResizeService.RESIZE_BUCKET) def tearDown(self): s3 = boto3.resource('s3') upload_bucket = s3.Bucket(TestResizeService.UPLOAD_BUCKET) upload_bucket.objects.all().delete() upload_bucket.delete() resize_bucket = s3.Bucket(TestResizeService.RESIZE_BUCKET) resize_bucket.objects.all().delete() resize_bucket.delete() def test_resize(self): s3_client = boto3.client('s3') s3_client.upload_file('tests/sample.jpg', TestResizeService.UPLOAD_BUCKET, 'sample.jpg') with open('tests/s3_event.json', 'r') as f: event = json.load(f) handler.resize(event, None) thumb_object = s3_client.get_object(Bucket=TestResizeService.RESIZE_BUCKET, Key='sample_thumb.jpg') self.assertEqual(thumb_object['ResponseMetadata']['HTTPStatusCode'], 200) self.assertGreater(int(thumb_object['ResponseMetadata']['HTTPHeaders']['content-length']), 0) # 生成されたサムネイル画像をダウンロードすることも出来る(実際に作成される) # s3_client.download_file(TestResizeService.RESIZE_BUCKET, 'sample_thumb.jpg', # 'tests/sample_thumb.jpg')
python -m unittest -v
を実行してみると raise NoCredentialsError
のエラーが出てテストが失敗しました。。。
NoCredentialsError のエラーを解消する
Web で調べると AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY が設定されていないことが原因と書かれている記事を見かけるのですが、解決したという人としなかったという人を見かける上に、テストの方はモックの S3 に問題なくアクセスできていて今ひとつ良く分かりません。
試しに AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY をいくつかの方法で設定してみると以下の結果でした。
- test_resize メソッド内で AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY の環境変数を設定するのは NG。
- resize_service/handler.py で
s3_client = boto3.client('s3')
→s3_client = boto3.client('s3', aws_access_key_id='test', aws_secret_access_key='test')
に変更すると OK。 python -m unittest -v
を実行するコマンドプロンプトで AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY の環境変数を設定すると OK。
何となく原因が分かりました。おそらく以下の動作になっているのでしょう。
- test_resize.py が読み込まれる。
from resize_service import handler
により resize_service/handler.py が読み込まれる。- resize_service/handler.py のグローバルで
s3_client = boto3.client('s3')
が実行される。この時は boto3 の s3 クライアントはまだモックではなく Credentials の情報がセットされない。 - test_resize.py の TestResizeService.test_resize メソッドが実行される。この時は mock_s3 デコレータ により boto3 の s3 クライアントはモック化されて Credentials の情報がセットされる。
- 結果として test_resize.py 内の処理ではモックの S3 に問題なくアクセスできるが、resize_service/handler.py 内の処理でモックの S3 にアクセスしようとすると
raise NoCredentialsError
のエラーが出る。
resize_service/handler.py の s3_client = boto3.client('s3')
を記述する位置を resize 関数の最初に変更します。
.......... logger = logging.getLogger() logger.setLevel(logging.INFO) def resize_image(image_path, resized_path): with Image.open(image_path) as image: image.thumbnail(thumbnail_size) image.save(resized_path) def resize(event, context): s3_client = boto3.client('s3') for record in event['Records']: bucket = record['s3']['bucket']['name'] ..........
python -m unittest -v
を実行すると今度は成功しました。
IntelliJ IDEA から debug 実行してみる
test_resize.py 内の handler.resize(event, None)
の行に breakpoint をセットしてから、IDEA のエディタの左側に表示されている矢印をクリックして Debug 'Unittests for test_r...'
を選択します。
初回は FileNotFoundError: [WinError 3] 指定されたパスが見つかりません。: 'tests/sample.jpg'
のエラーが出てエラーになります。
IDEA のメインメニューから「Run」-「Edit Configurations...」を選択して「Run/Debug Configurations」ダイアログを表示した後、「Python tests」の下に表示されている項目を選択してから画面右側の「Working directory」の設定を D:\project-serverless\ksbysample-serverless\resize-image-app-project\tests
→ D:\project-serverless\ksbysample-serverless\resize-image-app-project
に変更します(プロジェクトのルートディレクトリにする)。
再度 Debug 'Unittests for test_r...'
を選択して debug 実行すると今度は breakpoint で止まり、「Step Into」ボタンを押せば resize_service/handler.py の resize 関数に進みます。
最後にディレクトリ構成を記載する
Python には標準で unittest モジュールが用意されていたり、moto という AWS Service をモック化するモジュールが存在したり、IntelliJ IDEA で Lambda のソースを debug 実行できたりして、Serverless は debug しにくいと思っていましたが結構開発しやすくて意外でした。
履歴
2020/06/13
初版発行。
IntelliJ IDEA で Java Flight Recorder を有効にして実行する
記事一覧はこちらです。
会社の IntelliJ IDEA で開発をしていた時に画面右上のボタンに Run with Java Flight Recorder のようなボタンを見かけた気がしていて、家に帰ってから IntellJ IDEA を起動して確認するとそんなボタンが見当たりません。
位置的には下の画像の赤枠のボタンのはずなのですが、「Run with Profiler」というラベルが出るだけでボタンが押せません。。。
調べてみると Profiling Tools and IntelliJ IDEA Ultimate のページが見つかりました。書かれたのは March 6, 2020 で最近ですね。
Settings ダイアログを開いて Java Profiler の設定を見ると中央のリストには何も表示されていません。ボタンが押せないはずです。
AdoptOpenJDK の Migration Guide を見ると Java Flight Recorder は OpenJDK 11 に入っているようなので試してみます。
Settings ダイアログの Java Profiler の設定の中央のリストで「+」ボタンを押すと「Java Flight Recorder」が出てきたので選択します。
中央のリストに「Java Flight Recorder」が追加されます。設定は「Default」のままにして「OK」ボタンを押してダイアログを閉じます。
画面右上の「Run with Profiler」のドロップダウンリストをクリックすると「Run 'Application' with 'Java Flight Recorder'」のメニューが表示されました。クリックして実行します。
Tomcat が起動して、更に初めて見る「Profiler」タブが表示されました。
「Profiler」タブをクリックすると以下の画面が表示されて、
「Stop Profiling and Show Results」リンクをクリックすると Profile 結果が表示されました。残念ながら見方が全然分かりません。。。
Java Flight Recorder が Java 11 から OpenJDK でも使えるようになったというのは見かけていましたが、使うのが面倒そうだったので1度も試していなかったんですよね。IntelliJ IDEA から実行できるようになったのであれば試してみようと思います。
IntelliJ IDEA を 2020.1.1 → 2020.1.2 へ、Git for Windows を 2.26.2 → 2.27.0 へバージョンアップ
IntelliJ IDEA を 2020.1.1 → 2020.1.2 へバージョンアップする
IntelliJ IDEA の 2020.1.2 がリリースされているのでバージョンアップします。
- IntelliJ IDEA 2020.1.2 is Available!
https://blog.jetbrains.com/idea/2020/06/intellij-idea-2020-1-2/
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。
「IDE and Plugin Updates」ダイアログが表示されます。右下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。
Plugin の update も表示されました。このまま「Update and Restart」ボタンをクリックします。
Patch がダウンロードされて IntelliJ IDEA が再起動します。
IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。
IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2020.1.2 へバージョンアップされていることを確認します。
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。
Project Tool Window で src/test でコンテキストメニューを表示して「Run 'All Tests' with Coverage」を選択し、テストが全て成功することを確認します。
Git for Windows を 2.26.2 → 2.27.0 へバージョンアップする
Git for Windows の 2.27.0 がリリースされていたのでバージョンアップします。
https://gitforwindows.org/ の「Download」ボタンをクリックして Git-2.27.0-64-bit.exe をダウンロードします。
Git-2.27.0-64-bit.exe を実行します。
「Git 2.27.0 Setup」ダイアログが表示されます。インストーラーの画面を一通り見たいので「Only show new options」のチェックを外してから [Next >] ボタンをクリックします。
「Select Components」画面が表示されます。「Git LFS(Large File Support)」だけチェックした状態で [Next >]ボタンをクリックします。
「Choosing the default editor used by Git」画面が表示されます。「Use Vim (the ubiquitous text editor) as Git's default editor」が選択された状態で [Next >]ボタンをクリックします。
「Adjusting your PATH environment」画面が表示されます。中央の「Git from the command line and also from 3rd-party software」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Choosing HTTPS transport backend」画面が表示されます。「Use the OpenSSL library」が選択されていることを確認後、[Next >]ボタンをクリックします。
「Configuring the line ending conversions」画面が表示されます。一番上の「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。
「Choose the default behavior of
git pull
」画面が表示されます(新画面)。「Default (fast-forward or merge)」が選択されていることを確認した後、[Next >]ボタンをクリックします。「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Next >]ボタンをクリックします。
「Configuring experimental options」画面が表示されます。何もチェックせずに [Install]ボタンをクリックします。
インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View Release Notes」のチェックを外した後、[Next >]ボタンをクリックしてインストーラーを終了します。
コマンドプロンプトを起動して
git --version
を実行し、git のバージョンがgit version 2.27.0.windows.1
になっていることを確認します。特に問題はないようですので、2.27.0 で作業を進めたいと思います。