Laravel 5で作成したAPIをAWS Elastic Beanstalkにデプロイし、AWS API GatewayのHTTPプロキシのバックエンドとする構成について確認したメモになります。

Swagger-PHPを利用して生成したSwaggerドキュメントをAPI Gatewayにインポートする場合、API Gateway固有の設定をSwaggerドキュメントに追加する必要があります。

ここでは、前の記事「Swagger-PHPについてLaravel 5.2で確認したメモ」で実装したAPIに、API GatewayのSwagger拡張を追加してSwaggerドキュメントを生成しなおしAPI Gatewayにインポートする流れで解説していきます。

システム構成

この記事の目的は、バックエンドのAPIをLaravelで実装した場合に、API Gatewayに統合するにはどんな感じか概要をつかむことです。

LaravelやRailsのような、フレームワークを利用してAPIのデプロイ先としては、AWSでは、マネジメントサービスを利用する場合、ElasticBeanstalkかContainer Serviceが選択肢となるかと思いますが、ここではより構成手順が少ないElastic Beanstalkを選択します。

それを踏まえ、簡単に図にすると以下のようになります。

api-gateway-and-laravel-api-on-elastic-beanstalk

ここでは、ネットワーク、IAMおよびセキュリティグループについては省略していますが、実運用ではこの辺りをきちんと設計する必要があります。

Laravelプロジェクトのデプロイ環境をAWSに構築

LaravelプロジェクトをElastic Beanstalkにデプロイし、データベースはRDSを参照するように環境を構築していきます。

Elastic Beanstalkのアプリケーションおよび環境を作成

Laravel 5で作成したAPIを、Elastic Beanstalkにデプロイする準備をします。

Laravel 5をBeanstalkにデプロイする方法についてはAWS公式ドキュメントの「Elastic Beanstalk への Laravel アプリケーションのデプロイ」にあります。
本ブログでも、「Laravel 5プロジェクトをElastic Beanstalkにデプロイ」にまとめていますので参照ください。

Beanstalkに、デプロイ先のアプリケーションと環境を作成しておきます。

Laravelプロジェクトのルートディレクトリで以下のコマンドを実行し、Gitリポジトリを初期化します。

$ git init
$ git add .
$ git commit -m "First commit"

次に、Beanstalkの設定をします。

$ eb init
$ eb create

環境作成時に、プロンプトEnter DNS CNAME prefixで指定した値に..elasticbeanstalk.comを足した値が、Swaggerのアノテーションで指定するAPIのエンドポイントになります。

例えば、DNS CNAME prefixにlaravel-eb-devを指定して、us-east-1に作成した環境の場合はlaravel-eb-dev.us-east-1.elasticbeanstalk.com になります。

RDSにデータベースを追加

MySQLのデータベースを作成します。
この手順は省略します。

Elastic Beanstalkの環境変数を定義

Elastic Beanstalkの環境変数のセットは、画面または設定ファイルが利用可能です。
ここでは、設定ファイルを利用します。

今回のLaravel 5プロジェクトを動作させるにあたって、最小限必要となる環境変数をセットする設定ファイル.ebextensions/environmentvariables.configを以下のように作成します。

option_settings:
  - option_name: APP_KEY
    value: base64:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=
  - option_name: DB_HOST
    value: laraveldb.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com
  - option_name: DB_DATABASE
    value: laraveldb
  - option_name: DB_USERNAME
    value: laraveldbusername
  - option_name: DB_PASSWORD
    value: laraveldbpassword

データベースマイグレーションの定義

次に、デプロイ時にマイグレーションが実行されるように.ebextensions/laravelmigrate.configを以下のように作成します。

container_commands:
  01-migration:
    command: "php artisan migrate --force"
    leader_only: true
  02-seed:
    command: "php artisan db:seed --force"
    leader_only: true

Elastic Beanstalkのソフトウェア設定 ドキュメントルート

Laravel 5は、ドキュメントルートを /public に設定する必要があります。
これも、設定ファイルが利用できます。

