AR ホームベーカリー

オイラのアウトプット用ホームベーカリー!

Capistrano デプロイする環境をシュッと作る

プロジェクト初期にしかやらないから、毎回忘れて泣きながら調べてる。 ので、備忘録を兼ねて。

ちなみにこの手順を書くにあたって、社内のナレッジ ( docbase 使ってます ) を確認していたら、退社した元 CTO 兄貴迫真のカレーレシピを見つけて「日本人だいたい同じようなレシピに行き着くのか?」と DASH カレーと見比べてた。

作業環境は既存のプロジェクトでも、なんなら rails #{プロジェクト名} して scaffold しただけの環境でも構いません。 デプロイするのが目的なら、他の Gem と競合することもそうそう無いはず。

また、本作業は以下を想定しています。

  • puma
    • Unicorn だとスロークライアント掴んだ時や、同期系処理でプロセス掴みっぱなしの時、処理できる数が減ってしまうのでその対策も兼ねています。
  • webpack
    • 使ってなかったりわかんなかったら読み飛ばしてオッケー
  • ENV に staging を追加
    • 標準で用意されてるの development と production だけなの難しくない?

Gem の追加

以下を Gemfile に追加します。

基本的にデプロイ先の環境には不要な Gem ので、 group :development do 内に記載するのが簡単で良いでしょう。 しっかりやるのであれば、 group :deploy なり、デプロイ向けの専用グループを作ると良いですが、普段めったにやらないので割愛します。

記述追記後に、以下のコマンドで Gem を追加します

bundle install ${必要なら path をつける}

Gemfile

group :development do

  # なんか他の記述がある #

  # Deploy
  gem "capistrano", "~> 3.11", require: false
  gem 'capistrano-bundler', require: false
  gem 'capistrano-rails', require: false
  gem 'capistrano3-puma', require: false
end

デプロイファイルの雛形を作る

以下のコマンドでジュッと追加します。

bundle exec cap install

ENV に staging を追加する

そんなに難しくなくて、用意されたファイルをコピーすれば追加できます。

vi config/database.yml
cp config/environments/production.rb config/environments/staging.rb
vi config/webpacker.yml

config/database.yml

以下を production: の直上あたりに追加。

staging:
  <<: *default
  database: データベース名_staging

config/webpacker.yml

webpacler 使ってるかわからない場合、ファイルが存在しなければ飛ばして良いです。

staging:
  <<: *default
  compile: false
  extract_css: true
  cache_manifest: true
  public_output_path: packs

デプロイファイルを各種設定する

Capistrano がデプロイ時に参照するファイルを設定します。

Capfile

Capistrano が読み込む動作を設定します。

Capfile

# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

# Load the SCM plugin appropriate to your project:
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

# Include tasks from other gems included in your Gemfile
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano/puma'
install_plugin Capistrano::Puma  # Default puma tasks
install_plugin Capistrano::Puma::Workers  # if you want to control the workers (in cluster mode)

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

deploy.rb 共通処理

デプロイ時の設定のうち、すべての環境で共通するものを記載します。 ENV ごとに個別に記載したいものは、以降の項目で別途記載できます。

また、以下に必要と思われる項目を簡単に解説をば。

  • set :application, "example"
    • デプロイするアプリケーション名です、なんでもよかったはず。 大体リポジトリ名をつけとくとわかりやすくて良い。
  • set :repo_url, "git@github.com:example/example.git"
    • デプロイに利用するリポジトリ URL です。以下で指定する :local_user で何もオプションつけず特別なことをせず、 git clone ${URL} だけで clone できるようにしておく必要があります。
  • set :branch, ENV['BRANCH'] || "master"
    • ( ENV ごとの個別ファイルに書くほうが良いかもしれません。 ) これはコマンド実行時に環境変数 BRANCH がなければ、 master の内容を反映するという設定です。 受託開発など、自分たちで完全にプロジェクトをコントロールできない、特定のブランチ以外を適用する必要がある、という場合が往々にして発生するため、僕はこのような運用に行き着きました……。
  • set :deploy_to, "/var/www/example"
    • デプロイ先ディレクトリです。
    • 実際のドキュメントルートはこのパスに /current/ が付与されます。 Capistrano はこの current をシンボリックリンクを切り替える事で管理しており、実体は同階層の /releases/年月日時/となります。
  • set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto
    • Capistrano の動作ログを、実行した環境の log 以下に出力します。
  • set :local_user, -> { "ec2-user" }
    • デプロイ先で操作やらプロセス立ち上げを担当するローカルユーザを指定します、 SSH 操作を行うユーザと同一にしておいてください。 たぶん分離できると思うけどめんどくて試してない(本音)。
  • set :keep_releases, 5
    • デプロイしたファイルを何世代残すかの設定です。 この残してる世代だけロールバックできるので、最低でも 2 は指定するようにしてください。 5 なのは過去の知見から「5回も連続してデプロイ失敗せんじゃろ」という感じで設定しています。

config/deploy.rb

lock "~> 3.11.2"

# cap
set :application, "example"
set :repo_url, "git@github.com:example/example.git"
set :branch, ENV['BRANCH'] || "master"
set :deploy_to, "/var/www/example"
set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto
set :local_user, -> { "ec2-user" }
set :keep_releases, 5

append :linked_files, "config/puma.rb"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "vendor/bundle", "public/assets", "public/packs", "node_modules"

# puma
set :puma_conf, -> { File.join(shared_path, 'config/puma.rb') }
set :puma_daemonize, true

desc "Restart Application"
task :restart do
  on roles(:app), in: :sequence, wait: 5 do
    invoke 'puma:restart'
  end
end

after 'deploy:publishing', 'deploy:restart' 

