More than 1 year has passed since last update.

Mina - 公式ドキュメント

Capistrano is dead - use Mina - weluse Blog にて紹介されている、デプロイツール「Mina」の導入方法。

Mina は Capistrano のような、プロジェクトをリモートに配置する、いわばデプロイツールの一つだ。
ざっと調べてみた感じでは、

  • Rake そのものを拡張している (Capistrano のように、構文を揃えているだけではない)
  • リモートで流すスクリプトを予め用意しておくことで、SSH接続を1回に抑え、オーバーヘッドを解消
  • リモートで流されるコマンドをシェルスクリプトとして表示・確認することができる
  • Capistrano に比べてデプロイスピードが速く、マシンにも優しい
  • デフォルトで Capistrano の deploy_via :remote_cache のような挙動 (リポジトリの clone は最初だけ、次回以降は差分で更新)
  • まだ新しいプロジェクトなので、Capistrano に比べると拡張面で非力?
  • Capistrano より名前が可愛い (New!)

といった印象だ。
公式ドキュメントは情報量が少ないながらも綺麗にまとまっている。

物は試しということで、実際に使ってみた。

導入

$ gem install mina

例の如く gem でインストール。

セットアップ (ローカル)

$ mina init

カレントディレクトリに config/deploy.rb が作成される。
Capistrano の capify と同じ。

設定ファイルの書き方

設定ファイルを編集する前に DSL を少しだけ頭に入れておく。
基本的には Rake の記法に則る。
(実際のところ、mina コマンドは rake のエイリアスであり、lib/mina/helpers.rb による追加の DSL とデフォルトのタスクを読み込むだけの様子)


# set: キーに対して値を設定する (Mina の設定用)
set :user, 'username'
p user # => "username" (set したデータはメソッドとして呼び出せる)

# desc: task の前に書いてタスクに説明を付ける
desc "タスクの説明"
# task: タスクを定義する
task :mytask => [:subtask] do # subtask を前提タスクとして mytask を定義

  # queue: リモートで "touch hoge.txt" を実行するコマンドを実行キューに追加
  queue  'touch hoge.txt'
  # queue!: --verbose モードでコマンドが出力される以外は queue と同じ
  queue! 'rm -f hoge.txt' 

  # invoke: タスク another を実行
  invoke :another

  # commands: キューに入っているコマンドを配列で返す
  puts commands
  # >    touch hoge.txt
  # >    rm -f hoge.txt

  # run!: キューに入っているコマンドを全て実行する
  #       キューの中身が消えたりはしない
  run!

  # run: リモートでコマンドを即座に実行する
  run "bundle install"

  # to: 特定の処理に名前をつける。ブロック内部ではキューが独立する。
  #     同名で to 処理を二度書いた場合、ブロックが評価されるのは最初の一回だけである (上書きされない、但し実行はされる)
  to :barks do
    queue %[echo 'nyaoon']
    p commands(:queue) # => ["echo 'nyaoon'"] 
    run! # => nyaoon
  end

  # isolate: 明示的にキューを独立させる。
  #          (複数ドメインにデプロイするとき等に使う?)
  isolate do
    domains = ["a.example.com", "b.example.com"]
    domains.each do |domain|
      set :domain, domain
      Rake::Task[:deploy].reenable # 2回目以降でも実行する
      invoke :deploy
      run!
    end
  end

  # die: エラーを吐いて落ちる
  die "Bad Luck!"
end

設定

お好みのエディタで config/deploy.rb を編集。
一般的な Rails アプリケーションを想定した場合、こんな感じに。

config/deploy.rb
# 適宜必要な補助ライブラリを読み込む。
require 'mina/rails'
require 'mina/git'
require 'mina/bundler'
# require 'mina/rbenv'  # (Rbenv を使用する場合)
# require 'mina/rvm'    # (RVM を使用する場合)

