オープンソースのPaaSソフトウェア CloudFoundry の技術情報やイベント告知などを掲載します

2015-07-01

Expense を Cloud Foundry で動かす

月が変わり、「Cloud Foundry 百日行」も本日第20回を迎えることとなりました。本日取り上げるのは、Ruby on Rails 製の家計簿ソフト Expense です。

基本情報

ソースコードの取得と確認

$ git clone https://github.com/tristandunn/expense.git
$ cd expense/
$ ls
app  config  config.ru  db  doc  features  Gemfile  Gemfile.lock  lib  LICENSE  log  public  Rakefile  README.markdown  script  spec

Gemfile がありますので、まずはその中身を確認しましょう。

$ cat Gemfile
source "https://rubygems.org"

gem "bcrypt-ruby",  "3.1.2"
gem "dynamic_form", "1.1.4"
gem "rails",        "4.0.3"
gem "sqlite3",      "1.3.8"

group :assets do
  gem "sass-rails",     "4.0.1"
  gem "yui-compressor", "0.12.0"
end

group :development, :test do
  gem "rspec-rails", "2.14.1"
end

group :test do
  gem "cucumber-rails",     "1.4.0", require: false
  gem "database_cleaner",   "1.2.0"
  gem "factory_girl_rails", "4.4.0"
  gem "timecop",            "0.7.1"
end

rails 、それから、 sqlite3 も使われるようです。
Rails アプリで DB が使われる場合は、 config/database.yml に設定内容が記載されるので、そのファイルも見ておきましょう。

$ cat config/database.yml 
# SQLite version 3.x
#   gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000

development , test , production のそれぞれの環境において、 adapter: sqlite3 が使われており、その DB のパスが設定されています。

このデフォルトの sqlite3 を使うと簡便にデプロイできそうですが、ただし、その場合は DB がファイルとして作られるので、アプリを停止したり再起動すると、DB のファイルがリセットされてしまう、という欠点があります。
そこで、本日は、 sqlite3 でまずデプロイして確認した後、built-in の MySQL をバインドしてデプロイし、登録データが永続的になることを確認してみることにします。

SQLite を使ったデプロイ

https://github.com/tristandunn/expense のインストール手順では、ソースコードを取得した後、 bundle install 、そして、 rake db:create db:migrate を実行することとなっています。
しかし今回は、ローカルではなく、Cloud Foundry へデプロイしようとしているので、 bundle install の実行は、ruby-buildpack の適用時に行われるため不要になります。

次に、 rake db:create db:migrate ですが、これはDBを作成してデータを投入するコマンド。今回はローカルではなく、デプロイ先の Cloud Foundry 上にて DB を作成するため、デプロイ時にコマンド実行する必要がありますね。
第14回 の時にも、Ruby のアプリのデプロイ時にコマンド指定してましたので、 そちら も参考にしてください。

アプリの push

今回のアプリの push は、 -c のオプションで、 bundle exec rake db:create db:migrate のコマンド実行を指定します。なお今回は、 Gemfile で指定された環境でコマンドを実行するため、コマンドの前に、 bundle exec を付けます。
さらに、もう一つ。Cloud Foundry で利用する場合はポートを動的に設定する必要があります。そこで、環境変数 PORT を使い、動的ポートに対応してRails サーバが起動するようにします。
環境はデフォルトでは production としてデプロイされます。

$ cf push expense -c 'bundle exec rake db:create db:migrate ; bundle exec rackup --port $PORT'
:

0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 starting
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 down
0 of 1 instances running, 1 failing
FAILED
Start unsuccessful

TIP: use 'cf logs expense --recent' for more information

失敗したのでログを見ると、以下のように Invalid DATABASE_URL と出ています。

2015-06-30T18:00:47.46+0900 [App/0]      ERR Invalid DATABASE_URL

