akishin999の日記

調べた事などを書いて行きます。

Capistrano の db ロール

最近 Capistrano3 を触っていてハマったので忘れないようにメモ。

昨日身に染みて分かったんですが、Capistrano での 「role: :db」って「RDBMS が動作しているサーバ」ではなく、あくまで「rake db:migrate を実行するサーバ」に対して指定するためのロールだったんですね。

Capistrano3 を使っていて、最初以下のような server 指定を書いていました。

server '192.0.2.1', user: 'vagrant', roles: %w{web app}
server '192.0.2.2', user: 'vagrant', roles: %w{web app}
server '192.0.2.3', user: 'vagrant', roles: :db, no_release: true # DBサーバを指定

「role: :db」は DBサーバ用のロール、と思い込んでいたので、「RDBMS が動作しているサーバ」を指定。
また、DBサーバにはアプリは普通置かないだろ、という事で「no_release: true」も指定しています。

この状態で実行してみると何度やってもエラーが発生してしまいデプロイが正常に行えません。
試しに roles: :db の server 定義をコメントアウトしてみるとデプロイは正常に終了しましたが、当然ですが DB へのマイグレーションは実行されていませんでした。

いろいろ試行錯誤してみてもなかなか動かず困っていたのですが、調べてみると Stack Overflow に以下の投稿を発見しました。

Obviously, the name of role :db is misleading. As you pointed out, Capistrano defines it (with :primary => true) as a host that we execute rake db:migrate on, but database servers are not always running on such hosts. We can alter the schema of remote database server through a Rails app. The correct role name for this kind of host is not :db.

Inferring from the comments on lib/capistrano/configuration/roles.rb, the original meaning of the role :db is a host on which database servers are running. We are expected to login to the :db hosts and do some tasks.

The designers of Capistrano should have defined :migration role or something else for the deploy:migrate task. But the association between the :db role with this task was defined six years ago with 9a6d2fb and has not been changed since then.

Generally speaking, the users of Capistrano can define roles and associate them with tasks freely. The deploy:migrate task is provided just as a recipe for Rails developers. Unfortunately, this recipe includes a misconception about how we do the database migration and is used widely for a long time.

http://stackoverflow.com/questions/9781767/capistrano-db-role-whats-it-for/13637551#13637551

ソースコードを読んでみると、確かに migrate タスクは role: :db のサーバ上で release_path に移動して rake db:migrate を実行するように実装されているようです。

https://github.com/capistrano/rails/blob/5f112183dad808078a56c8558046d84d182eddf6/lib/capistrano/tasks/migrations.rake#L5-L14

であれば本当の DB サーバの IPアドレスは database.yml にだけ書いてあれば OK、という事になりますね。

というわけで、以下のように修正したところ、今度はあっさり動作しました。

server '192.0.2.1', user: 'vagrant', roles: %w{web app db}
server '192.0.2.2', user: 'vagrant', roles: %w{web app}

分かってしまえばそりゃそうだよな、という感じだったんですが、いろいろ検索してみても「DBサーバには role: :db を指定します」というような事を書いてある記事が結構あったりして、なかなか気付く事が出来ませんでした。

エラーメッセージも DB サーバ上で current に移動しようとしてディレクトリが無い!みたいな感じだったので、余計に分かり辛かったですね。
言い訳ですけど。

Stack Overflow の投稿では質問者が capify . とか書いているので、どうも Capistrano2 の頃からこういう挙動だったようです。
ハマった記憶が無かったので、念の為過去に書いた deploy.rb を確かめてみると、ローカルに HAProxy を立てて 127.0.0.1 で DBサーバへアクセスするようにしており、確かに role: :db にアプリを置いていないサーバの IPアドレスを指定しているものはありませんでした。
だから気付かなかったのか・・・。

それにしても検索してもあまり困ってる人がいなかったんですが、この辺りって常識だったりするんでしょうか?
挙動からすると role 名が db というのが混乱の元のような気がしてならないんですが・・・。

同僚に愚痴ったら「でも僕はハマった事ないですね」とはっきり言われてなんかショックでした。
精進したいと思います。

参考

Capistranoでmigration | kaeruspoon - おおいしつかさ
http://www.kaeruspoon.net/articles/350