AWS LambdaのFunctionを開発するときのMakefile

自分用のメモ

lambda-uploaderみたいなものもありますが、LambdaだけならAWS CLIで十分簡単に操作出来るのでMakefileを用意してみました。

f:id:nwpct1:20161006204151g:plain

API Gatewayに関しては、AWS CLIでやろうとすると1つのリソースあたり3個ぐらいコマンドを叩く必要があったり、AWS CLIで操作できるものではない印象でした。 まだ管理画面触ってる方が楽だと思います。 自動化したいならAWS CLIではなく、CloudFormationやServerlessフレームワークみたいなもの(例: serverless(node), Zappa(python2))を使ったほうが良さそうです(こちらについては、今作ってるフレームワークがあるのでまた後日記事書きます)。

Help text

$ make
Description:
    Quickly deployment tool for AWS Lambda.

Requirements:
    - AWS CLI
    - jq

Commands:
    deploy               Deploy to AWS Lambda
    functions            Show the list of AWS Lambda functions
    help                 Show help text
    invoke               Run lambda function and show the result and the log.
    ls-bucket            Show the files in S3 Bucket
    roles                Show the list of IAM Roles
    undeploy             Remove deploy package from s3 and Delete lambda function
    update               Update Lambda function

Deploy

  1. deploy packageの作成
  2. deploy packageをs3にupload
  3. Lambda関数の登録

Invoke

  1. input.jsonの中身をeventとして、Lambdaを呼び出す
  2. base64 encodeでログが送られてくるので、jqでパースして、base64 decode。
  3. 実行結果とログを保存して、表示

Makefile

Makefileは、ちゃんと書いたことがなかったので変な書き方してるかも。 何か変なところあれば教えてください。

.PHONY: help roles functions ls-bucket upload invoke undeploy deploy update

LAMBDA_NAME    := image-upload
LAMBDA_HANDLER := lambda_image_upload.lambda_handler
DESCRIPTION    := "Upload image posted by multipart/form-data."
TIME_OUT       := 16

INPUT_PYFILE   := lambda_image_upload.py
ZIP_FILE_NAME  := lambda_image_upload.zip
INPUT_JSON     := input.json
OUTPUT_FILE    := result.json
LOG_FILE       := invoke.log

S3_BUCKET      := lambda-packages
S3_KEY         := lambda_image_upload.zip
IAM_ROLE_ARN   := xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
REGION         := ap-northeast-1

.DEFAULT_GOAL := help
help: ## Show help text
    @echo "Description:"
    @echo "    Quickly deployment tool for AWS Lambda."
    @echo ""
    @echo "Requirements:"
    @echo "    - AWS CLI"
    @echo "    - jq"
    @echo ""
    @echo "Commands:"
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "    \033[36m%-20s\033[0m %s\n", $$1, $$2}'

roles: ## Show the list of IAM Roles
    aws iam list-roles | jq '.Roles | .[] | .RoleName, .Arn'

functions: ## Show the list of AWS Lambda functions
    aws lambda list-functions | jq '.Functions | .[] | .FunctionName'

ls-bucket: ## Show the files in S3 Bucket
    aws s3 ls s3://$(S3_BUCKET)

$(ZIP_FILE_NAME): $(INPUT_PYFILE) ## Create deploy package
    zip -r $(ZIP_FILE_NAME) $(INPUT_PYFILE)

upload: # Upload deploy package to S3
    make $(ZIP_FILE_NAME)
    aws s3 cp $(ZIP_FILE_NAME) s3://$(S3_BUCKET)/

# See http://docs.aws.amazon.com/lambda/latest/dg/with-userapp-walkthrough-custom-events-invoke.html
invoke: ## Run lambda function and show the result and the log.
    @aws lambda invoke \
        --payload file://$(INPUT_JSON) \
        --function-name $(LAMBDA_NAME) \
        --log-type Tail \
        $(OUTPUT_FILE) \
        | jq -r '.LogResult' \
        | base64 --decode \
        > $(LOG_FILE)

    @echo "-- Result\n"
    @cat $(OUTPUT_FILE)
    @echo "\n\n-- Log\n"
    @cat $(LOG_FILE)
    @echo ""

