Serverless FrameworkのVersion1.0 RC1版を試してみる(Lambda + API Gateway編)

# 初めに
[前回](http://qiita.com/tsuyopooon/items/2636e7e47d499a5cf2dd)はLambdaのみだったので、API Gatewayも使用して、Http経由でLambdaファンクションの実行を試してみます。
API Gatewayでstageがどう扱われるのかも気になってましたしね。

以下の記事は、[前回](http://qiita.com/tsuyopooon/items/2636e7e47d499a5cf2dd)に引き続いての内容になっています。

# Lambda + API Gateway

## APIのendpointの作成

[公式ドキュメント](https://github.com/serverless/serverless/blob/master/docs/02-providers/aws/events/01-apigateway.md)に従い、以下のように追記します。

```bash:serverless.yml
functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: message/hello
          method: get
```

早速、deployしてみましょう。

```bash
$ serverless deploy
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.......................
Serverless: Stack update finished...

Service Information
service: my-service
stage: dev
region: ap-northeast-1
endpoints:
  GET - https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/message/hello
functions:
  my-service-dev-hello: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:my-service-dev-hello
```

AWSコンソールから、API Gatewayを見てみると
![スクリーンショット 2016-09-27 8.30.57.png](https://qiita-image-store.s3.amazonaws.com/0/71316/f05337b3-d095-8e88-58a1-41c261065748.png "スクリーンショット 2016-09-27 8.30.57.png")

はい、もう出来ちゃってます。
早速、呼び出してみましょう。

```bash
$ curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/message/hello | jq .
・・・
{
  "message": "Go Serverless v1.0! Your function executed successfully!",
  "event": {
    "body": {},
    "stageVariables": {},
    "principalId": "",
    "headers": {
      "Via": "1.1 XXXXXXXXXXXX.cloudfront.net (CloudFront)",
      "CloudFront-Is-Desktop-Viewer": "true",
      "CloudFront-Is-SmartTV-Viewer": "false",
      "CloudFront-Forwarded-Proto": "https",
      "X-Forwarded-For": "XX.XX.XXX.XXX, XXX.XXX.XXX.X",
      "CloudFront-Viewer-Country": "JP",
      "Accept": "*/*",
      "User-Agent": "curl/7.43.0",
      "Host": "XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com",
      "X-Forwarded-Proto": "https",
      "X-Amz-Cf-Id": "XXXXXXXXXXXXXXX",
      "CloudFront-Is-Tablet-Viewer": "false",
      "X-Forwarded-Port": "443",
      "CloudFront-Is-Mobile-Viewer": "false"
    },
    "path": {},
    "query": {},
    "method": "GET",
    "identity": {
      "apiKey": "",
      "userArn": "",
      "cognitoAuthenticationType": "",
      "caller": "",
      "userAgent": "curl/7.43.0",
      "user": "",
      "cognitoIdentityPoolId": "",
      "cognitoIdentityId": "",
      "cognitoAuthenticationProvider": "",
      "sourceIp": "XX.XX.XXX.XXX",
      "accountId": ""
    },
    "stage": "dev"
  }
}
```

API Gateway経由で、Lambdaファンクションが実行され、HTTPのレスポンスとして、結果を取得できているのが確認できます。

簡単ですね。

## APIキーの設定

次はAPIキーを設定し、APIキーが正しい場合のみ、レスポンスを返すようにしてみましょう。
こちらも[公式ドキュメント](https://github.com/serverless/serverless/blob/master/docs/02-providers/aws/events/01-apigateway.md)に従い、以下のように追記します。

```bash:serverless.yml
service: my-service

provider:
  name: aws
  runtime: python2.7
  region: ap-northeast-1
  apiKeys:
    - myServiceKey

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: message/hello
          method: get
          private: true
```

再デプロイ

```bash
$ serverless deploy
Serverless: Packaging service...
Serverless: Removing old service versions...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................
Serverless: Stack update finished...

Service Information
service: my-service
stage: dev
region: ap-northeast-1
endpoints:
  GET - https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/message/hello
functions:
  my-service-dev-hello: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:my-service-dev-hello
```

AWSコンソールからもAPIキーが作成されていることが確認できます。
![スクリーンショット 2016-09-27 9.16.34.png](https://qiita-image-store.s3.amazonaws.com/0/71316/a0b49ab3-7a35-5e88-e6ea-4ddba2ee10f7.png "スクリーンショット 2016-09-27 9.16.34.png")

先ほどと同じ、リクエストを実行してみると

```bash
$ curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/message/hello | jq .
・・・
{
  "message": "Forbidden"
}
```

Lambdaファンクションの実行結果ではないものがレスポンスされました。

続いて、APIキーを指定して、実行してみます。

```bash
$ curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/message/hello --header 'x-api-key: XXXXX' | jq .
・・・
{
  "message": "Forbidden"
}
```

あれ。。。APIキー間違ってないのにどうして。。。

調べてみると、現状、AWSコンソールからAPIステージと使用量プランを紐付けてやらないとダメっぽいです。[^1]
というわけで、下記のように紐付けてあげます。

![スクリーンショット 2016-09-27 13.36.59.png](https://qiita-image-store.s3.amazonaws.com/0/71316/ac55faf0-8f51-c575-60ae-0228bedeec41.png "スクリーンショット 2016-09-27 13.36.59.png")

![スクリーンショット 2016-09-27 13.50.25.png](https://qiita-image-store.s3.amazonaws.com/0/71316/ad21df2d-45e1-0ea6-344f-ff646cbf89cf.png "スクリーンショット 2016-09-27 13.50.25.png")


制限値については、課金がされ過ぎない程度にとりあえず設定してもらえればと思います。[^2]

紐付けた後、再実行。

```bash
$ curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/message/hello --header 'x-api-key: XXXXX' | jq .
・・・
{
  "message": "Go Serverless v1.0! Your function executed successfully!",
  "event": {
    "body": {},
    "stageVariables": {},
    "principalId": "",
    "headers": {
      "Via": "1.1 XXXXXXXXXXXX.cloudfront.net (CloudFront)",
      "CloudFront-Is-Desktop-Viewer": "true",
      "CloudFront-Is-SmartTV-Viewer": "false",
      "CloudFront-Forwarded-Proto": "https",
      "X-Forwarded-For": "XXX.XX.XXX.XX, XXX.XXX.XXX.XXX",
      "CloudFront-Viewer-Country": "JP",
      "Accept": "*/*",
      "User-Agent": "curl/7.43.0",
      "Host": "XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com",
      "X-Forwarded-Proto": "https",
      "X-Amz-Cf-Id": "XXXXXXXXXXXXXXX",
      "CloudFront-Is-Tablet-Viewer": "false",
      "X-Forwarded-Port": "443",
      "x-api-key": "XXXXXXXXXXXXXXX",
      "CloudFront-Is-Mobile-Viewer": "false"
    },
    "path": {},
    "query": {},
    "method": "GET",
    "identity": {
      "apiKey": "XXXXXXXXXXXXXXX",
      "userArn": "",
      "cognitoAuthenticationType": "",
      "caller": "",
      "userAgent": "curl/7.43.0",
      "user": "",
      "cognitoIdentityPoolId": "",
      "cognitoIdentityId": "",
      "cognitoAuthenticationProvider": "",
      "sourceIp": "XXX.XX.XXX.XX",
      "accountId": ""
    },
    "stage": "dev"
  }
}
```

無事取得できました。

## stageの切り替え

さて、気になっていたAPI Gatewayと連携させた場合のstageの扱われ方がどうなるのか試してみます。

先ほどデプロイしたfunctionはAWSコンソール上ではこんな感じです。
![スクリーンショット 2016-10-03 7.45.54.png](https://qiita-image-store.s3.amazonaws.com/0/71316/c29daa83-f703-bc9d-8f4b-e3a2377bd12b.png "スクリーンショット 2016-10-03 7.45.54.png")

API名にも「dev」が付いている状態で、ステージも「dev」に紐づけられています。

というわけで、stageだけ変えて再度deployしてみます。

```bash
$ serverless deploy --stage test
...
Serverless: Deployment failed!

  Serverless Error ---------------------------------------

     An error occurred while provisioning your stack: ApiGatewayApiKey1
     - myServiceKey already exists in stack arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXX:stack/my-service-dev/XXXXXX-XXXXX-XXXXX-XXXX-XXXXXXXXX.

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
```

apiKeysの設定があると毎回作成しようとするのかな？
一旦、コメントアウト

```bash:serverless.yml
service: my-service

provider:
  name: aws
  runtime: python2.7
  region: ap-northeast-1
# apiKeys:
#   - myServiceKey
```

再チャレンジ。

```bash
$ serverless deploy --stage test
...
Serverless: Stack update finished...

Service Information
service: my-service
stage: test
region: ap-northeast-1
endpoints:
  GET - https://or4ebekgae.execute-api.ap-northeast-1.amazonaws.com/test/message/hello
functions:
  my-service-test-hello: arn:aws:lambda:ap-northeast-1:XXXXXXXX:function:my-service-test-hello
```

AWSコンソール上だと
![スクリーンショット 2016-10-03 8.10.52.png](https://qiita-image-store.s3.amazonaws.com/0/71316/30c4c815-ab43-42f1-7002-e3c97efaad4f.png "スクリーンショット 2016-10-03 8.10.52.png")

まぁ、予想通りというか、出来ればなってほしくない結果に。

理想としては、
API名は同じで「ステージ」に「dev」と「test」が出来てほしい。

# 所感

API Gatewayには12か月の無料利用枠もありますし、
サクッと個人的にAPIを作って試したい時は、お手軽に使えますね。


ただ、環境毎（開発、テスト、本番）に用意したい場合、
現状のバージョンでは、AWSのベストプラクティスからは外れた構成になるような気がしました。

ファンクション名やAPI名に「stage」は付与されずに
Lambdaファンクションであれば、「Alias」に、
API Gatewayであれば、「ステージ」に
それぞれ紐づけられて欲しいところ。

ただ上記の問題については、serverlessのissueでも取り上げられていて、近いうちに対応されそうな感じです。[^3]

正式リリース版に含まれてくれると嬉しいですね。


****
[^1]: [Python Serverless Microframework for AWS(chalice)でAPI Key認証を設定する](http://dev.classmethod.jp/etc/python-serverless-microframework-for-aws-chalice-api-key-authentication/)

[^2]: [【新機能】Amazon API Gatewayに「使用量プラン」機能が追加。キーごとにスロットリングやリクエストの制限が可能に](http://dev.classmethod.jp/cloud/aws/usage-plan-for-api-gateway/)

[^3]: [Can't set API Gateway stageVariables](http://forum.serverless.com/t/cant-set-api-gateway-stagevariables/314)