AWSのLambdaとAPI Gatewayを使ってサーバレス(EC2レス)なWebアプリケーションを作れるフレームワークServerless Frameworkを使ってサーバレスなサイト監視アプリを作ってみた。

serverless_framework

セットアップ

Serverless Frameworkをnpmでインストール
(事前にnpmとNode V4をインストールしておく必要がある。)

以下のコマンドでプロジェクトを生成する。

入力情報を元に、CloudFormationを使ってdev環境のAWSリソース(S3のバケットやIAM Roleとか)が作成される。

プロジェクトの構成

createコマンドで生成されたプロジェクトは↓のファイル構成になっている。

Serverless Frameworkに関するファイルは↓

  • .env
    この設定ファイルのstageを変更すると変更したstageにデプロイされるように誤解するかもしれないけど、ローカルのテストのみで使われるファイル。
    ※ stageやregionを変更する際は、serverlessのenv setコマンドを使うこと。
    (.gitignoreに記載されており、バージョン管理の対象外)
  • admin.env
    プロジェクト作成時に入力したアクセスキーとシークレットアクセスキーが保存されている。
    (.gitignoreに記載されており、バージョン管理の対象外)
  • s-project.json
    プロジェクトの設定と著作者の情報を設定
  • s-resources-cf.json
    CloudFormationのテンプレートファイル
  • _meta
    stage、region毎のCloudFomationのテンプレートや変数ファイルが保存されているディレクトリ。
    (デフォの.gitignoreには記載されていないけど、ドキュメントでは記載されていると書かれているので、追記してバージョン管理の対象外にする)

ComponentとFunction

このプロジェクトのままでは何のコードも書けないので、続いてComponentを作成する。

Componentの作成

↓のコマンドでcomponentを追加する。

Functionの作成

するとプロジェクトに下記構成が追加された状態になる。
ComponentとFunctionの粒度は、RESTでいうリソース=Component、アクション=Functionの粒度で作成する。
今回はWebサイトの監視を行うツールを作成するのでリソース=Siteとし各アクション(show,create,index,delete)をfunctionとして追加すると↓のような構成になる。

各Functionのファイル

  • s-function.json
    API Gatewayのエンドポイントの設定情報と対応するLambda Functionの設定情報
    ※ デフォルトで生成されるファイルはmemorySizeが1024MBになってるけど、そんなに使うこと無ければ最小の128MBとかでもOK。
  • handler.js
    Lambda Functionのハンドラ
  • event.json
    ローカルでテスト実行する際に使用するリクエストデータを設定

Viewテンプレート

API Gatewayにはリクエストやレスポンスを処理する際にデータの形式を変換できるマッピングテンプレートが用意されている。s-function.jsonのrequestTemplatesとresponseTemplatesにそれぞれContent-Typeをキーにして使用するテンプレートを指定する。このテンプレートエンジンには、Apache Velocityが使わている。

ただServerless Frameworkの場合、テンプレートはs-function.json内に記述するかComponentもしくはProject直下のs-templates.jsonに記述する必要がありいずれもJSON形式である。Velocityで記載できるのは良いのだけど、JSONの値としてViewを書くのはツラいので(外部ファイル使えたりするのか?)、今回は、ejsというJavaScriptベースの軽量テンプレートエンジンを利用して、Lambda Function内でテンプレート適用したHTMLをビルドする構成にした。

handler.jsに↑なコードで、テンプレートのHTMLファイルを読み込んで、ejsでパラメータをバインドすることができる。

エンドポイントのURLの取得

REST APIを公開するアプリケーションであれば意識する必要はないが(RESTful hypermedia APIとか利用するなら必要)、Webアプリケーションで画面の遷移を伴う場合、遷移先のURLが必要になる。Lambdaからレスポンスとして返すHTML内でそのリンクを記述する必要があるのだけど、Lambda単体ではどういうURLでアクセスされてきたのかは分からない。

そういうケースでは、API GatewayのItegration Requestのマッピングテンプレートを利用する。

http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/models-mappings.html#models-mappings-mappings

  • $input.params().header
  • $context.resourcePath
  • $context.stage

を組み合わせることでURLが取得できる(相対パスであればstageだけ分かれば問題ない)。

Serverless Frameworkでは、各Functionのs-function.jsonのrequestTemplatesに上記マッピングパラメータを任意のキーにマッピングすれば、handler.js内のeventオブジェクトに対象に値がセットされた状態で受け取れる。

AWSリソースの利用

Serverless FrameworkはAPI Gateway と Lambdaを組み合わせたフレームワークだけど、Lambdaのファンクションから他のAWSサービスを利用したいケースもある(DBとか)。今回はデータをDynamoDBにストアしようと思うので、DynamoDBが利用できるようs-resources-cf.jsonに、DynamoDBを利用できるようPolicyの追加と作成するDynamoDBのテーブル情報を定義する。