undeploy: ## Remove deploy package from s3 and Delete lambda function
    aws s3 rm s3://$(S3_BUCKET)/$(S3_KEY)
    aws lambda delete-function --function-name $(LAMBDA_NAME)

# See https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-on-demand-https-example-upload-deployment-pkg.html
deploy: ## Deploy to AWS Lambda
    make upload
    aws lambda create-function \
        --function-name $(LAMBDA_NAME) \
        --region $(REGION) \
        --code S3Bucket=$(S3_BUCKET),S3Key=$(S3_KEY) \
        --handler $(LAMBDA_HANDLER) \
        --timeout $(TIME_OUT) \
        --description $(DESCRIPTION) \
        --runtime python2.7 \
        --role $(IAM_ROLE_ARN)

update: ## Update Lambda function
    make upload
    aws lambda update-function-code \
        --function-name $(LAMBDA_NAME) \
        --s3-bucket $(S3_BUCKET) \
        --s3-key $(S3_KEY)

参考資料

AWS Lambda実践ガイド

AWS Lambda実践ガイド

AWS LambdaとAPI Gatewayを触ってみたのでメモ

PyCon JP スプリントの時に id:iktakahiro さんがLambdaの話をしていたのですが、Lambda + API Gatewayは覚えとくと便利そうなので試してみた。

Lambdaを触ってみる

まずAPI Gatewayは考えずに、Lambdaを触ってみる。 調べてみたら、公式のドキュメントにも今やりたいことに似たものがあった。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-s3-example.html

事前に、下のことをしておいた。

  • Lambda実行用のIAMロールを作成
    • RoleType: AWS Lambda
    • Policy: AWSLambdaFullAccess, AmazonS3FullAccess, AWSLambdaBasicExecutionRole
  • S3のバケットを2つ用意。
    • bucketとlambdaは同じリージョンに作る
    • hoge-image-source: 直接アップロードされた画像
    • hoge-image-resized: Lambdaによってサイズを変更した画像
  • image-resizedtest.jpg という画像データを保存
    • Lambdaの動きを確認する時に、 test.jpg がcreatedされたというサンプルイベントデータを渡す。

デプロイパッケージを作成

Pillowみたいなサードパーティのライブラリを使うときは、それらを一緒にzipに固めたデプロイパッケージを作る。 デプロイパッケージに含めるものは、 id:iktakahiro さんいわく ./vendors に入れておくのがいいらしいのでとりあえず真似してみる。

$ virtualenv -p python2.7 venv  # Python3使えないので注意
$ source venv/bin/activate
$ pip install pillow -t ./vendors
$ ls vendors/
PIL                    Pillow-3.3.1.dist-info

ただ、この方法はPillowとか使う場合うまくいかなかった。

Unable to import module 'lambda_function': /var/task/PIL/_imaging.so: invalid ELF header

ではまった。どうやらMacでビルドするのが行けないらしい。 Amazon LinuxインスタンスをEC2で立ち上げてビルドした。 Dockerでうまくビルド出来るようにすると楽になれそう。

こういうビルド済みのファイルを提供しているリポジトリもあるらしい。

コードを書く

thumbnail-generator.py

from __future__ import print_function
import boto3
import os
import sys
import uuid
from PIL import Image

s3_client = boto3.client('s3')
RESIZED_BUCKET_NAME = 'hoge-image-resized'
RESIZED_IMAGE_SIZE = (200, 100)

def resize_image(image_path, resized_path):
    with Image.open(image_path) as image:
        image.thumbnail(RESIZED_IMAGE_SIZE)
        image.save(resized_path)