staging.rb / production.rb ENV ごとの設定ファイル

与えられた ENV によって、設定 ( 実行 ) する内容を分けるために利用します。 最低限 IP は別だと思うのと、ファイル無いと動かないと思うので、DRY! とかいって全部 deploy.rb にかくのはやめようね……。

  • set :stage, :staging
    • 動作させる RAILS_ENV を指定します、ファイル名とイコールとしましょう、紛らわしいからね
  • server 'xxx.xxx.xxx.xxx', user: 'ec2-user', roles: %w[web app db]
    • server: IP もしくはドメインを指定します
    • user: SSH 接続するユーザを指定します
    • roles: これがちょっと難しくて、Capistrano の操作 ( タスク ) には、それと関連した role が割り当てられています。 たとえば、 db の role を持つ対象は rails db:〜 を実行しますが、割り当てられなければ実行されません。
      • これは複数台のサーバが存在する場合、同時に migrate が実行されるのを防ぐ、などのパターンが思いつきます。 とりあえずわからないうちは、 role は基本 [web app db] を書くのは一台だけ、複数台ある場合の残りは [web app] のみ、と考えてると良いです、残りは頑張って公式の README とかコード読もう。
  • keys: [File.expand_path('~/.ssh/aws_good-looking.pem')],
    • server で指定したインスタンスSSH ログインするための鍵ファイルです。 なるべく商用 ( 本番 ) 環境とそれ以外は鍵ファイルを分けよう、あと鍵ファイルはコミットするなよ!
vi config/deploy/staging.rb
vi config/deploy/production.rb

config/deploy/staging.rb

set :stage, :staging

server 'xxx.xxx.xxx.xxx', user: 'ec2-user', roles: %w[web app db]

set :ssh_options, {
  keys: [File.expand_path('~/.ssh/aws_good-looking.pem')],
  forward_agent: false,
  auth_methods: %w{publickey}
} 

config/deploy/production.rb

set :stage, :production

server 'xxx.xxx.xxx.xxx', user: 'ec2-user', roles: %w[web app db]

set :ssh_options, {
  keys: [File.expand_path('~/.ssh/aws_good-looking_production.pem')],
  forward_agent: false,
  auth_methods: %w{publickey}
} 

デプロイ先ディレクトリを用意する

以下のコマンドを実行すると、 SSH 疎通可能であれば、デプロイ先ディレクトリの不足分を補って雛形を生成してくれます。 ${ENV} の部分は、環境に合わせて変更してください。 本手順なら staging production のどちらかが利用できますね。

また、初回実行するとディレクトリがなくてエラー吐いて停止すると思いますが、そのときにひな形ディレクトリが生成さっるため、続けて二回目を実行すると正常に終了すると思います。 だめだったらなんか独自設定追加されてるはずだから、手動で足りないディレクトリとかファイル追加してね。

この時、 config/ 以下の puma 系の値に応じて puma 用のコンフィグファイルがついでに生成されます。 手動で作る場合は bundle exec cap ${ENV} puma:config でリモートに再生成できます。 しかし強制的に上書きしてバックアップも残らないので、リモートでだけ、手動で変更を加えていると死が待っているので、手動での変更は……やめようね!

参考: qiita.com

bundle exec cap ${ENV} deploy:check

デプロイする

おまたせしました、実際に反映してみましょう! コマンドは以下です。 また、例によって ${ENV} は環境に合わせてご記載ください。

bundle exec cap ${ENV} deploy

内容によってはだいぶ時間がかかりますが無事デプロイできたでしょうか? ちなみに上記は環境変数 BRANCH 未設定のため、 master が反映されるはずです。 ブランチを指定したい場合は以下を利用してください。

BRANCH=${対象のブランチ名} bundle exec cap ${ENV} deploy

ついでですが、Capistrano はリモートリポジトリの内容を反映するので、自分のローカルの作業中ブランチを反映するものではありません。 試した内容を誰にもバレずにこっそり試したい、というのはあると思いますが、諦めて適当なブランチをリモートに切ってください。 そしてやらかしたらブランチをクローズするのです。

このあとやること

実運用に持っていくには、おそらく wheneverdelayed_job あたりが大体必要じゃないかな、と思います。 以下簡単に説明を記載しておきます。

whenever

Gemfile

全体に適応する箇所に記載して、 bundle install する。

gem 'whenever', require: false

Capfile

 require 'whenever/capistrano'

config/deploy.rb

 set :whenever_identifier, -> { "#{fetch(:application)}_#{fetch(:stage)}" }

config/schedule.rb

require File.expand_path(File.dirname(__FILE__) + "/environment")
rails_env = ENV['RAILS_ENV'] || :development
set :environment, rails_env
set :output, "#{Rails.root}/log/whenever.log"
env :PATH, "/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin"

実行するスケジュールを指定する do
  rake とかのむっちゃ良い処理を書く
end

delayed_job

探すと Gem ふたつ出てくると思うんですが、こっちが delayed_job の README に記載されている ( たぶんこっちしか最新バージョンだと動かない ) ので、採用しています。 何より以下の記述どおりにすると、 デプロイ後に :publishing と書いている所で合わせて delayed_job 再起動してくれるので反映し忘れも防げるはず。

Gemfile

全体に適応する箇所に記載する。

gem 'delayed_job_active_record'
gem 'daemons'

capistrano 系の記述の直下に追加する。

gem 'capistrano3-delayed-job', require: false

終わったら bundle install する。

Capfile

 require 'capistrano/delayed_job'

config/deploy.rb

desc "Restart Application" の直上に挿入しておいてください、 role はとりあえず app で良いと思います。 場合によって変更してね。

# delayed_job
set :delayed_job_workers, 1
set :delayed_job_roles, [:app]