All Articles

Serverless Framework使ってみたメモ

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画像を生成して返すアプリを作ってみた。

…のですが、

というわけで無理でした。

あ、あと、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で書いたのでした。現場からは以上です。