Heroku で Rack アプリケーションを動かしてみた

もう WEB+DB PRESS vol.59 の Heroku 紹介記事はお読みになりましたか? Heroku というのは Ruby 版の GAE*1 みたいなもので、Ruby 製の Web アプリケーションをホスティングしてくれるサービスです。小さな環境なら無料で使用できます。わたしは Web アプリケーションにはあまり興味がなかったのでこれまで存在は知っていてても敬遠してきたのですが、ruby trunk changes をはじめてからコメントをコミット毎に保存するような支援ツールが欲しくて、iPadiPod touch を含む複数の環境で入力できるという条件を考えると Web アプリケーションとして作る必要がありそうなので検討してみました。
記事を読むまで Heroku は Rails 専用のホスティング環境だと思っていたのですが、実際は Rack の上で動くフレームワークなら利用できると知りました。実は別のアプリケーションでちょっと HTTP インタフェースを付けるために最近 rack の使い方を学んで、その手軽さに感動したところだったので、これなら簡単にできそうだなと思って、Rails は使わずに Rack を直に使って書くことにしました。WEB+DB PRESS の記事は主に Rails でアプリケーションを作る方法について書かれていて、rack 対応のアプリケーションについては config.ru を作っておけばそれを rackup してくれるということが書かれている程度です。これさえわかればなんとかなるだろうと思ったのですが、けっこうはまったので手順とはまりどころを残しておきます。Web アプリケーションに慣れている人ならなんでもないことなのかもしれませんけど。

アカウント作成

http://api.heroku.com/signup からメールアドレスを記入してアカウントを作成します。メールが届くのでリンクを開いてパスワードを設定します。

heroku ツールインストール

heroku のアプリケーション管理などを実行するツールが RubyGems で提供されています。これをインストールします。

$ gem install heroku

git リポジトリを作成

Heroku は git でリポジトリ管理するのが前提になってます。rails なら雛形を作ってくれますが今回は自分で 1 から作るのでとりあえず config.ru だけ含むリポジトリを作っておきます。

$ mkdir myapp
$ cd myapp
$ vi config.ru
$ git init
$ git add -A 
$ git commit -m "first commit"

config.ru の内容はとりあえずこんなので充分です。

run lambda{|env| [ 200, { "Content-Type" => "text/plain" }, [ "test" ] ] }

rack 自体の使い方は割愛します。わたしは以下の記事を参考にしました。
Route 477(2008-07-16)
第23回 Rackとは何か(1)Rackの生まれた背景:Ruby Freaks Lounge|gihyo.jp … 技術評論社
http://labs.unoh.net/2007/05/rackweb.html

heroku アプリケーションを作成

今作成した git リポジトリディレクトリ内で heroku create というコマンドを使うと Heroku に新しくアプリケーションを作成して、今の git リポジトリと Heroku のアプリケーションを関連づけてくれます。

$ heroku create <アプリケーション名>

この時アカウント登録した時のメールアドレスとパスワードをきかれます。

heroku create は内部的には以下のことをしています。

  1. heroku にアプリケーション作成要求を投げる
  2. heroku のアプリケーション作成が完了したかをポーリングで確認しながら待つ(... と表示されている間はポーリング中です)
  3. git のリポジトリの remote に heroku という名前でリモートのリポジトリを登録する

わたしは最初に heroku create した時なぜか ... の表示が止まらなくなってしまい、途中で止めてしまいました。そこで heroku コマンドの中をチェックして、あとは git の remote 登録すればいいとわかったのでそこを手動でやってみたのですが、そうすると次のデプロイで「まだ作成中です」と言われてしまいました。そこで一旦 https://api.heroku.com/myapps から作成中のアプリケーションのページに飛んで "Destroy App" ボタンを押して削除し、 heroku create からやりなおすと今度はあっさり作成できました。

Platform Stack の変更

Heroku は使用する ruby のバージョンをいくつか用意してくれていて、選択できるようになっています。これを Platform Stack と呼ぶそうです。わたしは 1.9.2 (というか trunk) で生活しているので、bamboo-mri-1.9.2 という Platform Stack を選択しました。

$ heroku stack:migrate bamboo-mri-1.9.2

デプロイ

いよいよコードを Heroku に送って動かしてみます。なんと Heroku ではコードを git push すると勝手にセットアップして環境設定(RubyGems のパッケージインストールなど)やアプリケーションの再起動などをやってくれます。すごいですね。

$ git push heroku master

これで http://<アプリケーション名>.heroku.com/ にアクセスすると上の適当に作った config.ru の返す test とかが表示されます。

LOAD_PATH について