option_settings:
  - namespace: aws:elasticbeanstalk:container:php:phpini
    option_name: document_root
    value: /public

環境設定は以上です。

環境設定をデプロイ

コミットを作成して、デプロイします。

$ git add .
$ git commit -m "Add beanstalk environments."
$ eb deploy

API定義にAWS API Gateway用のアノテーションを追加

API GatewayにSwaggerドキュメントをそのままインポートしても、バックエンドとの連携について別途設定が必要になっています。

バックエンド APIとの連携については、API Gatewayの「Swagger に対する API Gateway 拡張」をアノテーションに追加します。

Swagger-PHPで、拡張オブジェクトを記述するには、以下のようにx=を利用して指定します。

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\Product;

class ProductController extends Controller
{
  /**
   * @SWG\Get(
   *   path="/products",
   *   summary="list products",
   *   x={"amazon-apigateway-integration"={
   *     "type": "HTTP",
   *     "httpMethod": "GET",
   *     "uri": "http://eb-laravel-dev.jnbm8maymt.us-east-1.elasticbeanstalk.com/api/products",
   *     "responses": {
   *         "200": {
   *             "statusCode": "200"
   *         },
   *         "500": {
   *             "statusCode": "500"
   *         }
   *     }
   *   }},
   *   @SWG\Response(
   *     response=200,
   *     description="A list with products",
   *     @SWG\Schema(
   *             type="array",
   *             @SWG\Items(ref="#/definitions/Product")
   *     )
   *   ),
   *   @SWG\Response(
   *     response=500,
   *     description="an ""unexpected"" error"
   *   )
   * )
   */
  public function index()
  {
    return response()->json([
      'result'    => [
          'statistics' => [
              'products' => Product::all()
          ],
      ],
      'message'   => '',
      'type'      => 'success',
      'status'    => 0
    ]);
  }
}

Swaggerドキュメントを生成し直します。

$ mkdir public/api
$ ./vendor/bin/swagger ./app --output public/api/

API Gatewayにインポート

生成したSwaggerドキュメントは、awslabs/aws-apigateway-importerを利用してAPI Gatewayにインポートします。

基本的な使い方については、GETTING STARTED WITH THE AMAZON SWAGGER IMPORTERを参照ください。

前提条件

AWS CLIが利用可能。もしくは、~/.awsディレクトリに、regionとcredentialsの設定があれば、aws-apigateway-importerはそれを読み込む。

(RegionとCredentialsについては、コマンド実行オプションでも指定可能)

aws-apigateway-importerのインストール

aws-apigateway-importerはJavaのツールなのでクローン後Mavenでビルドします。
Mavenはhomebrewなどでインストールしておきます。

Gitクローンして、mvnコマンドでビルドします。

$ git clone https://github.com/awslabs/aws-apigateway-swagger-importer.git
$ cd aws-apigateway-swagger-importer
$ mvn assembly:assembly
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building aws-apigateway-importer 1.0.3-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:15 min
[INFO] Finished at: 2016-08-02T14:00:03+09:00
[INFO] Final Memory: 66M/687M
[INFO] ------------------------------------------------------------------------

aws-api-import.shを--helpオプションを指定して実行して以下の様に表示されれば、利用可能な状態です。

$ ./aws-api-import.sh --help
Usage: aws-api-import [options] Path to API definition file to import
  Options:
    --create, -c
       Create a new API
       Default: false
    --deploy, -d
       Stage used to deploy the API (optional)
        --help
       
       Default: false
    --profile, -p
       AWS CLI profile to use (optional)
       Default: default
        --raml-config
       RAML file for API Gateway metadata (optional)
    --region, -r
       AWS region to use (optional)
    --test, -t
       Delete the API after import (create only)
       Default: false
    --update, -u
       API ID to import swagger into an existing API

インポートを実行

以下のコマンドを実行して、生成したSwaggerドキュメントをインポートします。

