Claudia.jsで簡単サーバレスアーキテクチャー
サーバレスアーキテクチャと聞いて、どのようなアーキテクチャを思い浮かべるでしょうか。Martin Fowler.comのサーバレスアーキテクチャの解説記事ではいくつかのサーバレスアーキテクチャの実装例がリストアップされています。
ここでは、最もよく使われると思われるフロントエンドアプリのためのサーバレスアーキテクチャを考えてみます。
フロントエンドアプリのためのサーバレスアーキテクチャ
API Gateway、Lambdaを中心とした標準的な構成です。
- フロントエンド(SPA/Native)
- 認証認可API
- API Gateway
- FaaS (Function as a Service)
- 代表的なサービス: AWS Lambda
- 役割: BaaS連携、業務ロジック実行
- BaaS (Backend as a Service)
- 代表的なサービス: AWS DynamoDB, S3
しかしながら、実際にAWS上で一から実装しようとするとAPI GatewayとLambdaの連携、認証認可の実装、Lambdaの管理、デプロイや更新処理など面倒極まりない作業に直面します。何らかの抽象化レイヤーやツール無しにAWS上でサーバレスアーキテクチャを実現するのは困難だと実感しています。
ここでは、Claudia.jsというNode.js製のフレームワークを用いてこれらの課題を解決し、フロントエンドアプリのためのサーバレスアーキテクチャーを簡単に実現してみます。
Claudia.jsとは
AWS上でサーバレスアーキテクチャを実現するためのフレームワークの一つです。同様のフレームワークとしてServerlessやApex等が知られています。
- Apex: Lambdaにフォーカス。作者のTJがAPI Gatewayをの1st citizenで無いと言っており連携が煩雑。
- Serverless: YamlにAWSの設定を大量に記述するスタイル。抽象化度合いが低い。
Claudia.jsはこれらと比較した際に、API Gatewayや外部認証までをも含めたサーバレスアーキテクチャ全体を通して実装するのが簡単です。Node.jsでExpressを用いたAPIサーバを書いていくようなイメージで、設定ファイルを記述していくServerless等と比較して柔軟な処理が行えます。
準備
1. Claudia.jsをインストールします。
npm install -g claudia
2. プロジェクトを作成します。
mkdir [Proj Name] && cd [Proj Name] npm init npm install --save claudia-api-builder npm install --save aws-sdk npm install --save axios
実装
以下のようなフロントエンドアプリを実装してみます。エラー処理や詳細は割愛します。また、Hello World的なものはCloudia.jsのTutorialをみた方が手っ取り早いです。
- フロントエンドアプリがSNSの認証認可APIからOAuthのアクセストークンを取得する。(略)
- フロントエンドアプリがアクセストークンを付与してAWS API Gatewayにリクエストを投げる。(略)
- AWS API GatewayがSNSの認証認可APIを用いてアクセストークンを検証する。
- AWS API GatewayがFaaSの関数を呼び出す。
- AWS Lambdaの関数がAWS DynamoDBのテーブルからデータを取得してフロントに返す。
1. カスタム認証の作成
1. カスタム認証用のLambda関数を作成します。
authorizer.js
を作成します。Access Token検証APIを呼び出して有効な場合にAPI実行権限を付与しています。
var axios = require('axios'); //API実行ポリシーの生成 var generatePolicy = function(authToken, methodArn, context) { var awsInfo = methodArn.split(':'); var region = awsInfo[3]; var accountId = awsInfo[4]; var apiInfo = awsInfo[5].split('/'); var restApiId = apiInfo[0]; var stage = apiInfo[1]; var method = apiInfo[2]; //アクセスさせたいリソース var resource = 'arn:aws:execute-api:' + region + ':' + accountId + ':' + restApiId + '/' + stage +'/' + method + '/*'; return { 'principalId': authToken.split('-')[0], 'policyDocument': { 'Version': '2012-10-17', 'Statement': [{ 'Effect': 'Allow', 'Action': ['execute-api:Invoke'], 'Resource': [resource] }] } }; }; var url = "Access TokenのValidateを行うAPIのURL"; //Tokenの検証とポリシーの付与 exports.auth = function auth(event, context, cb) { axios.get(url, { params: { access_token: event.authorizationToken //HTTP headerのAuthorizationキーの値 } }).then(function (res) { var is_valid = true|false; //Tokenの有効性を検証して結果を格納 if (is_valid) { cb(null, generatePolicy(event.authorizationToken, event.methodArn)); } else { cb("Invalid Access Token"); } }); };
2. デプロイ
claudia
コマンドでカスタム認証用のLambda関数をAWSにデプロイします。
claudia create --name custom-authorizer --region us-east-1 --handler authorizer.auth --config claudia-auth.json
--name
: AWS Lambda上の関数名--region
: AWSのリージョン名--handler
: カスタム認証の関数名--config
: Claudia.js用の設定ファイル名(自動作成されるので任意)
{ "lambda": { "role": "custom-authorizer-executor", "name": "custom-authorizer", "region": "us-east-1" } }
2. APIとLambda関数の作成
1. APIとLambda関数を作成します。
app.js
という名前にします。
var ApiBuilder = require('claudia-api-builder'); var api = new ApiBuilder(); var AWS = require('aws-sdk'); var dynamodb = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'}); api.registerAuthorizer('apiGatewayAuthorizer', { lambdaName: 'custom-authorizer', //AWS Labmda上のカスタム認証の名前 headerName: 'Authorization' //Tokenを格納するHTTP headerのKey名 }); api.get('/news', function (req) { return dynamodb.get({ TableName:'news', Key:{ "id": req.headers.id } }).promise().then(function(res) { return res.Item; }); },{ customAuthorizer: 'apiGatewayAuthorizer' }); module.exports = api;
2. デプロイ
claudia
コマンドでAWSにデプロイします。設定ファイルclaudia.json
が自動で作られます。
claudia create --region us-east-1 --api-module app
{ "lambda": { "role": "myproj-executor", "name": "myproj-proj", "region": "us-east-1" }, "api": { "id": "[PID]", "module": "app", "url": "[エンドポイントのベースURL]" } }
3. AWS上の設定
AWS DynamoDBのテーブル作成
news
テーブルを作成するaws dynamodb create-table --table-name news --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1
id
キーで検索できるようにレコードを追加。aws dynamodb put-item --table-name news --item '{"id":{"S":"id001"}, "content":{"S":"News content 001"}}' aws dynamodb put-item --table-name news --item '{"id":{"S":"id002"}, "content":{"S":"News content 002"}}' :
AWS Lambdaのrole設定
- IAM > Roles > [作成したRole名]
- Attach Pollicy >
AmazonDynamoDBFullAccess
- 強すぎるので実際は適当なPolicyを作成
4. API実行
PostmanなどでHTTPヘッダの
Authorization
キーにAccess Tokenを、id
キーに存在するidを付与して投げます。Claudia.jsでAPIを作成した際にはCORSはデフォルトでONになっていますが制限することもできます。
5. アップデート
- スクリプトを修正します。
claudia
コマンドを実行します。
claudia update --config claudia-auth.json claudia update --config claudia.json
まとめ
Claudia.jsを利用すれば、外部の認証認可APIを用いたフロントエンドアプリのためのサーバレスアーキテクチャを簡単に実装できます。
以上
Web API用の負荷テストツールで遊んでみる
これはWeb API Advent Calendar 2014の16日目の記事です。
はじめに
Web API Advent Calendar 2014 8日目ではkazuchikaさんよりApigee製のAPI負荷ツールapibの紹介がありました。
この記事ではapibに加えてBoomというGolang製の類似ツールを簡単に紹介し、様々なマイクロWebフレームワークに対して両者を使ってみます。
Boom
- Python製のAPI負荷テストツールBoomをGo言語に移植したもの
- Googleエンジニアが開発
- Apache Bench(ab)と同じくリクエスト回数を指定した計測
- レスポンス時間がヒストグラム表示される
- ソースコードがコンパクト
インストール
Golang環境がセットアップされているものとします。
go get github.com/rakyll/boom
実行
boom -n 回数 -c 並列度 [URL]
その他オプションはboom
コマンドで閲覧できます。
apib
- C言語製
- Apigeeエンジニアが開発
- リクエスト回数ではなく計測時間を指定する
- Warm upの時間を指定できる
- リモートサーバにデーモンを仕込んだ測定もできる
インストール
brew install apib
実行
apib -d 計測時間 -c 並列度 [URL]
その他オプションはapib
コマンドで閲覧できます。
実行結果
複数のプログラミング言語のマイクロフレームワークを用いて単純なWebAPIを作成して負荷テストツールを実行してみます。条件は以下です。
- boomの場合、並列度100で1万リクエスト
- apibの場合、並列度100で10秒
Node.js - Express
Node.jsのスタンダードなWAFです。
var express = require('express')
var app = express()
var http = require('http');
http.globalAgent.maxSockets = 50;
app.get('/', function (req, res) {
res.send('Hello')
})
var server = app.listen(3000, function () {
})
Node.js - Restify
Expressに似ていますが、Web APIに特化しています。
var restify = require('restify');
http = require('http');
http.globalAgent.maxSockets = 50;
function respond(req, res, next) {
res.send('hello');
next();
}
var server = restify.createServer();
server.get('/', respond);
server.listen(3000, function(){});
Node.js - Hapi
Walmart Lab製のWAFです。自由度の高いExpressや類似フレームワークに対して、ある程度統制を効かせてコードをクリーンに保とうという意識を感じます。WebAPIのルーティングも可読性が高いです。日本ではあまり知られていないように思いますが、小売系の他npm、Mozillaなどtech系企業のユースケースも目立ちます。
var Hapi = require('hapi');
var http = require('http');
http.globalAgent.maxSockets = 50;
var server = new Hapi.Server();
server.connection({ port: 3000 });
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
reply('Hello');
}
});
server.start(function () {});
Ruby - Sinatra
言わずと知れたマイクロフレームワークです。フォロワーのWAFが大量に出たのがわかるミニマルな記法です。WEBRickは遅かったためThinに変更しています。
require 'sinatra'
set :server, 'thin'
set :logging, nil
get '/' do
'Hello'
end
Ruby - Espresso
速度が売りのマイクロフレームワークです。こちらもSinatraに合わせてThinに変更しています。期待してなかったのですが検討しています。
require 'e'
class App < E
map '/'
def index
"Hello"
end
end
App.run :server => :Thin
Python - bottle
恐らくPythonで最もミニマルなWAFで個人的にもよく使っています。WSGIに対応していてアプリケーションサーバをFacebook製のTornadoに変更しました。
from bottle import route, run
@route('/', methods=['GET'])
def index():
return "Hello"
run(server='tornado', host="0.0.0.0", port=3000, debug=False)
Haskell - Scotty
Sinatraに影響を受けたHaskellのマイクロWebフレームワークです。Haskellの中では最も親しみやすい印象でした。この記法でこの性能はいい感じです。
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
main = scotty 3000 $ do
get "/" $ do
text "Hello"
Go - Martini
DIの仕組みをもったGoのマイクロフレームワークです。通常の設定だと標準出力にログが出て速度がやや遅くなったのでzishe/app.goを参考にログを消しています。Golangの人気がうかがえる速度です。
package main
import (
"log"
"github.com/go-martini/martini"
)
func init(){
martini.Env = martini.Prod
}
type myClassic struct {
*martini.Martini
martini.Router
}
func withoutLogging() *myClassic {
r := martini.NewRouter()
m := martini.New()
m.Use(martini.Recovery())
m.MapTo(r, (*martini.Routes)(nil))
m.Action(r.Handle)
return &myClassic{m, r}
}
func main() {
m := withoutLogging()
m.Get("/", func(lg *log.Logger) string {
return "Hello"
})
m.Run()
}
Go - beego
MartiniよりリッチなGoのフレームワークです。マイクロフレームワークかと思っていたらフルスタックとのこと。フルスタックかつ高性能と謳っています。
package main
import (
"github.com/astaxie/beego"
)
type MainController struct {
beego.Controller
}
func (this *MainController) Get() {
this.Ctx.WriteString("Hello")
}
func main() {
beego.Router("/", &MainController{})
beego.Run()
}
注意
この記事はフレームワークの性能を表すものではありません。以下のようにフレームワーク周辺領域での選択やアプリやミドルウェアの構成による影響が大きく、何をもって"フレームワークの性能"と定めるか容易には決められないからです。
- ディスクやネットワークのIO。その性能もORMやコネクタに左右されます。
- シリアライズやバリデーションなど入れ替え可能な関連ライブラリ
- PythonのWSGIやRubyのRackのようにWebサーバとWebアプリケーションフレームワーク間の統一インタフェースが提供されている場合、Webサーバの選択が性能に大きく影響します。
- ルーティングを辞書でなく配列に格納してマッチングするWAFの場合、探索の計算量がO(N)となるため登録されているルーティングが多くなると性能が劣化してしまいます。
※サンプルコードはテキストでHelloという文字列を返すだけでWeb APIとしては不完全です。
感想
WebAPIの負荷テストツールを使うことで新たなフレームワークを試すのが楽しくなりました。この記事以外にも様々なフレームワークを試した結果、記法が美しいものから魅力的なレスポンスタイムを持つものまで様々なフレームワークに出会えましたが、その中で共通して感じたのはWeb APIを実装する敷居が低くなったということです。
昨今、サービスがWeb APIを公開するのはもちろんのこと、Web APIが組み込まれたソフトウェアも目立つようになりました。ソフトウェアのWebAPIがこの記事で使ったようなマイクロフレームワークで実装されていることもあります。
このような流れが加速してサービスもソフトウェアもWeb APIを持つのが当たり前になり、誰もがコードから簡単に操作できる自由な世界が訪れるといいですね。
Enjoy!
OSXとHomebrew-caskでHaskellの開発環境を構築
この記事ではHomebrewではなくHomebrew-caskを使ってHaskellの環境を構築します。
インストールには、以下のOSX用のHaskellディストリビューションを使います。コンパイル済みのバイナリとcabalが入っていて早くてお手軽です。
GHC
Cabal
Haskellのパッケージ管理ツールcabalは、初めからインストールされているのでアップデートするだけです。
cabal自体のアップグレード
cabal install cabal-install
パッケージアップデート
cabal update
OSX 10.10 Yosemiteでhomebrewによるgccインストールのエラーを回避する方法
OSX 10.10 YosemiteのDeveloper Preview版で普通にbrew install gcc
でgccをインストールしようとするとエラーになると思います。ここではパッチを当ててgcc4.9.1をインストールしてみます。
前提
参考
https://github.com/Homebrew/homebrew/issues/29845
方法
homebrewをアップデートしてgccのformulaを最新版にします。
brew update
gccのformulaの編集モードに入ります。
brew edit gcc
2行目に以下の4行を追加します。
patch do url "https://gcc.gnu.org/bugzilla/attachment.cgi?id=33180" sha1 "def0cb036a255175db86f106e2bb9dd66d19b702" end
例
class GCC < Formula # Patch for OSX 10.10 Developer Preview 4 # ref. https://github.com/Homebrew/homebrew/issues/29845 patch do url "https://gcc.gnu.org/bugzilla/attachment.cgi?id=33180" sha1 "def0cb036a255175db86f106e2bb9dd66d19b702" end def arch if Hordware::CPU.type +== : intel 略
インストール
brew install gcc
以上
OSXでssh-copy-idを使ってリモートサーバにパスワードなしでログインする
OSXでssh-copy-id
を使う方法です。
何ができる?
インストール
beautifulcode/ssh-copy-id-for-OSX - GitHubを使います。
ダウンロード
curl https://raw.githubusercontent.com/beautifulcode/ssh-copy-id-for-OSX/master/ssh-copy-id.sh -o /usr/local/bin/ssh-copy-id
実行権限を付与
chmod +x /usr/local/bin/ssh-copy-id
使い方
公開鍵を生成します。
~/.ssh/id_rsa.pub
が存在する場合は不要です。ssh-keygen
以下が生成されます。
~/.ssh/id_rsa.pub
公開鍵を配布します。
通常
ssh-copy-id [ユーザID]@[リモートサーバ] ssh-id-copy root@192.168.100.10 #例
~/.ssh/id_rsa.pub
以外の公開鍵を使う場合ssh-copy-id -i 公開鍵のフルパス [ユーザID]@[リモートサーバ] ssh-id-copy ~/.ssh/myidentity.pub root@192.168.100.10 #例
※ ログインパスワードを聞かれるので入力します。
sshでリモートのサーバにログインします。
ssh [ユーザID]@[リモートサーバのアドレス] ssh root@192.168.100.10 #例
※ ログインパスワードは聞かれません。