«前の日記(2011-10-01 (土)) 最新 次の日記(2011-10-07 (金))»  

まちゅダイアリー


2011-10-02 (日)

Heroku上でSinatraアプリを動かすまでのまとめ

Amazon認証リバースプロキシをHeroku + Sinatraに移植した。 Heroku上でSinatraアプリを動かすまでに調べたこと、やったことを過去の日記へのリンクを含めて整理しておく。

Product Advertising API用リバースプロキシ

環境準備その1 (git, rvm)

まず、以下のツールを用意する。

  • git … バージョン管理ツール
  • rvm … Ruby環境の管理ツール
  • bundler … Rubyのライブラリ管理ツール
  • heroku … Herokuの管理ツール

gitは基本なので説明不要か。rvmは複数のRubyバージョンを混在させるためのツール。 Macのデフォルトは1.8.7だけど、Herokuだと1.9系を使いたいのでrvmを入れておくと便利。 gitとrvmの使い方はこの日記を参照。

時代は1.9。Herokuの最新環境のサポートも1.9.2のみなので、1.9.2の最新版をインストールしておく。

$ rvm list known
$ rvm install 1.9.2

最近ではrvmよりシンプルなrbenvというのもある。

環境準備その2 (bundler)

RubyのライブラリはRubyGemsが定番だけど、RubyGemsだけだとライブラリがグローバルにインストールされてしまう。 そのため、bundlerを使って、アプリケーションごとに必要なライブラリを指定できるようにする。 こうしないと、Heroku側へ必要なライブラリを伝えることができない。

rvm で 1.9.2 の環境を使うようにして、gemでbundlerをインストールすると、bundleコマンドが使えるようになる。

$ rvm use 1.9.2
$ gem install bundler

herokuでHello World

まず、Herokuのサイトでアカウントを作っておく。 最小構成で動かすだけなら無料。Addonを使うには、(無料のアドオンであっても)クレジットカードの登録が必要。 Herokuのチュートリアルを読めば、基本的な動かし方はだいたい分かる。

最近、Herokuの環境 (stack) が新しくなって、node.jsやJavaやPythonなどのRuby以外の環境も動かせるようになった。 その影響でProcfileをなどのHeroku特有のファイルも必要になっている。 スタックについては、以下の記事を参照。 どの環境を使うかは、heroku stackコマンドで切り替えられる。

この時点でHello Worldアプリを作って、Herokuの使い方を把握しておくと良い。 さっき紹介したGetting Staetedの手順どおりにやればできる。

$ rvm use 1.9.2
Using /Users/machu/.rvm/gems/ruby-1.9.2-p290
$ mkdir machu-test
$ cd machu-test
$ vim web.rb

web.rbの中身はこれ。

require 'sinatra'

get '/' do
  "Hello, world"
end

次にGemfile。

$ vim Gemfile

中身はこれ。

source :rubyforge
gem 'sinatra', '1.1.0'
gem 'thin', '1.2.7'

bundleコマンドでGemfileに書いたライブラリをインストールする。

$ bundle install
Fetching source index for http://rubygems.org/
Installing daemons (1.1.4) 
(中略)
Installing sinatra (1.1.0) 
Installing thin (1.2.7) with native extensions 
Using bundler (1.0.18) 
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

次はHerokuの起動ファイル。

$ vim Procfile

中身はこれ。 bundlerを経由してweb.rbを起動している。

web: bundle exec ruby web.rb -p $PORT

Procfileを読み込むためにforemanというgemを入れ、アプリをローカルで起動。

$ gem install foreman
$ foreman start
10:53:58 web.1     | started with pid 82448
10:53:59 web.1     | /Users/machu/.rvm/gems/ruby-1.9.2-p290/gems/rack-1.3.4/lib/rack/backports/uri/common_192.rb:53: warning: already initialized constant WFKV_
10:53:59 web.1     | == Sinatra/1.1.0 has taken the stage on 5000 for development with backup from Thin
10:53:59 web.1     | >> Thin web server (v1.2.7 codename No Hup)
10:53:59 web.1     | >> Maximum connections set to 1024
10:53:59 web.1     | >> Listening on 0.0.0.0:5000, CTRL+C to stop

ブラウザでhttp://localhost:5000/に接続して、Hello Worldと表示されることを確認する。

OKなら、gitリポジトリを作成してコミット。

$ git init
$ git add .
$ git commit -m "init"
[master (root-commit) a2a76f3] init
 4 files changed, 30 insertions(+), 0 deletions(-)
 create mode 100644 Gemfile
 create mode 100644 Gemfile.lock
 create mode 100644 Procfile
 create mode 100644 web.rb