def lambda_handler(event, context):
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = record['s3']['object']['key']
        download_path = '/tmp/{}{}'.format(uuid.uuid4(), key)
        upload_path = '/tmp/resized-{}'.format(key)

        s3_client.download_file(bucket, key, download_path)
        resize_image(download_path, upload_path)
        s3_client.upload_file(upload_path, RESIZED_BUCKET_NAME, key)

アップロード

10MB以上なら、S3を経由してUploadする必要がある。 10MB以下なら、Lambdaの設定画面からUpload出来る。

ログ

こんな感じでグラフ化されている。 それぞれの詳細のログも見れるので、確認したい項目はprintしておけばいいみたい。

f:id:nwpct1:20161002232026p:plain

動作確認

AWS CLIを使う方法とかもあるけど、ブラウザ上のTestボタンを押す方法が一番手軽な感じ。 CIの導入とかuploadからtestまで一発でやりたくなったら、AWS CLIを使う方法を試して追記。

Testボタン

事前に test.jpg をアップロード済みなので、ブラウザからS3のPutイベントを発生させてみる。 管理画面でTestって名前がついた青いボタンがあるのでそれを押す。

awsRegionとbucketのarn, name, objectのkeyを変えて実行。

  • awsRegion: ap-northeast-1
  • s3 > object > key: test.jpg
  • s3 > bucket > arn: arn:aws:s3:::hoge-image-source
  • s3 > bucket > name: hoge-image-source

ログを見ながらデバッグ。 うまくいくと、 hoge-image-resized の方にサムネイルが生成されている。

Github

成果物

github.com

API Gateway

API Gatewayで画像のアップロード用APIを作りたい。 POSTで画像を受け取って、S3へアップロード、URLを返す。

リクエストの形式

まずリクエストのヘッダやボディがAPI Gateway経由でどのように渡されるのか確認する。 あとで気づいたのだけれど、API Gateway AWS ProxyのSample event templateを見ればわかった。

{
  "body": "{\"test\":\"body\"}",
  "resource": "/{proxy+}",
  "requestContext": {
    "resourceId": "123456",
    "apiId": "1234567890",
    "resourcePath": "/{proxy+}",
    "httpMethod": "POST",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
    "accountId": "123456789012",
    "identity": {
      "apiKey": null,
      : (中略)
    },
    "stage": "prod"
  },
  "queryStringParameters": {
    "foo": "bar"
  },
  "headers": {
    "Accept-Encoding": "gzip, deflate, sdch",
    : (中略)
  },
  "pathParameters": {
    "proxy": "path/to/resource"
  },
  "httpMethod": "POST",
  "stageVariables": {
    "baz": "qux"
  },
  "path": "/path/to/resource"
}

うん、とても分かりやすい。 楽をさせるために、request bodyはunicodeで渡されるらしい。

2016/10/04 削除

ここに色々書いてたのですが、文字列についてだいぶ大きな勘違いをしてました。 unicode形式だと画像とかファイルは受け取れないと思ってたんですが、普通にencodeすればいいだけなんですね。

レスポンスの形式

知らずにはまったんだけど、レスポンスは次の形式で返す必要がある。 よくよく考えたら、Hello World みたいに文字列返すだけだとステータスコードとヘッダ渡す場所がないので当たり前だった。

from __future__ import print_function


def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "headers": {"headerName": "headerValue", "Foo": "bar"},
        "body": "This is body"
    }

所感・メモ

Lambda

  • Pythonは2系のみ
  • 300秒まで使える
    • バッチ処理とかだと厳しい場合が結構ありそう
    • EC2立ち上げる人もいるらしい
  • Lambda Functionはバージョニング可能
  • VPC対応あり
  • CloudWatch EventsのScheduleで定期実行できる
  • 勝手にスケールしてくれる

API Gateway

