Serveless Framework使ってみる
特定のエンドポイントに対して大量のアクセスが来て、それを捌けるようなアプリケーションの構築案件があって、Lambda + API Gatewayならオートスケールだしいいかもと思って触ってみました。
インストール
npmで入る。
npm install -g serverless
serverless コマンドが使えるけど、長いので省略形の sls コマンドも用意されている(ただのエイリアスっぽい)。
プロジェクトを作る
sls project create
質問に対して入力していくとプロジェクトセットができる(一部伏せています):
Serverless: Initializing Serverless Project...
Serverless: Enter a name for this project: (serverless-hyohxe) serverless-sample
Serverless: Enter a new stage name for this project: (dev)
Serverless: For the "dev" stageundefined do you want to use an existing Amazon Web Services profile or create a new one?
> Existing Profile
Create A New Profile
Serverless: Select a profile for your project:
yyyyyyy
xxxxxxx
> default
Serverless: Creating stage "dev"...
Serverless: Select a new region for your stage:
us-east-1
us-west-2
eu-west-1
eu-central-1
> ap-northeast-1
Serverless: Creating region "ap-northeast-1" in stage "dev"...
Serverless: Deploying resources to stage "dev" in region "ap-northeast-1" via Cloudformation (~3 minutes)...
AWSのプロファイルはこのタイミングで新規作成できるっぽいけど、普段からaws使ってるのでExisiting Profileを使用した。RegionはTokyoを選択。 このタイミングでUser: arn:aws:iam::xxxxxxx ...みたいなエラーが出る時はPolicyがダメみたいなのでRoleなどを確認してみてください。
あと、Documentを見るとGetting Startedでは「AdministratorAccessのPoclicyつけろ」って書いてあるけど、DEEP DIVEの節では、「AdministratorAccessは簡単にするために書いているんだからね。PowerUserAccessが推奨だよ」って書いてあります。最初からそれでいいんじゃないの。
ファイル構成はこんな感じ:
.
├── _meta
│ ├── resources
│ │ └── s-resources-cf-dev-apnortheast1.json
│ └── variables
│ ├── s-variables-common.json
│ ├── s-variables-dev-apnortheast1.json
│ └── s-variables-dev.json
├── admin.env
├── package.json
├── s-project.json
└── s-resources-cf.json
Functionを作る
関数単位で作成して、各々がLambdaのfunctionに対応しているみたい。
cd serverless-sample/
sls function create function/function1
ランタイムを聞かれるので、nodejs4.3を選択。
Serverless: Pleaseundefined select a runtime for this new Function
> nodejs4.3
python2.7
nodejs (v0.10undefined soon to be deprecated)
Serverless: For this new Functionundefined would you like to create an Endpointundefined Eventundefined or just the Function?
> Create Endpoint
Create Event
Just the Function...
Serverless: Successfully created function: "functions/function1"
functions/function1ディレクトリができる。Endpoint別に分けて作成したりできそう。
.
├── _meta
│ ├── resources
│ │ └── s-resources-cf-dev-apnortheast1.json
│ └── variables
│ ├── s-variables-common.json
│ ├── s-variables-dev-apnortheast1.json
│ └── s-variables-dev.json
├── admin.env
├── functions
│ └── function1
│ ├── event.json
│ ├── handler.js
│ └── s-function.json
├── package.json
├── s-project.json
└── s-resources-cf.json
function内のファイルは、
- event.json : ローカル実行用のLambda eventオブジェクトになる(handlerの第一引数)
- handler.js : メインのLambda実行ファイル
- s-function.json: AWSの設定とかAPIのハンドラ設定を記述。ややこしい
という構成になっている。基本的にLambda用ですね。 nodeのプロジェクトなら、ここにnpm installとかしてnode_moduleを置けばバンドルしてくれるみたい。便利ですね。
デプロイしてみる
CUIで選択してデプロイする:
sls dash deploy
ServerlessError: Function Deployment Failedというエラーが出ると、Lambdaにdeployする権限が無いらしい。(参考:https://github.com/serverless/serverless/issues/903) この状態でAWSのコンソール上にServerLessがIAM Roleを作成しているので、確認して設定ファイルに追記しないといけないらしいです(面倒):
# _meta/variables/s-variables-dev-appnortheast1.jsonに記述
{
...
"iamRoleArnLambda": "[RoleARNを記述]"undefined
...
}
これで再度deployすると成功しました。Endpointも同時にdeployするとアクセスできるURLが表示されるので、それにアクセスすればレスポンスが確認できます。またはAPI Gatewayの画面からでも確認できますね。
以上、ざっくり使い始めた感触でした。
その他
API EndpointでGET/POSTなリクエストを受け取る時、Lambdaはeventオブジェクトとしてしか受け取れないので、はs-function.jsonの設定でeventオブジェクトへのマッピングを書かないといけないです。 これがApache Velocity形式で書かないといけない上に、JSON内の文字列で書くのは正気の沙汰じゃないと思う…
…ので、GETリクエストの時のクエリパラメータの受け取りとPOSTリクエストの時のPOSTボディの受け取りで上手く行った方法をメモしておきます。endpointsのセクション内の設定です。これをベースにしてパラメータキーの値を調整すれば使えるかと。
GETのクエリパラメータ
例) https://example.com/endpoint?foo=bar&hoge=huga の場合、
"endpoints": [
{
"path": "/endpoint"undefined
"method": "GET"undefined
"type": "AWS"undefined
"authorizationType": "none"undefined
"authorizerFunction": falseundefined
"apiKeyRequired": falseundefined
"requestParameters": {
"integration.request.querystring.foo": "method.request.querystring.foo"undefined
"integration.request.querystring.hoge": "method.request.querystring.hoge"
}undefined
"requestTemplates": {
"application/json": "{\n\"foo\": \"$input.params('foo')\"undefined\n\"hoge\": \"$input.params('hoge')\"}"
}undefined
....
}
]
と書くとhandler.jsonのeventではevent.foo / event.hogeで値が取得できます。
POSTボディ
いわゆる application/x-www-form-urlencodedなMimeTypeでPOSTされるデータです。通常はJSON形式のPOSTを想定しているのか、この形式のマッピングは面倒です。
ref: http://qiita.com/durosasaki/items/83af014aa85a0448770e
こんなの書いてられないので(というか設定できるのかな…)、回避策として、RawDataをそのままパスして、handler.js側でparseするのが賢いと思います。
"endpoints": [
{
"path": "/endpoint"undefined
"method": "POST"undefined
"type": "AWS"undefined
"authorizationType": "none"undefined
"authorizerFunction": falseundefined
"apiKeyRequired": falseundefined
"requestParameters": {
}undefined
"requestTemplates": {
"application/x-www-form-urlencoded": "{\"message\": \"$input.body\"}"
}undefined
....
}
]
としておいて、handler.jsで、
"use strict";
const qs = require("querystring");
exports.handler = function(eventundefined contextundefined cb) {
const postBody = qs.parse(event.message);
// do something...
};
とすればPOSTボディが扱えました。
おまけ
オートスケールだしいいなって思ってパラメータで指定されたサイズのPlaceholder画像を生成して返すアプリを作ってみた。
…のですが、
"AWS API Gateway is not support binary output." /(o)\
— 獅子唐 (@sugimoto1981) 2016年7月6日
というわけで無理でした。
あ、あと、LambdaのImageMagickは以前の脆弱性対応からか、policy.xml対応を行っていて、EPHEMERAL, HTTPS, HTTP, URL, FTP, MVG, MSL, TEXT, LABELがDisableになっているようです。
ref: https://alas.aws.amazon.com/ALAS-2016-699.html
なのでLambdaで画像にWaterMark載せたりするのできなくなってました。ご注意を。
結局アプリは愚直にGolangで書いたのでした。現場からは以上です。