Serverless Frameworkのプラグインを利用した外部モジュールの管理
2018年1月30日追記
執筆時点で検証したプラグインのバージョン(v1.2.0)から設定方法が大分変更されているようです。このプラグインを利用する際は最新バージョンの情報を参照するようにしてください。
はじめに
こんにちは、中山です。
Serverless Frameworkを利用したPythonベースのLambda関数を作成する際に、標準で同梱されている以外の外部モジュールを使いたい場合、みなさんはそのモジュールをどのように管理されているでしょうか。方法は色々と考えられます。単純にpipコマンドでインストールしたモジュールをデプロイメントパッケージに含める方法や、Amazon Linuxでモジュールをインストールしておく方法などです。それぞれメリット/デメリットがありますが、今回はUnitedIncome/serverless-python-requirementsというServerless Frameworkのプラグインで外部モジュールを管理する方法についてご紹介したいと思います。
このプラグインを利用するメリットはどういった点でしょうか。色々と考えられますが、私は以下の2点が大きいと思います。
- Serverless Frameworkのプラグインとして実装されているので
sls
コマンドとシームレスに連携できる - lambci/docker-lambdaを利用した非Pure Pythonなモジュールにも対応している
まず1点目について。ソースコードを確認すると分かりますが、デプロイメントパッケージを作成する前にモジュールをインストールする処理がHookに定義されています。そのため、 sls deploy
すると自動的にモジュールがインストールされ、デプロイメントパッケージに同梱することが可能です。この実装のメリットは、モジュールのインストールを意識することなく通常のServerless Frameworkのデプロイフローと同じ感覚で扱えるという点だと思います。つまり、pipコマンドを明示的に叩いたりする手間が省けます。もちろん、sls requirementsというコマンドも定義されているので明示的に実行することも可能です。
続いて2点目について。恐らくLambda関数を利用した経験のある方なら一度はハマるであろうネイティブライブラリ問題にも対応しています。非Pure Pythonで実装されているモジュールを、例えばMac上でインストールしてもLambda関数はLinuxベースのコンテナが利用されているため、そのままでは使えない場合があります。エラーメッセージ(「invalid ELF header」)が表示され悲しみにくれた経験がある方も多いでしょう。この問題に対して、今まではAmazon LinuxあるいはAmazon LinuxのDockerイメージを利用する方法などがよく使われていたかと思います。このプラグインではlambci/docker-imageというLambda関数の実行環境とほぼ同等のDockerイメージを利用しています。 custom
プロパティに dockerizePip: true
と定義するだけで利用可能です。
ただし、注意点があります。こちらのドキュメントに記載されているように、lambci/docker-lambdaはAWSが公式にサポートしているものではありません。個人の方がOSSで公開しているものです。大抵の環境で問題なく動作するかと思いますが、この点は注意しておいた方がよいと思います。
なお、本エントリを執筆する上で検証に利用した主要な各種ツールのバージョンは以下の通りです。バージョンによって結果が変更される可能性があるので、その点ご了承ください。
- Serverless Framework: 1.5.1
- Serverless Python Requirements: 1.2.0
使ってみる
説明が長くなりました。早速使ってみましょう。
インストール
インストールは簡単です。Serverless Frameworkで管理しているディレクトリ上で以下のコマンドを実行するだけです。コマンドを実行すると、 node_modules
というディレクトリに各種モジュールがインストールされます。
$ npm install --save serverless-python-requirements
Pure Pythonなモジュール
まずは最初にPure Pythonで実装されたモジュールを使ってみます。今回はほぼ標準モジュール的な扱いであるpytzを利用して、現在の時刻をUTCからJSTに変換して表示させてみます。以下のファイルを用意してください。
serverless.yml
frameworkVersion: ">=1.5.0" service: test provider: name: aws runtime: python2.7 cfLogs: true plugins: - serverless-python-requirements functions: hello: handler: handler.hello
plugins
プロパティで利用するプラグインを指定しています。
handler.py
from datetime import datetime import requirements import pytz def hello(event, context): return str(datetime.now(pytz.utc).astimezone(pytz.timezone('Asia/Tokyo')))
ハイライトした箇所に注目してください。 import requirements
でrequirements.pyをインポートしています。中身は単に sys.path
にモジュールへのパスを追加しているだけです。この文は利用したいモジュールより前に書く必要がある点に注意してください。
requirements.txt
pytz
ファイルを用意したらデプロイしてみます。
$ sls deploy -v Serverless: Creating Stack... Serverless: Checking Stack create progress... CloudFormation - CREATE_IN_PROGRESS - AWS::CloudFormation::Stack - test-dev CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket CloudFormation - CREATE_COMPLETE - AWS::S3::Bucket - ServerlessDeploymentBucket CloudFormation - CREATE_COMPLETE - AWS::CloudFormation::Stack - test-dev Serverless: Stack create finished... Serverless: Packaging Python requirements helper... Serverless: Packaging required Python packages... Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (1.49 MB)... Serverless: Updating Stack... <snip>
ハイライトしている箇所で requirements.txt
に記載したモジュールをデプロイメントパッケージに含めているのが確認できます。この時点でディレクトリは以下のようになっています。
$ tree -I 'node_modules' -L 2 -a . ├── .npmignore ├── .requirements │ ├── pytz │ └── pytz-2016.10.dist-info ├── .serverless │ ├── cloudformation-template-create-stack.json │ ├── cloudformation-template-update-stack.json │ └── test.zip ├── handler.py ├── requirements.py ├── requirements.pyc ├── requirements.txt └── serverless.yml 4 directories, 9 files
.requirements
ディレクトリに requirements.txt
で指定したpytzがインストールされていることが確認できます。また、 requirements.py
がコンパイルされ .pyc
ファイルが生成されています。デプロイメントパッケージ( .serverless/test.zip
) の中身を見るとpytzが同梱されていることが確認できます。
$ unzip -l .serverless/test.zip Archive: .serverless/test.zip Length Date Time Name --------- ---------- ----- ---- 192 01-21-2017 10:51 .npmignore 19398 01-21-2017 10:51 .requirements/pytz-2016.10.dist-info/DESCRIPTION.rst 4 01-21-2017 10:51 .requirements/pytz-2016.10.dist-info/INSTALLER <snip>
最後に実行してみましょう。
$ sls invoke -f hello -l "2017-01-21 19:52:49.224927+09:00" -------------------------------------------------------------------- START RequestId: bfe1f06f-dfc7-11e6-8d16-b56e041c9162 Version: $LATEST END RequestId: bfe1f06f-dfc7-11e6-8d16-b56e041c9162 REPORT RequestId: bfe1f06f-dfc7-11e6-8d16-b56e041c9162 Duration: 0.53 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 17 MB
正常に実行されているようです。Lambda関数のモジュールパスがどうなっているか確認すると、 sys.path
にモジュールへのパスが追加されていることが確認できます。
[ "/var/task", "/var/runtime/awslambda", "/var/runtime", "/usr/lib/python27.zip", "/usr/lib64/python2.7", "/usr/lib64/python2.7/plat-linux2", "/usr/lib64/python2.7/lib-tk", "/usr/lib64/python2.7/lib-old", "/usr/lib64/python2.7/lib-dynload", "/usr/local/lib64/python2.7/site-packages", "/usr/local/lib/python2.7/site-packages", "/usr/lib64/python2.7/site-packages", "/usr/lib/python2.7/site-packages", "/usr/lib64/python2.7/dist-packages", "/usr/lib/python2.7/dist-packages", "/var/task/.requirements" ]
非Pure Pythonなモジュール
続いて非Pure Pythonなモジュールを利用してみます。今回は有名な画像処理ライブラリであるPillowを利用します。当然ですが、このServerless Frameworkプラグインは内部的にDockerを利用しているためその環境は事前に用意しておく必要があります。
serverless.yml
frameworkVersion: ">=1.5.0" service: test provider: name: aws runtime: python2.7 cfLogs: true plugins: - serverless-python-requirements custom: dockerizePip: true functions: hello: handler: handler.hello
custom
プロパティでlambci/docker-lambdaを利用することを指定しています。
handler.py
import requirements from PIL import Image import urllib2 def hello(event, context): url = 'http://3.bp.blogspot.com/-fQOCSaHHl_8/U7O61b5i-uI/AAAAAAAAiTo/3AOyCEbtMIA/s800/tatemono_hakubutsukan.png' image_path = '/tmp/test.png' with open(image_path, 'w') as f: f.write(urllib2.urlopen(url).read()) return Image.open(image_path).filename
今回は簡単に画像ファイルをopenしてファイル名を表示するだけです。
requirements.txt
Pillow
ファイルが用意できたらデプロイしてみましょう。なお、lambci/docker-lambdaのイメージサイズはかなりでかいです。Pythonの場合1.61GBあります(Amazon LinuxのDockerイメージは約300MB)。まだイメージをローカルにpullしていない場合、回線の状況によってはそもそもイメージのダウンロードに時間が掛ると思います。また、CIツール/サービスによってこの部分がネックになる可能性がある点も注意が必要です。あと、進捗が端末に表示されないので処理が止まっているのか進んでいるのか分かりにくい。。。 sls deploy
でイメージをpullするのではなく、事前に以下のコマンドでイメージをダウンロードしておいた方がよいと思います。
$ docker pull lambci/lambda:build-python2.7 build-python2.7: Pulling from lambci/lambda 20dfd86accb2: Pull complete 184cd63a224a: Pull complete 6a7c292ef725: Pull complete 488c82c53ac3: Pull complete c3005cb5a993: Pull complete Digest: sha256:0324acc6bf7dcff814776ab4f2fad5a278aebaf51bc214bc952b90c651e94f8e Status: Downloaded newer image for lambci/lambda:build-python2.7
デプロイは以前と同じく以下のコマンドで実行可能です。
$ sls deploy -v Serverless: Creating Stack... Serverless: Checking Stack create progress... CloudFormation - CREATE_IN_PROGRESS - AWS::CloudFormation::Stack - test-dev CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket CloudFormation - CREATE_COMPLETE - AWS::S3::Bucket - ServerlessDeploymentBucket CloudFormation - CREATE_COMPLETE - AWS::CloudFormation::Stack - test-dev Serverless: Stack create finished... Serverless: Packaging Python requirements helper... Serverless: Packaging required Python packages... <snip>
デプロイが完了したら実行してみましょう。
$ sls invoke -f hello -l "/tmp/test.png" -------------------------------------------------------------------- START RequestId: 8bde2661-df9d-11e6-af55-4574a61f712c Version: $LATEST END RequestId: 8bde2661-df9d-11e6-af55-4574a61f712c REPORT RequestId: 8bde2661-df9d-11e6-af55-4574a61f712c Duration: 526.71 ms Billed Duration: 600 ms Memory Size: 1024 MB Max Memory Used: 21 MB
正常に実行されているようです。やりましたね。
まとめ
いかがだったでしょうか。
Serverless Frameworkのプラグインを利用した外部モジュールの管理方法についてご紹介しました。外部モジュールを含んだデプロイメントパッケージの管理方法は色々とあり、それぞれメリット/デメリットがあります。今回ご紹介させていただいた方法を含めて、自分の環境に合った方法を見つけると良いのではないでしょうか。
本エントリがみなさんの参考になれば幸いに思います。