# デプロイユーザ、SSH接続先、デプロイ先のパス、Git のリポジトリ、ブランチ、RAILS_ENV
set :user,       'www'
set :domain,     '10.0.1.1'
set :deploy_to,  '/Users/www/rails/waooon'
set :repository, 'ssh://git@10.0.1.2/~/repos/waooon.git'
set :branch,     'develop'
set :rails_env,  ENV['RAILS_ENV'] || 'production'

# サーバ固有のファイルを設定する。
# #{deploy_to}/shared にファイルを置いておけば、deploy:link_shared_paths タスクによって本来の位置へとシンボリックリンクされる。
# config/database.yml を設定したい場合はサーバサイドで shared/config/database.yml を書いておけば OK
set :shared_paths, ['config/database.yml', 'log']

# 環境設定用タスク。
# パスを張ったりプロキシの設定をするならここ?
# 試しに .bashrc を読んであげたら上手く動いた。無いと bundle にパスが通ってなくてエラー
task :environment do
  queue %[source ~/.bashrc]

  # Rbenv を使用する場合
  # invoke :'rbenv:load'

  # RVM を使用する場合
  # invoke :'rvm:use[ruby-1.9.3-p125@default]'
end

# リモートセットアップ時の挙動を設定する
# (デフォルト)
task :setup => :environment do
  queue! %[mkdir -p "#{deploy_to}/shared/log"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/log"]

  queue! %[mkdir -p "#{deploy_to}/shared/config"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/config"]

  queue! %[touch "#{deploy_to}/shared/config/database.yml"]
end

# デプロイ時の挙動を設定する
desc "Deploys the current version to the server."
task :deploy => :environment do
  deploy do
    # 一般的な Rails セットアップ用のタスク
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'

    to :launch do # 再起動用ファイルを作成
      queue 'mkdir -p tmp'
      queue 'touch tmp/restart.txt'
    end
  end
end

セットアップ (リモート)

リモートのセットアップを行う。その前に、リモートにデプロイ先ディレクトリが無いなら作成しておく。
(11/8 13:00 追記) ディレクトリがない場合は自動で作成されるようだ。親ディレクトリに書き込み権限がない場合などは、当然自分で作ることになる。

$ ssh www@10.0.1.1 "mkdir -p /Users/www/rails/waooon"

リモートのセットアップコマンドを実行。

$ mina setup

セットアップ後のファイル構造は以下のようになった。

├── releases
└── shared
    ├── config
    │   └── database.yml
    └── log

このタイミングでリモートの database.yml を適宜編集。
他に shared_paths 以下の設定ファイルがあれば編集しておく。

デプロイ (確認)

--simulate オプションを使ってみる。所謂 Dry-Run?

$ mina deploy --simulate

タスク実行時にリモートで行われる予定のスクリプトがずらずらと羅列されていく。
リモートで実際にどんな処理が行われるかというのは気になるところだと思うので、これは確認しておこう。
といっても、僕は主にデバッグ用途に使っていた。

デプロイ

$ mina deploy --verbose --trace

--verbose, --trace は詳細表示とデバッグ用のオプション。
--verbose オプションを設定したとき、deploy.rb で queue! を使っていれば、実行前にコマンドが表示される。
(他のオプションはこちら)

下記のような結果が表示されれば成功。

-----> Creating a temporary build path
       $ touch "deploy.lock"
       $ mkdir -p "$build_path"
       $ cd "$build_path"

-----> Fetching new git commits
       $ (cd "/Users/www/rails/waooon/scm" && git fetch "ssh://git@10.0.1.2/~/repos/waooon.git" "master:master" --force)

-----> Using git branch 'master'
       $ git clone "/Users/www/rails/waooon/scm" . --recursive --branch "master"
       Cloning into '.'...
       done.

-----> Using this git commit

       $ git --no-pager log --format="%aN (%h):%n> %s" -n 1
       AOKI Yuuto (xxxxxx):
       > initial commit
       $ rm -rf .git


-----> Symlinking shared paths
       $ mkdir -p "./config"
       $ mkdir -p "."
       $ rm -rf "./config/database.yml"
       $ ln -s "/Users/www/rails/waooon/shared/config/database.yml" "./config/database.yml"
       $ rm -rf "./log"
       $ ln -s "/Users/www/rails/waooon/shared/log" "./log"

-----> Installing gem dependencies using Bundler
       $ mkdir -p "/Users/www/rails/waooon/shared/bundle"
       $ mkdir -p "./vendor"
       $ ln -s "/Users/www/rails/waooon/shared/bundle" "./vendor/bundle"
       $ bundle install --without development:test --path "./vendor/bundle" --binstubs bin/ --deployment
       (省略)
       Your bundle is complete! It was installed into ./vendor/bundle

-----> Migrating database
       $ RAILS_ENV="production" bundle exec rake db:migrate

-----> Precompiling asset files
       $ RAILS_ENV="production" bundle exec rake assets:precompile RAILS_GROUPS=assets

-----> Build finished

-----> Moving build to releases/1
       $ mv "$build_path" "$release_path"

-----> Updating the current symlink
       $ ln -nfs "$release_path" "current"

-----> Launching
       $ cd "$release_path"

-----> Done. Deployed v1
       Elapsed time: 16.00 seconds

注意点 (今回のデプロイにあたって)

  • database.yml が正しく編集されていなかったりすると普通に mina deploy 内の db:migrate で落ちる (当たり前だけど)
  • デフォルトのままだと #{Rails.root}/tmp が作成されず 'touch tmp/restart.txt' が失敗する可能性がある
  • environment タスクで .bashrc を読んであげないと bundle が見つからずエラーになった。 (command not found: bundle)
  • isolate とループの組み合わせで繰り返し invoke する際、reenable でタスクを有効化する必要がある。呼び出したタスクで更に invoke が行われていた場合それも reenable しなければいけない為、現実的ではない?

覚えてたら便利そうなこと

$ mina tasks

上記コマンドで使い方を確認。

Basic usage:
  mina help    # Show help.
  mina init    # Creates a sample config file.
  mina tasks   # Show all tasks.

Server tasks:
  mina console            # Starts an interactive console.
  mina deploy             # Deploys the current version to the server.
  mina rails[arguments]   # Execute a Rails command in the current deploy.
  mina rake[arguments]    # Execute a Rake command in the current deploy.
  mina run[command]       # Runs a command in the server.
  mina setup              # Sets up a site.

More tasks:
  mina bundle:install                  # Install gem dependencies using Bundler.
  mina deploy:cleanup                  # Clean up old releases.
  mina deploy:force_unlock             # Forces a deploy unlock.
  mina deploy:link_shared_paths        # Links paths set in :shared_paths.
  mina git:clone                       # Clones the Git repository to the release path.
  mina rails:assets_precompile         # Precompiles assets (skips if nothing has changed since the last release).
  mina rails:assets_precompile:force   # Precompiles assets.
  mina rails:db_migrate                # Migrates the Rails database (skips if nothing has changed since the last release).
  mina rails:db_migrate:force          # Migrates the Rails database.

deploy.rb 内で invoke していたタスクはそれぞれ切り出して個別に使える。
また、mina run なんかはサーバのログをちょっと確認したりするのに良いかもしれない。

Capistrano に取って代わるプロジェクトと成りうるか?

使い方次第だとは思うけれど、Capistrano と Mina でタスクに互換性がないのが難点。
僕は Cap 自体あまり突っ込んだ使い方をしていなかったので、Mina に移行しても不便はないかなと感じた。
冒頭で紹介した記事では「Capistrano は死んだ」なんて煽り気味のタイトルが踊っている訳だが、まだまだエンジニア次第ではないだろうか。

参考になったページ