記述したら上記リソースをAWSの環境に↓のコマンドで反映する。

完了したらAWS上のDynamoDBに↑で定義したテーブルが作成されている。
s-resources-cf.jsonに定義したテンプレートを各ステージ毎に展開した結果が、_meta/resourcesと_meta/variablesにそれぞれ保存される。

デプロイ

serverless dash deployコマンドを実行すると、コンソール上でインタラクティブなデプロイができる。

デプロイ対象にfunctionを選択すればfunctionがLambda Functionとして、endpointを選択すればAPI Gatewayにデプロイされる。デプロイが完了するとendpointのURLにアクセスすればデプロイしたアプリケーションが確認できる。
(serverless function deployやserverless endpoint deployコマンドで直接デプロイも可能。)

ログの確認

Lambda Functionの実行ログは、Functionのディレクトリに移動して↓のコマンドで確認できる。(-tはtailオプション)

※ serverlessはコマンドとしては長いのでslsというエイリアスが定義されている。

コード

今回作ったコードは↓

https://github.com/azuchi/serverless-web-monitor

今のところできるのはサイトのDynamoDBへの登録・確認と、登録されているサイトへの監視リクエストの発行。
今後レスポンスコードによってSNSによる通知の実装やサイトの削除など実装していく。

Plugin

その他Serverless Frameworkでは、以下のようなPluginが提供されている。

  • Plugin Boilerplate
    Serverless FrameworkのPluginの作成を支援するためのスタータープロジェクト。
  • Serve
    API GatewayをローカルでシミュレートするPluginで、API Gatewayの呼び出しを全てlocalhostに対して行う。
  • Offline
    Serveと同様API GatewayとLambdaをローカルでエミュレートするPlugin
  • Alerting
    Lambda Functionに対してCloud Watchのアラームを追加するPlugin
  • Optimizer
    Lambda Functionのサイズを削減し、実行パフォーマンスを向上させるPlugin
  • CORS
    CORS (Cross-origin resource sharing)サポート用のPlugin
  • CloudFormation Validator
    CloudFormationのテンプレートファイルの検証を行うPlugin
  • Prune
    古いバージョンのLambda Functionを削除するPlugin
  • Base-Path
    API Gatewayのエンドポイントにベースとなるパス(/apiみないな)を設定するPlugin
  • Test
    Serverless用のテストフレームワーク
  • SNS Subscribe
    Lambda Functionに簡単にSNS Notificationを適用するPlugin
  • JSHint
    Lambda FunctionにJSHintを適用するPlugin
  • Webpack
    Optimizer PluginをフォークしたPluginで、OptimizerがProduction環境のLambda Functionの最適化を行うのに対し、こっちは開発環境のLambda Functionの最適化を行うPlugin
  • Serverless Client
    Serverlessプロジェクトのweb clientをS3のバケットにデプロイするPlugin

ベストプラクティス

Serverless Frameworkのドキュメントでは以下のようなベストプラクティスが紹介されている。

  • Admin権限を持つAWSのアクセスキーを使わない
    スタートガイド等ではAdmin権限を持つアクセスキーを使っているが、実際には最大でもPowerUserAccess権限までが推奨される。
  • Lambdaのコードベースはできるだけ小さくする
    コードサイズが小さいほどLambda実行時のコンテナは速く起動し実行される。
  • Lambdaコードの再利用
    全てのServerless FunctionsはComponent内のlibフォルダを利用できるため、function内で同じようなコードロジックがある場合はlibフォルダ内に集約するのがオススメ。
  • CloudFormationリソースを整理する
    CloudFormationのリソースはs-resources-cf.jsonで定義できる。Serverless外でプロビジョニングするのも可能だけど、コードとセットで構成も管理しておくのがオススメ。
  • 外部サービスの初期化はLambdaのコード外で行う
    DynamoDBのようなサービスを使う場合、その初期化はLambdaコード外で行う。(Nodeのmodule initializerやJavaのstaticコンストラクタのような。)もしDynamoDBのコネクションの初期化をLambda Function内に書くと、Function実行時に毎回実行されることになる。

参考

所感

  • API Gateway + Lambdaのコンポーネントの数が増えると設定を含めそれらの構成を管理するのが大事になってくるので、Serverless Framewortkのようにフレームワークで構成を管理できるのは便利。
  • デプロイまでサポートしているので、Lambda FunctionやAPI Gatewayのエンドポイントの登録など自動でやってくれるの便利。
  • responseTemplatesにキーが”text/html”で値がVelocityテンプレートなデータを設定するのは可能だけど、VelociityのViewデータを文字列でJSONファイル内に記載するのはシンドイ。外部ファイルを指定できると良いなー。(AWSのコンソールからは任意の形式でテンプレート記述できるので)
  • node.jsなコード書き慣れてないので同期的なコード書いていろいろハマった。