ローカルでは、上述した database.yml に記載された DB ファイルへのパス情報にて DB 接続されるのですが、Cloud Foundry にデプロイしようとしたら、環境変数 DATABASE_URL に間違った値が入ってしまい、DB へ接続できなくなったようなので、 DATABASE_URL に明に設定します。
sqlite3 の DB の URL のフォーマットは、 sqlite3:////full/path/to/file.sqlite3 のようになります。
今回使う DB の情報は、上述した config/database.yml によると、 production 環境の場合は、 db/production.sqlite3
それを Cloud Foundry にデプロイすると、フルパスは、 /home/vcap/app/db/production.sqlite3 となります。
従って、設定したい URL は、 sqlite3:////home/vcap/app/db/production.sqlite3
これを次のように Cloud Foundry での環境変数 DATABASE_URL に設定します。

$ cf set-env expense DATABASE_URL sqlite3:////home/vcap/app/db/production.sqlite3

確認は、以下のように env コマンドで。

$ cf env expense
:

User-Provided:
DATABASE_URL: sqlite3:////home/vcap/app/db/production.sqlite3

:

さて、 DATABASE_URL の設定ができたところで、アプリを restage します。

$ cf restage expense
Restaging app expense in org ueno / space test1 as ueno...

:

App expense was started using this command `bundle exec rake db:create db:migrate ; bundle exec rackup --port $PORT`

Showing health and status for app expense in org ueno / space test1 as ueno...
OK

requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: expense.10.244.0.34.xip.io
last uploaded: Tue Jun 30 08:57:38 +0000 2015
stack: lucid64

     state     since                    cpu    memory           disk      details   
#0   running   2015-06-30 06:11:09 PM   0.0%   110.2M of 256M   0 of 1G      

デプロイ成功し、アプリが起動しました。

動作確認

http://expense.10.244.0.34.xip.io にアクセスすると、初期画面が現れますので、まずは “sign up” のリンクを辿ります。

任意のメールアドレスとパスワードを入力してサインアップ。

メイン画面です。
“Item” に入力してみましょう。

入力した Item が順次表示されていきます。

“Mac” を “Search” すると、ちゃんと検索されて表示されました。

“Settings” のリンクを辿ると、タイムゾーン設定ができる画面に遷移します。

ここでアプリから一旦サインアウトし、アプリを再起動してみましょう。

$ cf restart expense

再起動後のアプリに先ほどと同じアカウントでサインインしようとしても、入れませんね。
入力データも登録アカウントデータも全て消えてしまっています。

その問題の対策のため、今度は Cloud Foundry に built-in された MySQL サービスをバインドして試してみます。

MySQL を使ったデプロイ

デフォルトの SQLite を使ったデプロイが成功したので、今度は予定通り、MySQL を使ったデプロイを試してみます。
今回利用する MySQL サービスは、 cf-mysql-release を利用して構築しています。

GemfileGemfile.lock の書き換え

Gemfilesqlite3 の登録を削除し、代わりに mysql2 を登録します。

$ vi Gemfile

Gemfile.lock は、 bundle install 実行によって作成されますが、今回は sqlite3 から mysql2 への変更のみなので、直接ファイルを編集します。
gem search mysql2 で確認し、 mysql2 の gem のバージョン 0.3.18 を指定します。

$ vi Gemfile.lock 

GemfileGemfile.lock の編集前後の差分を以下、 git diff の実行結果で示しておきます。

$ git diff
diff --git a/Gemfile b/Gemfile
index 855ae21..8fc993e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,7 +3,7 @@ source "https://rubygems.org"
 gem "bcrypt-ruby",  "3.1.2"
 gem "dynamic_form", "1.1.4"
 gem "rails",        "4.0.3"
-gem "sqlite3",      "1.3.8"
+gem "mysql2"
 
 group :assets do
   gem "sass-rails",     "4.0.1"
diff --git a/Gemfile.lock b/Gemfile.lock
index 33cd2f3..08cf571 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -113,7 +113,7 @@ GEM
       actionpack (>= 3.0)
       activesupport (>= 3.0)
       sprockets (~> 2.8)