リクエストの情報をかなりラップして渡してくれていて、手軽。

  • リクエストのpayloadサイズに10MBの上限がある。画像とかファイルアップロードするときは厳しい
  • 結構分かりやすい形式でLambdaにリクエストの情報を送ってくれる
  • レスポンスの形式も明確

References

Amazon Web Services クラウドネイティブ・アプリケーション開発技法 一番大切な知識と技術が身につく (Informatics&IDEA)

Amazon Web Services クラウドネイティブ・アプリケーション開発技法 一番大切な知識と技術が身につく (Informatics&IDEA)

  • 作者: NRIネットコム株式会社,佐々木拓郎,佐藤瞬,石川修,高柳怜士,佐藤雄也,岸本勇貴
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2016/04/20
  • メディア: 単行本
  • この商品を含むブログ (1件) を見る

#pyconjp でWeb(WSGI)フレームワークの作り方について話してきました

昨年のPyConJPでは pandas-validator について話していたのですが、今回はトークセッションをしてきました。 Twitter見てると早速スライドを見ながら実装してくださってる方もいらっしゃって嬉しいかぎりです。

日本語のWSGIフレームワークの作り方の資料としては最もまとまっているものになっていると思うので、ぜひご活用ください。

基礎から学ぶWebアプリケーションフレームワークの作り方

WSGIの解説から始まり、ルーティング・リクエスト・レスポンス・HTMLテンプレートやミドルウェアについて順番に解説しながら、ボトムアップフレームワークを作り上げていく話をしてきました。

半分ライブコーディングみたいな形だったので、少し不安もありました。 もう少しこうやればスムーズだったかなと反省点も少しありますが、スライドとかTogetterまとめがホッテントリ入りしていたらしく、周りからの反響もよくて一安心です。

動画


2_04 基礎から学ぶWebアプリケーションフレームワークの作り方

スライド

c-bata.link

Togetterまとめ

togetter.com

Github

GitHub - c-bata/webframework-in-python: "How to develop WSGI WEB Framework" talked at PyConJP 2016

明日はマイクロソフト社さんのオフィスで、PyConJPのスプリントがありますが会場にいるので、もし質問とかあればお声がけください。

remark.js の紹介

少し話がそれるのですが、資料作りにあたって、 Remark.js が素晴らしかったので紹介。

f:id:nwpct1:20160923220141g:plain

このように

  1. shift + c で新しいWindowを開く
  2. shift + p でプレゼンテーションモードに変更

するとうまく同期してくれます。 reveal.jsだと発表者ノートを表示するためにwebsocketのサーバを立ち上げないといけませんが、こちらは必要ありません。Github Pagesでも上のスライドのリンクのようにそのまま動かせます。

  1. Markdownでかけて
  2. CSSで簡単にスタイルを修正できて
  3. Github pagesで簡単に発表者ノート毎表示できます。

最高です。

全体的な感想

  • 1日目のパーティで沢山の方にお声がけいただけました。有名人になれたみたいで嬉しかったです。このブログすごい参考にしてますと言ってくださる方もいらっしゃったのでしっかり更新続けないとなと思いました
  • スピーカーパーティの時にPyDataの山本さんという方とお話したのですが、xgboostや競馬予測に関して本当に面白い話をお聞きすることができました。データ系に詳しい参加者がたくさんいて、普段Webをやっている人間もデータ系の全く知らなかった話を知っていけるのは、Pythonのカンファレンスならではですね。
  • 非同期に関して色々と知りたいこと・よく分かってないことが多いのですが、スピーカーパーティの時にjbkingさんからめちゃくちゃ丁寧な解説をいただきました。本当に面白くてコードを書くモチベーションがめちゃくちゃ高くなってしまった。
  • 来年は非同期で高速なWebフレームワークに関して発表したい

以上、おつかれさまでした。

エキスパートPythonプログラミング改訂2版

エキスパートPythonプログラミング改訂2版