Capistrano ver.3 tasks for puma

Capistrano を version 3 にしようとしてつまづいた

ずいぶん変わっているので、各所でつまづきの声が聞こえています。

しかし Rails から切り離されてることでシンプルになって汎用性が上がり、withintest など便利なメソッドも使えて良い感じ!

ver.2 までを使っていたら気をつけること

  • multistage と color がデフォルトに!
  • Rails oriented なツールではない
  • ver 2 を流用しようと思わずに一から書き直したほうが早い
  • SSHKit をちゃんと読む https://github.com/capistrano/sshkit
  • 公式のドキュメントもちゃんと読む。他には Update Guide とかも読む
  • scm git しか使えない
  • deploy_via :copy は使えない
  • set したら fetch()

Rails 関連で気をつけること

  • gem 'capistrano-rails', :require => false in Gemfile と require 'capistrano/rails' in Capfile しよう
  • 勝手に log とか tmp とか作ったりしないので、set :linked_dirs, ...

puma タスクを Capistrano 3 対応する

puma 本家puma/capistrano は 2014/01時点で Capistrano 3 に対応していないので、悩むより自分で書いたほうがすぐできる感じ。

設定ファイルや start/stop/restart はもともとの puma/capistrano から、全体の構成はこちらのunicorn向けtaskを真似させてもらっています。

namespace :puma do
  task :env do
    set :puma_state,  "#{shared_path}/tmp/sockets/puma.state"
  end

  def start_puma(bind_addr)
    execute :bundle, :exec, :puma, start_options(bind_addr)
  end

  def stop_puma
    # Now use 'halt' to stop puma, because 'stop' remains puma process living
    # and remove pumactl.sock by halves. It seems a puma's bug.
    # https://github.com/puma/puma/issues/306
    execute :bundle, :exec, :pumactl, "-S #{puma_state_path} halt"
  end

  def restart_puma(phased_restart = nil)
    restart = phased_restart ? 'phased-restart' : 'restart'
    execute :bundle, :exec, :pumactl, "-S #{puma_state_path} #{restart}"
  end

  def start_options(bind_addr = '127.0.0.1')
    if config_file
      "-q -d -e #{app_env} -C #{config_file}"
    else
      "-q -d -e #{app_env} -b 'tcp://#{bind_addr}:3000' -S #{fetch(:puma_state)} --control 'tcp://localhost:3001'"
    end
  end

  def config_file
    # @ToDo
    nil
  end

  def puma_state_path
    (config_file ? configuration.options[:state] : nil) || fetch(:puma_state)
  end

  def configuration
    require 'puma/configuration'

    config = Puma::Configration.new(config_file: config_file)
    config.load
    config
  end

  desc "Start puma"
  task :start => :env do
    on roles(:app) do |host|
      within current_path do
        start_puma(host)
      end
    end
  end

  desc "Stop puma"
  task :stop  => :env do
    on roles(:app) do
      within current_path do
        stop_puma
      end
    end
  end

  desc "Restart puma"
  task :restart => :env do
    on roles(:app) do |host|
      within current_path do
        if test("[ -f #{puma_state_path} ]")
          restart_puma
        else
          start_puma(host)
        end
      end
    end
  end

  desc "Phased restart puma"
  task :phased_restart => :env do
    on roles(:app) do |host|
      within current_path do
        if test("[ -f #{puma_state_path} ]")
          restart_puma('phased-restart')
        else
          start_puma(host)
        end
      end
    end
  end

  desc "Force clean up puma"
  task :cleanup => :env do
    on roles(:app) do
      if test("[ -f #{puma_state_path} ]")
        execute <<-EOC
kill -9 `grep pid #{puma_state_path} | awk '{print $2;}'`
rm -f #{puma_state_path}
EOC
      else
        execute <<-EOC
kill -9 `ps ax | grep ruby | grep puma | awk '{print $1}'`
rm -f #{puma_state_path}
EOC
      end
    end
  end

  after 'deploy:publishing', 'puma:restart'
end

上記ではアプリ本体、Control Server ともに TCP で立ち上げているが、UNIX ソケット unix://... でもおっけー。

Config ファイルのロードはテストしていないので省いたけれど、puma 本家のものを流用できるはず…