Capistrano3のSSHKitでエラーハンドリングする方法

今取り組んでいる仕事でCapistrano3を使ったデプロイを実装していました。

SSHコマンド実行先ホストでエラーが発生した場合の対処方法に
相当悩まされ、なんとか解決できたので、せっかくなので共有します。

サマリ

sshkitでコマンド実行先のエラーをハンドリングしたい場合、
raise_on_non_zero_exit:false を設定するとともに、
captureメソッドで戻り値を取得し、実行元サーバで判定するとよい。

実現したいこと

  1. Jenkinsでアプリをビルドしたい
  2. JenkinsからCapistranoでS3に最新資材を配置したい
  3. JenkinsからCapistranoでAPサーバ上の資材取得スクリプトを実行したい
  4. APサーバ上の資材取得スクリプトで、S3から最新資材を取得したい

今回悩んだのは、3,4の資材取得スクリプト実行で失敗した場合、
S3上の最新資材をロールバックしたいという点でした。

困ったこと

sshkitは、(特に考慮しない場合、)
コマンドの戻り値が0以外だとその時点で異常終了してしまいます。
(それがたとえdiffの結果であっても、、!)

調査を進めたところ、 raise_on_non_zero_exit パラメータで、
エラーを無視することができそうだということがわかりました。

ということはsshkitのデフォルト機能でエラーハンドリングできるのかも?
と調査を進めたところ、下記のような実装が見つかりました。

    def exit_status=(new_exit_status)
      @finished_at = Time.now
      @exit_status = new_exit_status

      if options[:raise_on_non_zero_exit] && exit_status > 0
        message = ""
        message += "#{command} exit status: " + exit_status.to_s + "\n"
        message += "#{command} stdout: " + (full_stdout.strip.empty? ? "Nothing written" : full_stdout.strip) + "\n"
        message += "#{command} stderr: " + (full_stderr.strip.empty? ? 'Nothing written' : full_stderr.strip) + "\n"
        raise Failed, message
      end
    end

sshkit/command.rb at master · capistrano/sshkit · GitHub

なんで !raise_on_non_zero_exit && exit_status > 0 をフックさせてくれないの、、

ということで、デフォルトにエラーハンドリングの機能はないと判断しました。

※sshkitではなくcapistrano3にはデフォルトでrollbackタスクがありますが、
 今回はカスタムで作りこんでいたため利用しませんでした。

結論

raise_on_non_zero_exit でエラーを無視でき、
デフォルトにハンドリング機能はないことがわかったので、
愚直に戻り値を元にハンドリングすることにしました。

具体的には下記のような実装です。

    ret = capture :bash /xxxx/xxxx/apply_package.sh > /dev/null;echo $?", raise_on_non_zero_exit: false
    # 資材取得スクリプトの戻り値が0以外の場合にS3上の資産をロールバック
    invoke "aplfront:rollback_s3" if !ret.to_i.zero?

SSHコマンド実行先のサーバで、 /xxxx/xxxx/apply_package.sh を実行し、
戻り値をcaptureメソッドによって ret 変数に格納しています。
その後、 ret 変数の値が0でない場合、 aplfront:rollback_s3 を実行しています。

また、rollback処理が複数起動しないよう、該当のタスクは同時実行数が1となるように設定しています。

ちなみに

Capistrano3以前はon_rollbackを定義できたご様子。なくなっちゃったの?

Capistranoでのエラー処理(ロールバック処理) - \ay diary