さて config.ru から自分のアプリケーション用のクラスの定義は別のスクリプトに分離して require しよう、と思ったらできませんでした。config.ru で $LOAD_PATH を表示させてみるとどうやらリポジトリ内のファイルが置かれているパスは含まれないようです。おそらく Rails ではそこも面倒をみてくれているのだと思いますが、素の Rack だけ使っているのでここは自分で解決しないといけません。
config.ru の先頭に以下のような行を追加します。

$LOAD_PATH << File.join(File.expand_path(File.dirname(__FILE__)), "lib")
require "app"

ここではリポジトリ内の lib/ というサブディレクトリ内に app.rb を置いて、そこに自分のアプリケーションを書くことにしています。ファイル名は適当なのでそれに応じて require する feature 名を変更します。
なお 1.9.2 なので require_relative を使えばいいかなと思ったのですが、どうも禁止されているようでエラーになってしまいました。

DB について

Heroku では DB は PostgreSQL だけが利用でき、ファイルシステムへの保存はできないそうなので、データを保存するには何かの DB アクセス用のライブラリを使う必要があります。
http://docs.heroku.com/rack には ActiveRecord、DataMapper、Sequel を使う方法が紹介されています。しかしここのサンプルコードには typo があるので注意が必要です require "active_record" / require "rubygems" -> require "datamapper"">*2

わたしは普段 sqlite くらいしか使うことがないので、どのライブラリがいいのかよくわかりませんでしたが、ActiveRecordRails でも使われていて有名だし、使いやすいという評判をきいたことがあるのでとりあえず ActiveRecord を使ってみることにしました。
こんなコードを書いておけばいいそうです。

dbconfig = YAML.load(File.read('config/database.yml'))
ActiveRecord::Base.establish_connection dbconfig['production']

config/database.yml は Heroku が自動的に生成してくれるファイルで、ここにデータベースの情報が入っているみたいです。

使用する RubyGems パッケージについて

さて rack は最初から require された状態なのでよかったのですが、ActiveRecord を利用するには RubyGems のパッケージをインストールしておかないといけません。Heroku ではアプリケーション毎に依存している RubyGems パッケージを指定して、それをインストールした環境を作って動かしてくれるようです。

パッケージを指定する方法には2つあって

  1. ".gems" ファイルにパッケージ名を記述する
  2. "Gemfile" に記述する

それぞれ http://docs.heroku.com/gemmanifesthttp://docs.heroku.com/bundler にドキュメントがあります。
最初は ".gems" ファイルに記述する方法を使っていましたが、この方法は deprecated で Gemfile を使うほうが推奨と書かれていたのでそっちを使うことにしました。

Gemfile は Bundler という Rails3 で導入されたらしい RubyGems パッケージの管理をしてくれるツールの設定ファイルらしいです。なのでこれを利用するために bundler パッケージをローカルにもインストールしておく必要があります。なお Bundler が利用できるのは Platform Stack の bamboo シリーズだけらしいので、古い Platfrom Stack を使う時は .gems ファイルに書くほうを使いましょう。

$ gem install bundler

Gemfile には RubyGems パッケージのダウンロード元となる source と利用する gem パッケージを以下のように記述すればいいようです。この例は rack と activerecord を依存パッケージとして指定しています。

source :rubygems
gem "rack"
gem "activerecord", :require => "active_record"

activerecord の :require は、パッケージ名は "activerecord" だけど、require に渡す feature 名は "active_record" と異なるので、それを指定するためのものです。Heroku のドキュメントに typo があったので、わたしはここでしばらくはまりました。

この他特定のバージョンを指定してインストールすることなどができるようです。

このように Gemfile を作成したら、 bundle コマンドを使って Gmefile.lock というファイルを生成させます。このファイルもリポジトリに追加しておく必要があります。

$ bundle install
$ git add Gemfile.lock

さらに、 confing.ru の $LOAD_PATH の設定の前に以下の行を追加します。

require "bundler"

Bundler.require

これで "rack" と "active_record" は require 済みの状態になるみたいです。
なお、rack はもともと heroku の環境に存在するのですが、どうやら Bundler を利用するとその他の RubyGems パッケージは require できなくなってしまうみたいなので、Gemfile を利用する時は rack も追加してあげないといけません(.gems ファイルを利用する時は不要でした)。

これで諸々変更点をコミットして git push すれば、必要な gem パッケージがインストールされアプリケーションが再起動します。まだ実際の DB アクセスはしていませんが、準備は整ったはずです。

*1:Google App Engine

*2:require "activerecord" -> require "active_record" / require "rubygems" -> require "datamapper"