herokuコマンドで、Heroku上にアプリを登録する。--stackオプションでcedarスタックを選択して、アプリ名にmachu-testと名付けている。

$ heroku create --stack cedar machu-test
Creating machu-test... done, stack is cedar
http://machu-test.herokuapp.com/ | git@heroku.com:machu-test.git
Git remote heroku added

メッセージに表示されているように、この時点でgitのremoteにherokuリポジトリが登録される。

$ git remote
heroku
$ git remote show heroku
* remote heroku
  Fetch URL: git@heroku.com:machu-test.git
  Push  URL: git@heroku.com:machu-test.git
  HEAD branch: (unknown)

herokuリポジトリにアプリケーションをpushすれば、それがデプロイになる。なんとも大胆なアプローチ。

$ git push heroku master
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 667 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)

-----> Heroku receiving push
-----> Ruby app detected
-----> Installing dependencies using Bundler version 1.1.pre.10
       Running: bundle install --without development:test --path vendor/bundle --deployment
       Fetching dependency information from the API at http://rubygems.org/....
       Installing daemons (1.1.4)
       Installing eventmachine (0.12.10) with native extensions
       Installing rack (1.3.4)
       Installing tilt (1.3.3)
       Installing sinatra (1.1.0)
       Installing thin (1.2.7) with native extensions
       Using bundler (1.1.pre.10)
       Your bundle is complete! It was installed into ./vendor/bundle
       Cleaning up the bundler cache.
-----> Discovering process types
       Procfile declares types -> web
       Default types for Ruby  -> console, rake
-----> Compiled slug size is 6.2MB
-----> Launching... done, v4
       http://machu-test.herokuapp.com deployed to Heroku

To git@heroku.com:machu-test.git
 * [new branch]      master -> maste

Webブラウザで「アプリ名.herokuapp.com」にアクセスすれば、Hello Worldが表示される。 作成したアプリケーションの情報はHerokuのWebサイト https://api.heroku.com/myapps でも確認できる。

アプリケーションの構成を考える

Herokuの概要が分かったので、次は作成するアプリケーションのソフトウェア構成を考える。

  • プラットフォーム
  • フレームワーク
  • データベースとO/Rマッパー
  • ビューテンプレート

決めるのはこんな感じか。

(1) プラットフォーム

プラットフォームはRuby (Rack) か Node.js から選択した。 Node.jsは同時接続数が多い場合に有利だけど、非同期処理+コールバック関数を使うのでコーディングがちょっと大変。

結局、慣れている Ruby を使うことにした。

(2) フレームワーク

定番ものではSinatraかRailsの二択。 Railsは王道。情報もプラグインも多い。ジェネレータも充実している。 ただ、最初は「規約」を覚えるのが大変。scaffoldで生成したコードが何をやっているのかを理解するまでに、少し時間がかかる。 一方のSinatraはシンプルで取っつきやすいのが特徴。

認証プロキシの場合、画面数も多くないので取っつきやすいSinatraを選択した。 Sinatraの基本は、README(日本語)を読めばだいたい分かる。

もう少し詳しい使い方を知りたい場合は、以下のドキュメントが参考になる。

(3) データベースとORM(モデル)

次はデータを保存するためのデータベース。 Herokuで標準で使えるデータベースは、RDBのPostgreSQL。 5MBまで無料で使える。 NoSQL系だと MongoDB や CouchDB がアドオンとして提供されている。

  • MongoHQ … 16MBまで無料
  • MongoLab … 240MBまで無料
  • Cloudant … 250Mbまで無料 (bが小文字なのはByteじゃなくてbit?)

PostgreSQL以外はHerokuではなく外部のホスティングに依存することになる。 でも、最初の始めやすさを考えて、MongoDBを提供するMongoHQを使うことにした。 とりあえず、以下のドキュメントを読む。

Mongoidはなんとなく、ActiveRecordと同じように使える(どちらもActiveModelをサポートしている)けど、RDBとNoSQLの概念の違いは把握しておいたほうがいい。 ちょっとアプリを作るときには、スキーマレスのMongoDBの方が気軽に使える。

MongoHQを有効にするには、heroku addons:addコマンドで指定する。

$ heroku addons:add mongohq:free

MongoHQへの接続情報はheroku configコマンドで確認できる。

