Serverless Frameworkに入門してみた

最近巷では[Serverless](https://github.com/serverless/serverless)なるものが流行っていると聞き、
これは役に立ちそうだと思ったので少し触ってみました。

入門ということで、APIゲートウェイでリクエストを受けてS3からテキストを落としてS3内の別の場所に上げ直すという単純かつ無意味なことをしています。

# 環境構築

## Serverlessのインストール
Serverlessを使うにはnpmでインストールします。
よって、先んじてNode.jsを入れる必要があるのですが、そちらは割愛。

`npm install serverless -g`

-gオプションはつけない主義なのですが、ローカルインストールだとかえって色々めんどくさそうなので断腸の思いでグローバルインストール

2016/09/26時点での最新バージョンは1.0.0-rc.2です。
以下、このバージョンでのコマンドで作業を進めますが、わりと頻繁にコマンドの体系が変わってるみたいなので注意してください。

## プロジェクト作成

新規作成時のコマンドはこんな感じ。
`serverless crete --template as-nodejs --path ./serverless-test`
今回はnodejsを採用。pythonで実装するなら`--template aws-python`になります。

うまくいったら↓のファイルが出来上がります。
```
serverless-test
|- event.json
|- handler.json
|- serverless.yml
```

# 実装
## バケットポリシー設定
S3にアクセスするので、バケットポリシーを設定しておきます。
バケットポリシーの設定を忘れて「IAMロールに権限与えてるのにアクセスできねぇぞ!」となるのはあるあるですね。

AWSのコンソールからS3->バケット選択-> Edit bucket policyと進みます。
バケットポリシーのjsonを自前で書くのはしんどいので[ポリシージェネレーター](http://awspolicygen.s3.amazonaws.com/policygen.html)を使用して、
今回使用するバケットのputObjectとgetObjectを許可するポリシーを作ります。

```json:policy.json
{
  "Id": "Policy1474861813220",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1474857263263",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::my-bucket-name/*",
      "Principal": {"AWS": "arn:aws:iam::my-account-id:root"}
    }
  ]
}
```

参考にする場合、my-bucket-nameとmy-account-idは適宜読み替えてください。
「別にどこからアクセスされても構わん!」という勇者はPrincipal:"*"でもOKです。

## serverless.ymlの編集

設定関係はserverless.ymlに記述します。記述する内容としては

- どのリージョンに置くか
- どんな権限を与えるか
- 実行のトリガーは何か
- etc...

って感じです。
そのほかは[このへん](https://github.com/serverless/serverless/blob/master/docs/02-providers/aws/README.md)を参考に。

今回はS3のputObjectとgetObjectの権限を与えるとともに、APIゲートウェイからの呼び出しも設定します。

```yaml:serverless.yml
service: serverless-test 
provider:
  name: aws
  runtime: nodejs4.3
  stage: dev
  region: us-east-1
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:getObject"
        - "s3:putObject"
      Resource: "arn:aws:s3:::my-bucket-name/*"

functions:
  transfer:
    handler: handler.transfer
    memorySize: 256
    timeout: 30
    events:
      - http:
          path: transfer
          method: get
```

`serverless create`コマンドで生成された直後だとdefaultsという項目があって、
その下にstageとかregionとかがありましたが、後にデプロイするときに「defaultsは非推奨やねん。providerの下に置いてや」と警告されたのでそうしてます。

providerが実行環境に関する設定で、functionsが各ファンクションのトリガーの設定という感じかと思います。

今回はAPIゲートウェイで/transferにGETメソッドでアクセスするとファンクションが走ります。

## Lambdaファンクション実装

それではLambdaのファンクションを実装します。
前述のとおり、S3に置いたテキストファイルを落としてきて、それを同じバケット内の別の場所に上げ直します。

テストということでオブジェクトのキーは決め打ちです。

コールバック地獄の入り口に立っていますが、ご容赦を。
（真面目にやるならPromiseとか使うべきだろうなぁ...）

```js:handler.js
'use strict';

const AWS = require('aws-sdk');

module.exports.transfer = (event, context, callback) => {

    const s3 = new AWS.S3();
    const BUCKET = "my-bucket-name";
    s3.getObject({Bucket: BUCKET, Key: 'serverless-test/test.txt'}, (err, data) => {

        if(err !== null){
            return callback(err, {message: err.stack});
        }

        let buf = data.Body;

        s3.putObject({
            Bucket: BUCKET,
            Key: 'serverless-test/output.txt',
            Body: buf
        }, (putErr, putData) => {
            callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', putData });
        });

    });


};
```

# 実行

まずはローカルで作成したコードをAWSにデプロイします。
プロジェクトルートで`severless deploy`!

成功したらコマンドライン上にAPIゲートウェイのエンドポイントとかの情報が表示されます。

これだけでLambdaファンクションのアップロードとAPIゲートウェイの設定が済むなんて...!


AWSコンソールのAPIゲートウェイのところからテストすることもできますが、
`serverless invoke`コマンドで手元からすぐに実行できます。

`serverless invoke --function transfer`
で実行、とりあえずエラーはなさそうだ!

![serverless-test.JPG](https://qiita-image-store.s3.amazonaws.com/0/116434/89e88b43-fa46-b8d0-d58b-9102d0a3ab2e.jpeg)

アップロードもできてるぞ!

# 感想
自前でやると面倒だったLambdaとAPIゲートウェイの連携がサクッとできるのはいい感じです。
ただ、IAMのポリシーの設定とかそっちの方で学習コストはそれなりにかかるのかなという印象です。
それでも自前でサーバーを持たずにAPIを手早く作れるというのは大きなメリットかと思います。
あとはServerlessの仕様が安定する日を待つばかりです。