AWS Lambdaを活用したServerless Frameworkを触ってみる

[Wantedly Advent Calendar 2015](http://qiita.com/advent-calendar/2015/wantedly) 13日目です。
iOS エンジニアの 杉上 @susieyy です。

![serverless_serverless.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/ab25eb10-0b9f-eda8-3f62-b67ff9663fa9.jpeg "serverless_serverless.jpg")



Serverless Frameworkとは
--------

[Serverless Framework](https://github.com/serverless/serverless)のV0(BETA)が12/8に遂にリリースされました。

[Serverless Framework](https://github.com/serverless/serverless)はサーバレスなアプリケーションサービスを構築するためのフレームワークで、AWSの各種サービスを相互活用して動作します。AWS Lambdaを中心に添えた構成によりメンテナンス性とスケーラビリティがが高いサービスを構築することができます。

> Serverless (formerly JAWS): The serverless application framework – Use bleeding-edge AWS services to redefine how to build massively scalable (and cheap) apps!


歴史
--------------

![JAWS_Framework_-_Google_検索.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/c2aa4d32-dc57-3abe-f423-c1d483cf53bc.jpeg "JAWS_Framework_-_Google_検索.jpg")

[Serverless Framework](https://github.com/serverless/serverless)は前進である[JAWS Framework](https://github.com/jaws-stack/JAWS)が装いも新たにフルリニューアルのもとローンチされた位置づけになっています。掲げていたビジョンこそ同じものの下位互換性はまったくなく、フォルダ構成やコンフィグファイルのフォーマットも変更されているためマイグレーションは難しそうなので、すでにJAWSを利用して開発を行っている場合は一から作り直すの方が良さそうです。


[Serverless Framework](https://github.com/serverless/serverless)の前進である[JAWS Framework](https://github.com/jaws-stack/JAWS)は2015年の夏頃から開発が始まり、2015年の秋頃、[AWS re:Invent 2015](https://www.youtube.com/watch?v=D_U6luQ6I90)に登壇したことで注目を浴びます。Qiita上でもいくつか記事が書かれています。

サーバーレスアーキテクチャの有用性について参考になります。

- [サーバーレスアーキテクチャという技術分野についての簡単な調査](http://qiita.com/zerobase/items/3bc0d15980b472af841d)

JAWSを用いて実際にアプリケーションを作った記事です。

- [JAWSフレームワークで、サーバレスな分散ロックサービス「Joumae」をつくった](http://qiita.com/mumoshu/items/3cbe31b315be31f510d0)
- [JAWS Frameworkを使ってお手軽にLambda+API GatewayでHelloWorldするメモ](http://qiita.com/mokemokechicken/items/f81c886053f7d81a655d)
- [JAWS FrameworkでWebブラウザからS3にファイルをアップロードして、有効期限付きのダウンロード付きURLとパスワードを生成するサービスを作ってみた、時の開発メモ](http://qiita.com/mokemokechicken/items/40e0fbf19779c50e53db)

そんな折v1.3.3のリリースを最後にJAWSの開発はコミュニティに対して沈黙します。ISSUEやPRがたくさん上がるのですが粛々とv1.4ブランチで開発を進めて、ブランチをコミットをドキドキしながら追いかけていると、[Serverless: merge completed](https://github.com/serverless/serverless/commit/a69d4783cbd1f20b8d2a7384e317845cad0edc71)がマージされて別のフレームワークになっていました。


リニューアル後はコミュニティとの対話もあり、プルリクエストも受け入れて開発が進められています。


ロードマップ
------------------

ロードマップは[Trello](https://trello.com/b/EX6SxBJJ/framework)で管理されています。`Future`のタブを覗くと未来を垣間見れるかもしれません。

![Serverless___Trello.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/c4ef9775-c172-4bc3-785b-5c2a36d4d4d9.jpeg "Serverless___Trello.jpg")



インストール
--------------

```shell
$ npm install serverless -g
$ serverless version
0.0.12
```

Node V4 に依存いているので、必要ならバージョンを確認してインストールまたはバージョンアップします。

```shell
$ node --version
v4.1.1
```

AWSの準備
---

#### AWS CLIの準備

AWS CLIをインストールします。

```
$ pip install awscli

```

クレデンシャルを設定します。それぞれの`KEY`はご自身のものに書き換えてください。


```~/.aws/credentials
[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
```

デフォルトのリージョンを設定します。ここではLambdaまわりの新サービスのリリースがはやい`us-east-1`を指定しました。

```~/.aws/config:
[default]
region = us-east-1
```

詳細は[オフィシャルドキュメント](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html)を参照ください。


使ってみる
-------------


CLIでコマンドにより操作します。コマンドは`serverless`は長いので、`sls`がエイリアスになっています。

コマンドを入力すると以下のようなヘルプが見れます。ちょっとカッコイイですね。

![1__fish___Users_susieyy__fish_.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/b3a61d2a-de1c-0804-37a4-880a257943ec.jpeg "1__fish___Users_susieyy__fish_.jpg")


### プロジェクトの作成

まずはプロジェクトを作成します。作成は対話的に進めます。
プロジェクトの作成はAWSの`CloudFormation`スタックを作成するため５分ほどかかります。


```shel
$ serverless project create ServerlessTest
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                          serverless.com, v.0.0.12
`-------'
Serverless: Enter a project name:  (serverless41FwFSrHx) ServerlessTest
Serverless: Enter a project domain (used for serverless regional bucket names):  (myapp.com)
Serverless: Enter an email to use for AWS alarms:  (me@myapp.com)
Serverless: Select a region for your project:
  > us-east-1
    us-west-2
    eu-west-1
    ap-northeast-1
Serverless: Select an AWS profile for your project:
  > default
Serverless: Creating a project region bucket on S3: serverless.useast1.myapp-n1jkfssr.com...
Serverless: Creating CloudFormation Stack for your new project (~5 mins)...
Serverless: Successfully created project: ServerlessTest
```

作成されたフォルダに移動して構成を確認してます。設定ファイルは３つほどです。

```
$ cd ServerlessTest
$ tree .
.
├── README.md
├── admin.env
├── back
│   └── modules
├── cloudformation
│   └── resources-cf.json
├── plugins
└── s-project.json

4 directories, 4 files
```

`admin.env`は`~/.aws/credentials`を元にKEYが作られている模様です。

```
$ cat admin.env
SERVERLESS_ADMIN_AWS_ACCESS_KEY_ID= AKIAIOSFODNN7EXAMPLE
SERVERLESS_ADMIN_AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
```


プロジェクトの設定ファイルは以下のようになります。（一部マスキングしています）

```shell
$ cat ./s-project.json
```

```JSON
{
  "name": "ServerlessTest",
  "version": "0.0.1",
  "profile": "serverless-0",
  "location": "https://github.com/...",
  "author": "",
  "description": "",
  "domain": "myapp-xxxxxxxxxx.com",
  "stages": {
    "development": [
      {
        "region": "us-east-1",
        "iamRoleArnLambda": "arn:aws:iam::xxxxxxxxxx:role/ServerlessTest-development-r-IamRoleLambda-xxxxxxxxxx",
        "regionBucket": "serverless.useast1.myapp-xxxxxxxxxx.com"
      }
    ]
  },
  "custom": {},
  "plugins": []
}
```

Cloudformationの設定ファイルは以下のようになります。（一部マスキングしています）


```shell
$ cat cloudformation/resources-cf.json
```


```JSON
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "ServerlessTest resources",
  "Parameters": {
    "aaProjectName": {
      "Type": "String",
      "AllowedValues": [
        "ServerlessTest"
      ],
      "Default": "ServerlessTest"
    },
    "aaProjectDomain": {
      "Type": "String",
      "Default": "myapp-xxxxxxxxxx.com"
    },
    "aaStage": {
      "Type": "String",
      "AllowedValues": [
        "development"
      ]
    },
    "aaDataModelStage": {
      "Type": "String",
      "AllowedValues": [
        "development"
      ]
    },
    "aaNotficationEmail": {
      "Type": "String",
      "Default": "me@myapp.com"
    },
    "aaDefaultDynamoRWThroughput": {
      "Type": "String",
      "Default": "1"
    }
  },
  "Resources": {
    "IamRoleLambda": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/"
      }
    },
    "IamInstanceProfileLambda": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [
          {
            "Ref": "IamRoleLambda"
          }
        ]
      }
    },
    "IamGroupLambda": {
      "Type": "AWS::IAM::Group",
      "Properties": {
        "Path": "/"
      }
    },
    "IamPolicyLambda": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": {
          "Fn::Join": [
            "_-_",
            [
              {
                "Ref": "aaStage"
              },
              {
                "Ref": "aaProjectName"
              },
              "lambda"
            ]
          ]
        },
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              "Resource": {
                "Fn::Join": [
                  ":",
                  [
                    "arn:aws:logs",
                    {
                      "Ref": "AWS::Region"
                    },
                    "*:*"
                  ]
                ]
              }
            }
          ]
        },
        "Roles": [
          {
            "Ref": "IamRoleLambda"
          }
        ],
        "Groups": [
          {
            "Ref": "IamGroupLambda"
          }
        ]
      }
    }
  },
  "Outputs": {
    "IamRoleArnLambda": {
      "Description": "ARN of the lambda IAM role",
      "Value": {
        "Fn::GetAtt": [
          "IamRoleLambda",
          "Arn"
        ]
      }
    }
  }
}
```


### モジュールの作成

```shell
$ serverless module create -m greetings -f hello
Serverless: Successfully created hello function.
Serverless: Installing "serverless-helpers" for this module via NPM...
serverless-helpers-js@0.0.3 node_modules/serverless-helpers-js
└── dotenv@1.2.0
Serverless: Successfully created new serverless module "greetings" with its first function "hello"
```

`back` フォルダ以下に`modules`フォルダが配置され、今回作成した`greetings`モジュールのフォルダと、その配下に`hello`フォルダが生成されています。`JAWS｀ではプロジェクト単位で`node_modules`を管理していたのが、モジュール単位に変更されたようで、それぞれのモジュールで必要なライブラリだけをフレキシブルに利用できそうです。


