AWS Lambda Pythonをlambda-uploaderでデプロイ
AWS Lambda を開発する際には
- コードを書く
- ZIP で固めてアップロードする
- サンプルイベントをインプットに Lambda 関数をテスト実行する
- CloudWatch Logs でログを確認してデバッグ
というフローが発生します。
今回は lambda-uploader を使い、 Step2の手順、つまり、コードのZIP化と AWS Lambda へのアップロードをコマンド一発で実行する方法について解説します。AWS Lambda では依存ライブラリも含めてZIP化しないといけないため、多くの人が一度は頭を悩まし、効率化を追求したくなるステップかと思います。
python-lambda-local と組み合わせることで
- python-lambda-local を使ってローカル環境で開発
- lambda-uploader で AWS Lambda にデプロイ
という AWS Lambda Python に特化した開発フローの出来上がりです。
lambda-uploader ができること
lambda-uploader は AWS Lambda Python 向けのツールです。
- コードの ZIP 化
- コードの AWS Lambda へのアップロード
- AWS Lambda 設定の更新
などが可能です。
IAM 周りの事前準備
Lambda 関数作成時に IAM ロールを指定します。そのため、デプロイするユーザーは iam:PassRole
権限が必要です。
また、Lambda 関数用のロールも事前に作成しておいてください。
- Lambda 関数が利用するリソースの操作権限
- プルモデル Lambda の場合はイベントソースをポーリングする権限
- プッシュモデル Lambda の場合はイベントソースが Lambda 関数を呼び出す権限
などが必要です。
Lambda の権限周りの詳細は公式ドキュメントをご確認ください。
http://docs.aws.amazon.com/lambda/latest/dg/intro-permission-model.html
インストール方法
virtualenv を使って閉じた Python 環境を用意します。
$ virtualenv uploader $ source uploader/bin/activate
pip で lambda-uploader をインストールします。
$ pip install lambda-uploader # 最新のリリース版をインストール $ pip install git+https://github.com/rackerlabs/lambda-uploader # git main branch をインストール
ヘルプコマンドを叩いて、インストールできていることを確認します。
$ lambda-uploader --version version 0.2.1
デプロイしてみる
ライブラリ requests を利用した Lambda 関数をデプロイしてみます。
Lambda 関数の定義ファイルの用意
まずは lambda-uploder を使ったデプロイでコアとなる lambda.json
という JSON 形式の Lambda 関数の定義ファイルを用意します。
{ "name": "Canary", "description": "lambda-uploader w/ 3rd party packages", "region": "ap-northeast-1", "handler": "canary.lambda_handler", "role": "arn:aws:iam::123456789012:role/lambda_canary_execution", "timeout": 300, "memory": 128 }
- name は Lambda 関数名です。関数名をキーに関数の新規・更新処理が行われます。
- handler には
ファイル名.ハンドラー関数名
を指定しましょう。 - role は Lambda 関数の Execution Role の arn です。事前に定義しておきましょう。
あとは項目名の通りで迷うことはないかと思います。
Lambda 関数の定義
関数(canary.py
)を用意します。blueprint-canary をベースに 3rd パーティーの requests ライブラリを使うように一部書き換えているだけです。
from datetime import datetime import requests SITE = 'https://www.amazon.com/' # URL of the site to check EXPECTED = 'Online Shopping' # String expected to be on the page def validate(res): '''Return False to trigger the canary Currently this simply checks whether the EXPECTED string is present. However, you could modify this to perform any number of arbitrary checks on the contents of SITE. ''' return EXPECTED in res def lambda_handler(event, context): print('Checking {} at {}...'.format(SITE, event['time'])) try: if not validate(requests.get(SITE).text): raise Exception('Validation failed') except: print('Check failed!') raise else: print('Check passed!') return event['time'] finally: print('Check complete at {}'.format(str(datetime.now())))
依存ライブラリは requirements.txt で管理し、pip 経由でインストールします。
$ cat requirements.txt requests $ pip install -r requirements.txt
requests モジュールが必要という情報はレポジトリ管理しますが、requests モジュールそのものはソースコード管理しない戦略です。
サンプルイベントの用意
最後に、ローカル環境から Lambda 関数を invoke できるようにサンプルイベントファイル(event.json
)も用意します。
{ "account": "123456789012", "region": "us-east-1", "detail": {}, "detail-type": "Scheduled Event", "source": "aws.events", "time": "1970-01-01T00:00:00Z", "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", "resources": [ "arn:aws:events:us-east-1:123456789012:rule/my-schedule" ] }
カレントディレクトリにあるファイルは以下のようになります。
$ tree . . |-- canary.py |-- event.json |-- lambda.json `-- requirements.txt 0 directories, 4 files
AWS Lambda にデプロイ
カレントディレクトリで $ Lambda-uploader
コマンドを叩けば、デプロイ完了です。
$ lambda-uploader λ Building Package λ Uploading Package λ Fin
requirements.txt
で指定した 3rd パーティライブラリ(や Lambda 関数実行には不要なライブラリ)も含めて ZIP 化&アップロードされます。
親ディレクトリにいる時は $ lambda-uploader ./canary
のようにコマンド実行することもできます。
デプロイした Lambda 関数を確認
マネージメントコンソールからデプロイした Lambda 関数を確認しましょう。
API でデプロイした関数を確認しましょう。
$ aws lambda get-function --function-name Canary { "Code": { "RepositoryType": "S3", "Location": "https://awslambda-ap-ne-1-tasks.s3-ap-northeast-1.amazonaws.com/snapshots/123456789012/Canary-6b7ee826-45a8-4904-8a96-b94721d7eeba?x-amz-security-token=..." }, "Configuration": { "Version": "$LATEST", "CodeSha256": "sALDX0piM8OddamwnbUDVDWfDjjA7IAEVK3I1iqj2X8=", "FunctionName": "Canary", "MemorySize": 128, "CodeSize": 3752046, "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:Canary", "Handler": "canary.lambda_handler", "Role": "arn:aws:iam::123456789012:role/lambda_basic_execution", "Timeout": 300, "LastModified": "2015-11-07T08:28:20.160+0000", "Runtime": "python2.7", "Description": "lambda-uploader w/ 3rd party packages" } }
デプロイした Lambda 関数を実行
デプロイした Lambda 関数 Canary を invoke してみましょう。 サンプルイベントとして、先ほど用意した event.json ファイルを指定します。
$ aws lambda invoke \ --invocation-type RequestResponse \ --function-name Canary \ --payload file://event.json \ outputfile.txt { "StatusCode": 200 } $ cat outputfile.txt "1970-01-01T00:00:00Z"
成功です。
Lambda 関数の更新の場合
Lambda-uploader は関数名をキーにして
- 存在しなければ、新規登録処理(
create-function
) - 存在すれば、コードと設定の更新処理(
update-function-code
&update-function-configuration
)
を行います。
コードと Lambda 関数設定を変更して再デプロイしてみます。
$ aws lambda get-function --function-name Canary { "Code": { "RepositoryType": "S3", "Location": "https://awslambda-ap-ne-1-tasks.s3-ap-northeast-1.amazonaws.com/snapshots/123456789012/Canary-895d00d9-2a63-41b7-9711-2f6261faaacf?x-amz-security-token=..." }, "Configuration": { "Version": "$LATEST", "CodeSha256": "4LOuCyfuKoS+vyECl0FkerJElCG6EKjprs8tJBbzmHc=", "FunctionName": "Canary", "MemorySize": 128, "CodeSize": 3752529, "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:Canary", "Handler": "canary.lambda_handler", "Role": "arn:aws:iam::123456789012:role/lambda_basic_execution", "Timeout": 180, "LastModified": "2015-11-07T08:57:10.772+0000", "Runtime": "python2.7", "Description": "updated description" } } $ aws lambda invoke \ --invocation-type RequestResponse \ --function-name Canary \ --payload file://event.json \ outputfile.txt { "StatusCode": 200 } $ cat outputfile.txt "Check passed!"
デプロイ時の細かい設定について
バージョニングを有効にする
- lambda-uploader 時に
-p/--publish
オプションを有効にする - lambda.json ファイルで属性
"publish" : true
を追加する
と Lambda 関数がバージョニングされます。
エイリアス設定する
lambda-uploader 時に
- エイリアス名(
--alias
) - エイリアスの説明(
--alias-description
)
を指定すると、デプロイと同時にエイリアス更新もできます。
エイリアス機能はバージョニングされているのが前提のため、--publish
オプションもつけてください。
$ lambda-uploader --publish --alias test --alias-description "my first test alias" λ Building Package λ Uploading Package λ Fin $ aws lambda list-aliases --function-name Canary { "Aliases": [ { "AliasArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:Canary:test", "FunctionVersion": "1", "Name": "test", "Description": "my first test alias" } ] }
エイリアス名をキーにして
- 存在しなければ、新規登録処理(
create-alias
) - 存在すれば、更新処理(
update-alias
)
を行います。
試しにエイリアスを更新してみましょう。
$ lambda-uploader --publish --alias test --alias-description "my 2nd test alias" λ Building Package λ Uploading Package λ Fin $ aws lambda list-aliases --function-name Canary { "Aliases": [ { "AliasArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:Canary:test", "FunctionVersion": "2", "Name": "test", "Description": "my 2nd test alias" } ] }
デプロイパイプラインをもう少し詳しく見てみる
ここからは若干趣味の領域です。 興味のない方は読み飛ばしてください。
- 作業ディレクトリを作成し、コードをまとめる
- コードを ZIP 化
- 作業ディレクトリの削除
- AWS Lambda にデプロイ
という処理から成り立っています。
このパイプライン処理を少し深追いしてみます。
コードのZIP化だけをする
--no-upload
オプションをつけると、ZIP 化までの処理を行い、AWS Lambda へのアップロードは行いません。--no-clean
オプションをつけると、作業ディレクトリを削除しません。
verbose オプション(-V
) を有効にして実行してみましょう。
$ lambda-uploader --no-upload --no-clean -V λ Building Package INFO:lambda_uploader.package:Copying site packages INFO:lambda_uploader.utils:Copying source files INFO:lambda_uploader.package:Creating zipfile λ Fin $ ls -al total 3700 drwxrwxr-x 3 ec2-user ec2-user 4096 Nov 7 08:51 . drwx------ 18 ec2-user ec2-user 4096 Nov 7 08:38 .. drwxrwxr-x 4 ec2-user ec2-user 4096 Nov 7 08:51 .lambda_package -rw-rw-r-- 1 ec2-user ec2-user 934 Nov 7 07:42 canary.py -rw-rw-r-- 1 ec2-user ec2-user 300 Nov 3 06:43 event.json -rw-rw-r-- 1 ec2-user ec2-user 251 Nov 7 07:15 lambda.json -rw-rw-r-- 1 ec2-user ec2-user 3752380 Nov 7 08:51 lambda_function.zip -rw-rw-r-- 1 ec2-user ec2-user 22 Nov 7 08:29 outputfile.txt -rw-rw-r-- 1 ec2-user ec2-user 9 Nov 7 07:04 requirements.txt
lambda_function.zip
が AWS Lambda にアップロードする ZIP ファイルです。4MB 近くあります。
.lambda_package
ディレクトリ以下が作業ディレクトリです。
依存ライブラリの指定方法
デプロイ時に依存パッケージを何も明示的に指定しなければ requirements.txt
が利用されます。
Lambda.json
ファイルの "requirements" で依存パッケージを指定することもできます。
{ "name": "canary", ... "requirements": ["requests"], ... }
2重管理を避けるために、requests.txt
に一本化したほうが良いでしょう。
何が ZIP 化されるのか
$ ls -l --block-size=MB *zip -rw-rw-r-- 1 ec2-user ec2-user 4MB Nov 7 08:32 lambda_function.zip
4MB と思ったより大きいですね。 ZIP ファイルの中身をのぞいてみてみましょう。
$ unzip -l lambda_function.zip Archive: lambda_function.zip Length Date Time Name --------- ---------- ----- ---- 315 11-07-2015 08:32 easy_install.pyc 22 11-07-2015 08:32 outputfile.txt 126 11-07-2015 08:32 easy_install.py 9 11-07-2015 08:32 requirements.txt 300 11-07-2015 08:32 event.json 934 11-07-2015 08:32 canary.py 251 11-07-2015 08:32 lambda.json 69120 11-07-2015 08:32 setuptools/cli-arm-32.exe 1274 11-07-2015 08:32 setuptools/windows_support.pyc ... 3686 11-07-2015 08:32 wheel/signatures/__init__.pyc 3779 11-07-2015 08:32 wheel/signatures/__init__.py 13985 11-07-2015 08:32 wheel/tool/__init__.pyc 13310 11-07-2015 08:32 wheel/tool/__init__.py 5063 11-07-2015 08:32 _markerlib/markers.pyc 1097 11-07-2015 08:32 _markerlib/__init__.pyc 552 11-07-2015 08:32 _markerlib/__init__.py 3979 11-07-2015 08:32 _markerlib/markers.py --------- ------- 10066874 833 files
- site-packages 以下
- requirements.txt または labmda.json で指定したライブラリ
- カレントディレクトリ以下
をひとまとめにして ZIP 化しているようです。
作業ディレクトリ(.lambda_package
)を覗いてみましょう。
$ tree -d -L 2 .lambda_package/ .lambda_package/ |-- lambda_package | |-- _markerlib | |-- pip | |-- pip-7.1.2.dist-info | |-- pkg_resources | |-- requests | |-- requests-2.8.1.dist-info | |-- setuptools | |-- setuptools-18.2.dist-info | |-- wheel | `-- wheel-0.24.0.dist-info `-- venv |-- bin |-- include |-- lib |-- lib64 -> lib `-- local 17 directories
コードを斜め読みすると、以下の様なことをやっているようです。
- 作業ディレクトリ以下に別途 virtualenv 環境を作る
- その中で pip install やファイルのコピーを
.lambda_package/lambda_package
以下に向けて行う .lambda_package/lambda_package
以下を ZIP ファイル(lambda_function.zip
)にする
ZIP ファイルには pip/setuptools のような Lambda 関数実行に関係のないライブラリが含まれていたり、pyc ファイルも含まれていたりするので ZIP ファイルをスリム化する余地はまだまだ残っています。改善したいですね。
まとめ
今年の9月から10月にかけては Lambda 関数を node.js/Python である程度書く機会がありました。開発で一番面倒に感じたのがコードの ZIP パッケージ化です。今回紹介した lambda-uploader はその処理を担ってくれます。
AWS Lambda Python はまだデビューしてから1ヶ月程度しか経過していないため、開発周辺ツールが発展途上です。 ローカル環境での開発ツールや AWS へのデプロイツールが整い、生産性が上がると、より多くの人が Python Lambda を Lambda 関数開発の第一候補に考えるのではないでしょうか。