$ ./aws-api-import.sh -c /path/to/project/public/api/swagger.json
2016-08-02 18:39:21,813 INFO - Using API Gateway endpoint https://apigateway.us-east-1.amazonaws.com
2016-08-02 18:39:25,730 INFO - Attempting to create API from Swagger definition. Swagger file: /Users/hrendoh/workspace/eb-laravel/public/api/swagger.json
reading from /Users/hrendoh/workspace/eb-laravel/public/api/swagger.json
2016-08-02 18:39:25,877 INFO - Parsed Swagger with 1 paths
2016-08-02 18:39:25,877 INFO - Creating API with name My first swagger documented API
2016-08-02 18:39:26,571 INFO - Created API 65x20f4pe7
2016-08-02 18:39:27,140 INFO - Removing default model Empty
2016-08-02 18:39:27,352 INFO - Removing default model Error
2016-08-02 18:39:27,579 INFO - Creating model for api id 65x20f4pe7 with name Product
2016-08-02 18:39:27,743 INFO - Generated json-schema for model Product: {"properties":{"name":{"type":"string","description":"The product name"}},"definitions":{}}
2016-08-02 18:39:28,204 INFO - Creating resource 'api' on j1pm3lwh23
2016-08-02 18:39:28,644 INFO - Creating resource 'products' on x6mc1r
2016-08-02 18:39:29,486 INFO - Creating method response for api 65x20f4pe7 and method GET and status 200
2016-08-02 18:39:29,686 INFO - Creating new model referenced from response: Alistwithproducts
2016-08-02 18:39:29,686 INFO - Creating model for api id 65x20f4pe7 with name Alistwithproducts
2016-08-02 18:39:29,699 INFO - Generated json-schema for model Alistwithproducts: {"type":"array","items":{"$ref":"#/definitions/Product"},"definitions":{"Product":{"properties":{"name":{"type":"string","description":"The product name"}}}}}
2016-08-02 18:39:30,330 INFO - Creating method response for api 65x20f4pe7 and method GET and status 500
2016-08-02 18:39:30,938 INFO - Creating integration with type HTTP
2016-08-02 18:39:32,167 INFO - Creating method for api id 65x20f4pe7 and resource id vhapyw with method get

マネジメント コンソールで確認すると以下のようにリソースが追加されています。
aws-api-gateway-resource-imported-from-swagger-file

モデルは以下のように追加されます。
aws-api-gateway-model-imported-from-swagger-file

ステージングにデプロイ

APIをステージングにデプロイするには--deployオプションを指定します。
importerでデプロイのみはできないので、--updateと併せて利用します。

$ ./aws-api-import.sh --update 65x20f4pe7 --deploy v1 path/to/swagger.json

APIのURLに含まれるバージョンが、/v1/api/と言うのは微妙ですが、ひとまず以下のようにデプロイされました。

aws-api-gateway-staged-api

まとめ

以上で、なんとなくLaravelやRailsのようなフレームワークを利用して実装したAPIとAPI Gatewayの連携方法が確認できたと思います。

この後の検討事項としては以下のようなものが考えられます。

  • 認証: API Gateway側のカスタム認証の実装でOAuth2を実装する?
  • バックエンドAPIのセキュリティ: API Gatewayに認証を任せるとするとバックエンドAPIとのセキュリティをどうするか?
  • スロットリング: API利用ユーザーごとにスロットリングを実装するには?
  • バージョニング: APIのバージョン管理は、API Gatewayのステージを使うのが良い?

なかなか長い道のりそうですね。

参考

API Gateway + IAM + S3 + Elastic Beanstalk + RDS + Elastic Load Balancer = Our server-less API infrastructure

Swaggerで始めるモデルファーストなAPI開発

これから始めるエンタープライズ Web API 開発第3回 aws-apigateway-importerを使ったAmazon API Gatewayの設定

Amazon API Gateway Importer を使って Rails x Grape から API を生成する

補足

basePath問題

マネジメントコンソールからもSwaggerファイルをインポートできますが、basePathがなくなります。
Gateway API importer: Swagger basePath is ignored during import