```shell
$ tree .
.
├── README.md
├── admin.env
├── back
│   └── modules
│       └── greetings
│           ├── hello
│           │   ├── event.json
│           │   ├── handler.js
│           │   └── s-function.json
│           ├── lib
│           │   └── index.js
│           ├── node_modules
│           │   └── serverless-helpers-js
│           │       ├── README.md
│           │       ├── env
│           │       │   └── index.js
│           │       ├── index.js
│           │       ├── node_modules
│           │       │   └── dotenv
│           │       │       ├── Contributing.md
│           │       │       ├── README.md
│           │       │       ├── config.js
│           │       │       ├── dotenv.png
│           │       │       ├── lib
│           │       │       │   └── main.js
│           │       │       ├── package.json
│           │       │       └── test
│           │       │           ├── config.js
│           │       │           └── main.js
│           │       └── package.json
│           ├── package
│           ├── package.json
│           └── s-module.json
├── cloudformation
│   └── resources-cf.json
├── plugins
└── s-project.json

15 directories, 22 files
```

作成された`hello`ファンクションのコードを覗いてみます。


```shell
$ cat back/modules/greetings/lib/index.js
```

```JS
/**
 * Lib
 */

module.exports.respond = function(event, cb) {

  var response = {
    message: "Your Serverless function ran successfully!"
  };

  return cb(null, response);
};
```