$ heroku config
GEM_PATH    => vendor/bundle/ruby/1.9.1
LANG        => en_US.UTF-8
MONGOHQ_URL => mongodb://ID:パスワード@staff.mongohq.com:10051/データベース名
PATH        => vendor/bundle/ruby/1.9.1/bin:/usr/local/bin:/usr/bin:/bin:bin

ここに表示される接続情報 (MONGOHQ_URL) をMongoHQのWebサイトに登録しておけば、Web上の管理ツールからもデータベース内を見ることができる。

MongoHQ

あと、HerokuではMONGOHQ_URLの例のように、Herokuでは環境変数で設定値を指定することが多いので覚えておくといい。

(4) ビューテンプレート

いろいろ選べるけど、erbとhamlで迷って今回はhamlを選択した。 hamlの記法とRuby1.9のJavaScript風ハッシュ記法は相性がいい。 こんな感じで、カッコ内のオプション指定を簡潔に書ける。

%h3 プロフィール
%form{action: "/profile/#{current_user.id}", method: 'post'}
  %input{type: 'hidden', name: '_method', value: 'put'}

Gemfileを書く

この時点でのGemfileはこうなった。

source 'http://rubygems.org'
gem 'sinatra'
gem "mongoid", "~> 2.2"
gem "bson_ext", "~> 1.3"
gem "omniauth"
gem "haml"
gem "rack-flash"

説明していなかったけど、omniauthはTwitter認証を実現するライブラリ。 ユーザ認証は、以前Railsで試したときの手順が参考になった。Sinatraでも同じようにやればいい。 自前でパスワードを管理したくないので、Twitterの認証を使う。 Twitterの開発者向けサイトでアプリを登録してTwitterのKeyとSecretを入手しておく。

rack-flashはRailsのflash相当の機能を提供するRackミドルウェア。

bundle installで必要なライブラリをインストールする。

アプリを書く

Sinatraのいいところは、とりあえず何も考えずに1つのファイルに書いていけること。 もちろん、いつかはRailsのようにconfig, models, controllersを分離したほうがいいんだけど、最初は1つのファイルに一気に書いた方が楽だし分かりやすい。 参考までに、Twitter認証機能までを作った時点では、モデルもDBへの接続設定も1ファイルに全部書いていた。

僕としては、あとで困ってから分ければいい、くらいの考えでとにかく書いた方が、いろいろ悩むよりは良かった。

テストを書く

rspecでテストを書く。モデルのテストは(フィクスチャやジェネレータがないことを除けば)RailsもSinatraもそう変わらない。 Travis CIでテストを自動実行したかったので、Rakefileを書いた。

テストを書きだすと、コントローラーとモデルを別ファイルに分離したくなったので、rpaproxy.rbからmodels/user.rbとmodels/proxy.rbを切り出した。 こうやって必要になってから分けるメソッドも悪くない。

テストはまだちゃんと書けてない。 本格的にテストを書くにはモックとスタブを活用しないとダメかも。

ソースコードのコミットとHerokuへのデプロイ

ローカルでのコミットはこまめにやる。 何をやったのかを後で思い出せるようにするためにも、1つの作業ごとにコミットしていく。

$ git status
$ git diff
$ git add ファイル名
$ git push origin master
$ git push heroku master

ただ、このままだとコミットがたくさんできすぎるので、慣れてきたらブランチを切ってローカルコミットをあとでまとめるのがいいかも。

サービスを運用する

アプリを作ってHerokuに配置した後は、運用のことも考えておかないといけない。 開発環境と違って自分のPC上にスタックトレースが表示される訳じゃないので、ログが重要になる。 Heroku標準のログ機能では500行しかログが残らないので、25MBで1日間ログを残してくれるLogglyアドオンを導入した。

Loggly

アプリケーションのパフォーマンス管理には、New Relicを使う。

Ner Relic

まとめ

認証リバースプロキシを作ってみて強く思ったのは、いきなり完璧なものを作ろうとしないことが大切だということ。 最初から綺麗なアプリを書こうとすると、いろんなサンプルを調べることに時間がかかってしまう。 理論より実践で、まずは動くコードを書く。その後で変だと思ったところをリファクタリングしていく。 この順序の方が、「なぜこう書かないといけないのか」を理解しやすい。 例えば、Modelを分離しておかないとテストが書きにくいとか、ね。

カッコ悪くても動くものを優先する。これは勇気がいることだけど、徐々に自分のレベルが上がっていくのが分かる。 そして、Heroku+Sinatra+MongoDBの構成は、「まず動かしてみる」には最適な環境。いい時代だねぇ。