Capistrano 3 で Java Webアプリケーションをデプロイする

タイトルの通りCapistranoJavaのWebアプリケーション(warファイルとかJettyを組み込んだjarファイルとか)をデプロイしてるのですが、みなさんどうやってるんでしょうか!?

会社ではJava以外のプロダクトも結構あって、それらをCapistranoでデプロイしているからJavaのプロダクトもCapistranoでデプロイしているのですが、Gradleとかでやるのが多いのかなぁー。。。

最近Capistranoのバージョンを2から3へ上げたときに、その辺の事情が気になったのでブログ記事に書くことにしました。

Capistranoのバージョンは、3.2.1です。

インストールやタスクのフローなどは以下のページが参考になると思います。

ビルド・デプロイの流れ

  1. Gitリポジトリにpush
  2. Jenkinsでユニットテスト&ビルド
  3. 社内のMavenリポジトリにpublish

という流れは自動化されてます。

この後、Mavenリポジトリにpublishされたアーティファクトを自動でステージング環境へデプロイしたり、本番環境へデプロイを楽にするためにもデプロイを自動化したくなります。 これらのデプロイをCapistranoを使って自動化しています。

デプロイタスク

Capistranoでデプロイするとき、デフォルトだとGitからソースコードを取得するのですが、タスクをオーバーライドすることで社内のMavenリポジトリからアーティファクトwgetで取得するように変更してます。

Capistrano3では以下のページにある通り、複数のタスクが順番に呼ばれます。

Capistrano3のデプロイフレームワークの使い方 - Qiita

呼び出されているタスクを見てみると、git関連のタスクがdeploy:checkタスクとdeploy:updatingタスクの中で呼び出されています。

まずは、deploy:checkタスクをオーバーライドしてgit:checkを呼ばないようにします。(参考:Overriding Capistrano tasks
元々の処理からgit:checkの呼び出しだけ削除します。

# deply.rb
namespace :deploy do
  Rake::Task["deploy:check"].clear_actions
  task :check do
    #invoke "#{scm}:check" gitを使わないので不要
    invoke 'deploy:check:directories'
    invoke 'deploy:check:linked_dirs'
    invoke 'deploy:check:make_linked_dirs'
    invoke 'deploy:check:linked_files'
  end
end

次に、deploy:updatingをオーバーライドします。
が、その前にアーティファクトのバージョン番号を入力するためのタスクを作ります。そして、beforeを使って作成したタスクをdeploy:updatingタスクの実行前に呼ばれるようにします。
askを使うと対話的にパラメータを渡すことができます。

# deply.rb
namespace :deploy do
  task :version do
    ask(:version, "nil")
    on roles(:all) do |h|
      if fetch(:version).nil? || fetch(:version) == "nil"
        error "version が指定されていません"
        exit 99
      else
        execute "echo \"OK! version: #{fetch(:version)}\""
      end
    end
  end

  before :updating, :version
end

これでアーティファクトのバージョン番号を対話的に入力できるようになりました。環境変数で渡しても良いですね。

では、deploy:updatingをオーバーライドして、Mavenリポジトリからアーティファクトをダウンロードするようにします。
また、deploy:updatingタスクから呼び出しているdeploy:set_current_revisionタスクでRIVISIONというファイルを作っているのですが、このタスクの中でもgit関連のタスクを呼び出しているのでそこも書き換えます。

# deply.rb
set :application, 'myapp'
set :group_dir, 'com/exmaple'

set :m2repo, 'http://m2repo.example.com'

namespace :deploy do
  Rake::Task["deploy:updating"].clear_actions
  task :updating => :new_release_path do
    #invoke "#{scm}:create_release" gitを使わないので不要
    # 代わりに、wgetでMavenリポジトリからjar/warファイルをダウンロードするようにする
    on release_roles :all do
      execute :mkdir, '-p', release_path
      execute :wget, "#{fetch(:m2repo)}/#{fetch(:group)}/#{fetch(:application)}/#{fetch(:version)}/#{fetch(:application)}-#{fetch(:version)}.war -P #{release_path}"
    end

    #invoke "deploy:set_current_revision" gitを使わないので不要
    # 代わりに、入力させたバージョンをREVISIONファイルに書き込む
    on release_roles :all do
      within release_path do
        execute :echo, "\"#{fetch(:version)}\" >> REVISION"
      end
    end

    invoke 'deploy:symlink:shared'
  end
end

あとは再起動タスクを作ります。afterを使ってpublishingの後に実行されるようにします。

# deply.rb
namespace :deploy do
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do |host|
      info("restart on #{host}")
      # LBからサーバーを切り離して、アプリケーションサーバーを再起動したりする処理をここに書く
    end
  end

  after :publishing, :restart
end

これで、Capistranoを使って自動デプロイできるようになりました。
ステージング環境へデプロイしたい場合はcap staging deployですね!

おわりに

今のところ、このデプロイ方法で困ってないのですが、Java アプリケーションのデプロイについてのベストプラクティスが気になってるところです・・・。

最後にdeploy.rbの全体像を載せておきます。

# deply.rb
set :application, 'myapp'
set :group_dir, 'com/exmaple'

set :m2repo, 'http://m2repo.example.com'

namespace :deploy do
  Rake::Task["deploy:check"].clear_actions
  task :check do
    #invoke "#{scm}:check" gitを使わないので不要になる
    invoke 'deploy:check:directories'
    invoke 'deploy:check:linked_dirs'
    invoke 'deploy:check:make_linked_dirs'
    invoke 'deploy:check:linked_files'
  end


  task :version do
    ask(:version, "nil")
    on roles(:all) do |h|
      if fetch(:version).nil? || fetch(:version) == "nil"
        error "version が指定されていません"
        exit 99
      else
        execute "echo \"OK! version: #{fetch(:version)}\""
      end
    end
  end

  before :updating, :version


  Rake::Task["deploy:updating"].clear_actions
  task :updating => :new_release_path do
    #invoke "#{scm}:create_release" gitを使わないので不要になる
    # 代わりに、wgetでMavenリポジトリからjar/warファイルをダウンロードするようにする
    on release_roles :all do
      execute :mkdir, '-p', release_path
      execute :wget, "#{fetch(:m2repo)}/#{fetch(:group)}/#{fetch(:application)}/#{fetch(:version)}/#{fetch(:application)}-#{fetch(:version)}.war -P #{release_path}"
    end

    #invoke "deploy:set_current_revision" gitを使わないので不要になる
    # 代わりに、入力させたバージョンをREVISIONファイルに書き込む
    on release_roles :all do
      within release_path do
        execute :echo, "\"#{fetch(:version)}\" >> REVISION"
      end
    end

    invoke 'deploy:symlink:shared'
  end


  task :restart do
    on roles(:app), in: :sequence, wait: 5 do |host|
      info("restart on #{host}")
      # LBからサーバーを切り離して、アプリケーションサーバーを再起動したりする処理をここに書く
    end
  end

  after :publishing, :restart
end