### デプロイ

早速このファンクション（Lambda）をデプロイしてみます。

```shell
$ serverless function deploy
serverless function deploy
Serverless: Deploying functions in "development" to the following regions: us-east-1
Serverless: Successfully deployed functions in "development" to the following regions: us-east-1
```

AWSのコンソールで確認してみます。

![AWS_Lambda.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/fb054cb8-0ebb-f9f2-3ff7-5e44db67acb2.jpeg "AWS_Lambda.jpg")


続いてエンドポイント（ApiGateway）をデプロイしてみます。(一部マスキングしています）

```shell
$ serverless endpoint deploy
Serverless: Deploying endpoints in "development" to the following regions: us-east-1
Serverless: Successfully deployed endpoints in "development" to the following regions:
Serverless: us-east-1 ------------------------
Serverless:   GET - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/development/greetings/hello
```

エンドポイントのURLが作成されたのでアクセスしてみます。(一部マスキングしています）

```shell
$ http https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/development/greetings/hello
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 56
Content-Type: application/json
Date: Sun, 13 Dec 2015 16:15:44 GMT
Via: 1.1 a6980301a21d33b02853b61627119c24.cloudfront.net (CloudFront)
X-Amz-Cf-Id: UH7EoPOKGZ5jilolWvqeGo0BWaBIY04pPbvB8mhXcLjKpiFu4lZldg==
X-Cache: Miss from cloudfront
x-amzn-RequestId: c1fd5ba5-a1b4-11e5-8cfa-57bae789aad5

{
    "message": "Your Serverless function ran successfully!"
}
```

AWSのコンソールで確認してみます。


![API_Gateway.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/4da83c20-38b0-0f56-75e2-674308dc47ed.jpeg "API_Gateway.jpg")


以上で簡単なAPIを作成することができました。

ローカルでファンクションの実行
------------

デプロイ前に開発中のファンクションの動作を確認するために`run`コマンドがあります。


```shell
$ serverless function run
serverless function run
Serverless: Running GreetingsHello...
Serverless: -----------------
Serverless: Success! - This Response Was Returned:
Serverless: {"message":"Your Serverless function ran successfully!"}
```


もう１つファンクションを増やしてみる
------------

`greetings`モジュール配下に`bye`ファンクションを追加しました。

```shell
serverless function create -m greetings -f bye
Serverless: Successfully created bye function.
```

もう一度`run`を走らせてみると、どのファンクションを実行するのか対話的に選択できるようになっています。


```shell
$ serverless function run
Serverless: Select a function to run:
    greetings
  >   GreetingsBye
      GreetingsHello
```

デプロイは複数選択してアップロードできます。

      
```shell
$ serverless function deploy
Serverless: Select the functions you wish to deploy:
    greetings
  >   GreetingsBye
      GreetingsHello
    - - - - -
    Deploy
```                      
      
      
まとめ
-------------------

AWS Lambdaを活用したFrameworkとして注目されている[Serverless Framework](https://github.com/serverless/serverless)が成熟することで、サービスの作り方に新たな一石を投じるインパクトを秘めていると感じています。

Lambdaを用いることで費用を抑えつつも、高可用でスケーラブル、そしてOSやアプリケーションサーバのメンテナンスや運用からも開放される新たな境地にわくわくしています。