-    sqlite3 (1.3.8)
+    mysql2 (0.3.18)
     thor (0.18.1)
     thread_safe (0.1.3)
       atomic
@@ -139,6 +139,6 @@ DEPENDENCIES
   rails (= 4.0.3)
   rspec-rails (= 2.14.1)
   sass-rails (= 4.0.1)
-  sqlite3 (= 1.3.8)
+  mysql2
   timecop (= 0.7.1)
   yui-compressor (= 0.12.0)

アプリの push

アプリを push します。
SQLite の時と異なり、MySQL と後でバインドするので、 --no-start で push します。

$ cf push expense2 -c 'bundle exec rake db:create db:migrate ; bundle exec rackup --port $PORT' --no-start

MySQL サービス作成とバインド

まず利用できるサービスを確認します。

$ cf marketplace
Getting services from marketplace in org ueno / space test1 as ueno...
OK

service      plans                     description   
PostgreSQL   Basic PostgreSQL Plan*    PostgreSQL on shared instance.   
p-mysql      100mb-dev, 1gb-dev        A MySQL service for application development and testing   
p-redis      shared-vm, dedicated-vm   Redis service to provide a key-value store   

* These service plans have an associated cost. Creating a service instance will incur this cost.

TIP:  Use 'cf marketplace -s SERVICE' to view descriptions of individual plans of a given service.

p-mysql を使って今回の MySQL サービス expense-mysql を作成します。

$ cf create-service p-mysql 100mb-dev expense-mysql

作成した MySQL サービス expense-mysql をアプリとバインドします。

$ cf bind-service expense2 expense-mysql

アプリの起動

ではアプリを起動させます。

$ cf start expense2
Starting app expense2 in org ueno / space test1 as ueno...

:

App expense2 was started using this command `bundle exec rake db:create db:migrate ; bundle exec rackup --port $PORT`

Showing health and status for app expense2 in org ueno / space test1 as ueno...
OK

requested state: started
instances: 1/1
usage: 256M x 1 instances
urls: expense2.10.244.0.34.xip.io
last uploaded: Tue Jun 30 11:04:11 +0000 2015
stack: lucid64

     state     since                    cpu    memory           disk      details   
#0   running   2015-06-30 08:05:47 PM   0.0%   113.6M of 256M   0 of 1G      

無事起動しました。

MySQL を使ったバージョンの動作確認

では URL にアクセスし、初期画面からサインアップ、Item 登録し、一旦サインアウトします。
そしてアプリを再起動。

$ cf restart expense2

起動後、先ほどのアカウントでサインインしてみると、今度は入れます。
そして、先ほど入力した Item もちゃんと残っています。
バインドした MySQL DB を使うことで、データの永続化が実現できることが確認できます。

まとめ

今回はアプリ自体はデモレベルのシンプルなものでしたが、Rails アプリのデプロイ、そして、SQLite と MySQL のタイプの異なる DB を使うための方法を学びました。

SQLite を使ったデプロイでは、 DATABASE_URL の環境変数設定にて、正常に DB ファイルへ接続できるようになりました。
MySQL を使ったデプロイでは、サービスをバインドするだけで MySQL DB へ接続できるようになりました。
元々アプリには、 database.yml があり、SQLite を使う設定がされていましたが、Cloud Foundry で DB サービスをバインドすると、その設定が上書きされるようになっています。そして、Cloud Foundry は、VCAP_SERVICES 環境変数から、バインドされた MySQL DB への接続情報を読み取り、自動で MySQL DB 接続されるようになりました。

今回のアプリは「たった一つの入力しかない家計簿」でしたが、実用として使うには、まず、登録アイテムの修正機能が欲しいですね。そして、集計機能。月別集計以外にも、アイテム種別等の集計、それにグラフ表示等があると便利そうです。以前の百日行記事にも、請求書アプリがありましたが(第16回)、これらのような実用的なアプリが Cloud Foundry 上で簡便にデプロイして使えるようになると便利でいいですね。

今回使用したソフトウェア