Ruby
Rails
capistrano
vps
Rails5

(初心者向け)vpsを契約して、Capistrano3でRailsアプリをデプロイするまで [その2 ローカル設定編]

はじめに

(初心者向け)vpsを契約して、capistrano3でRailsアプリをデプロイするまで [その1 サーバー設定編] の続きです。
前回でサーバーの設定は一通り完了したので、こちらでは、デプロイするテストアプリの構築、Capistrano、Unicornの設定を行います。

1. Railaアプリの作成

Rilsチュートリアルの第2章に習ってscaffoldでユーザー名簿のアプリを作成します。
Rubyのインストール、Railsのインストールは省略します。

1-1 アプリの構築

Rails newで構成を作ります。

以下全てローカルです。任意のディレクトリで始めてください。

# Railsディレクトリの構築
$ rails new hoge_app
$ cd hoge_app
# gemのインストール
$ bundle install

1-2 model、view、controllerの作成

アプリ作成自体はこの記事の主ではないので、簡単にscaffoldで作成します。

# MVCの一括作成
$ rails generate scaffold User name:string email:string
# dbのmigrate
$ rails db:migrate

1-3 ルーティングの設定

このままだとroot(ipアドレス直下)で何が表示されるか設定されいないので、ルーティングを設定します。
config/routes.rbを開いて

$ vi config/routes.rb

以下の用にroot to:を追加してください

Rails.application.routes.draw do
  resources :users
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root to: 'users#index'
end

1-4 起動確認

rails serverで以下画面が表示されればとりあえずアプリは完成です。

# サーバーの起動
$ rails s

http://0.0.0.0:3000/
にアクセス

スクリーンショット 2018-01-02 7.36.45.png

1-3 データベースの設定

Rails newで作成したデフォルトの設定では、データベースがSQLiteとなっています。今回サーバー側では、MySQLを使用しますので、その設定を行います。
config/database.ymlを開いて

$ vi config/database.yml
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

test:
  <<: *default
  database: db/test.sqlite3

# ここから変更
production:
  adapter: mysql2
  encoding: utf8
  pool: 5
  database: hoge_app_production
  username: root
  password: root # その1で設定したサーバー側のmysqlのパスワード
  socket: /var/lib/mysql/mysql.sock

データベースの作成自体は後ほどcapistranoで行います。

次にproductionでSQLiteを使用しないので、Gemfileを修正します。

$ vi Gemfile

groupでくくられていないgem ’sqlite3’をコメントアウトし、developmentのグループのなかに’sqlite3’を追加します。
さらにproductionのグループを追記し、そちらにmysql2を設定しています。

.
.

# Use sqlite3 as the database for Active Record
# gem 'sqlite3' コメントアウト
.
.

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '~> 2.13'
  gem 'selenium-webdriver'
  # 追加
  gem 'sqlite3'
end
.
.
# 以下追加
group :production, :staging do
  gem 'mysql2'
end

編集終わったらインストール

$ bundle install

1-4 バージョン管理の開始

Capistranoではgithubを介したデプロイを行うので、githubのremoteリポジトリの登録とpushを行います。

# バージョン管理の開始
$ git init
# 変更履歴のコミット
$ git add . && git commit -m 'first commit'
# リモートリポジトリの登録(事前にご自身のgithubでリポジトリを作っておいてください)
# リポジトリ追加の際に表示される案内に従ってコマンドを入力
$ git remote add origin git@github.com:kawamataryo/hogeapp.git
# リモートへのアップ
$ git push -u origin master

2. Capistranoの設定

Capistranoとは、Ruby製のデプロイツールです。
デプロイツールとは、ローカルで開発していたアプリを、ステージング、プロダクションのサーバーにアップロードする様々な操作を自動化するものです。例えば、更新ファイルの転送、DBのmigrationの実行、NginxやUnicornの再起動、バージョン管理、複数サーバーへのデプロイなどを1コマンドで実行することができます。

これから出てくるCapistranoの設定方法については主に以下記事を参考にさせて頂きました。

(Capistrano編)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで - Qiita

2-1 Gemの追加

Capistranoの関連Gemをインストールします。

$ vi Gemfile

developのグループにcapistrano関連のgem、とproducitonのグループにunicornのgemを追加してください。

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  # 以下追記
  gem 'capistrano'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capistrano-rbenv'
end

group :production, :staging do
 gem 'mysql2'
 # 以下追記
 gem 'unicorn'
end

追加したらインストール!

$ bundle install

これで今回のデプロイで使うgemが全てインストールされました。

2-2 capistranoの設定ファイル生成

新たにcapコマンドが使えるのでそれでcapistranoの設定ファイルを作成します。

$ bundle exec cap install

以下のようなファイルが生成されます。

hogeapp
├─  Capfile
├─  config
│ ├─  deploy
│ │ ├─production.rb
│ │ └─staging.rb
│ └─deploy.rb
└─  lib
    └─capistrano
        └─tasks

2-3 capistranoの基本設定

capistranoの設定ファイルを編集します。
ます、何を読み込むか、Capistranoでどどのような動作を行うかに合わせて決めます。

$ vi capfile

最初に記載されている内容は削除で大丈夫です。
今回は下の用に設定しましょう。
require hogehogeの部分で読み込むファイルを設定しています。

require "capistrano/setup"
require "capistrano/deploy"
require "capistrano/rbenv"
require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
require "capistrano/scm/git"

install_plugin Capistrano::SCM::Git

# taskを記述したファイルを読み込む用に設定。
# なおデフォルトでは *.rakeとなっているのでもとの記述をそのまま使う場合は注意!!
Dir.glob("lib/capistrano/tasks/*.rb").each { |r| import r }

2-4 productionの環境設定

2-2で生成したconfig/deploy配下にあるprouction.rbにサーバーの情報を記述します。ちなみにstaging.rbはその名の通り、ステージングの情報です。

vi config/deploy/production.rb

こちらも元のコードは全削除で大丈夫です。

config/deploy/production.rb
# conohaのサーバーのIP、ログインするユーザー名、サーバーの役割
# xxxの部分はサーバーのIPアドレス
# 10022はポートを変更している場合。通常は22
server 'xxx.xxx.xxx.xxx', user: 'hoge', roles: %w{app db web}, port: 10022 

#デプロイするサーバーにsshログインする鍵の情報。サーバー編で作成した鍵のパス
set :ssh_options, keys: '~/.ssh/conoha_hoge/id_rsa'

次にproductionとstaging共通の設定について、
deploy.rbに記述します。

$ vi config/deploy.rb

こちらも最初の記述は全削除で。
※ capistranoのバージョンは確認して、以下記述のバージョンを調整してください。
rubyのバージョンからアプリケーション名、デプロイで送るgithubのリポジトリ、capistranoのタスクなどを記述します。

config/deploy.rb
# capistranoのバージョン固定
lock "~> 3.10.1"

# デプロイするアプリケーション名
set :application, 'hoge_app'

# cloneするgitのレポジトリ
# 1-3で設定したリモートリポジトリのurl
set :repo_url, 'git@github.com:kawamataryo/hogea_pp.git'

# deployするブランチ。デフォルトはmasterなのでなくても可。
set :branch, 'master'

# deploy先のディレクトリ。
set :deploy_to, '/var/www/hoge_app'

# シンボリックリンクをはるファイル
set :linked_files, fetch(:linked_files, []).push('config/secrets.yml')

# シンボリックリンクをはるフォルダ
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

# 保持するバージョンの個数(※後述)
set :keep_releases, 5

# rubyのバージョン
# rbenvで設定したサーバー側のrubyのバージョン
set :rbenv_ruby, '2.4.1'

# 出力するログのレベル。
set :log_level, :debug

# デプロイのタスク
namespace :deploy do

  # unicornの再起動
  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart'
  end

  # データベースの作成
  desc 'Create database'
  task :db_create do
    on roles(:db) do |host|
      with rails_env: fetch(:rails_env) do
        within current_path do
                  # データベース作成のsqlセット
                # データベース名はdatabase.ymlに設定した名前で
                  sql = "CREATE DATABASE IF NOT EXISTS hoge_app_production;"
                  # クエリの実行。
                # userとpasswordはmysqlの設定に合わせて
                execute "mysql --user=root --password=root -e '#{sql}'"

        end
      end
    end
  end

  after :publishing, :restart

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
    end
  end
end

参考に上げた
(Capistrano編)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで - Qiita
に詳細説明あるので、ここでは各パラメーターの説明は省略します。

2-5 環境変数の設定(サーバー側)

2-4で上げたdeploy.rbの設定において以下記述があります。

# シンボリックリンクをはるファイル
set :linked_files, fetch(:linked_files, []).push('config/secrets.yml')

これはRailsで使用するsecrets.ymlはここを読み込んでねというリンクを設定している記述です。
まだ、サーバー側でsecrets.ymlを作成していないので、そちらを作りましょう。

まず、ローカルで乱数を作成します。
rake secretで乱数を生成、それをpbcopyでクリップボードにコピーしています。

# 以下ローカルのRailsディレクトリで実行してください
$ rake secret | pbcopy

次はサーバー側 での処理です。
sshでサーバーにログインして、ディレクトリとファイルの作成を行います。

# サーバーログイン
$ ssh conoha_hoge
# ディレクトリの作成 
$ cd /var
$ sudo mkdir -p www/hoge_app/shared/config
# 所有者を設定
$ sudo chown -R hoge www
# ファイルの作成
$ cd www/hoge_app/shared/config
$ sudo vi secrets.yml

secrets.ymlには以下を記述してください。
secret_key_baseの値の乱数の部分は先程コピーしたものです。

secrets.yml
production:
  secret_key_base: jr934ugr89vwredvu9iqfj394vj9edfjcvnxii90wefjc9weiodjsc9oi09fiodjvcijdsjcwejdsciojdsxcjdkkdsv     

これで環境変数の設定は完了です。
なおsecret_key_baseは、クッキーの暗号化、復号化に使用するようです。
以下参考

Rails secret_key_baseとは何なのか - Qiita

2-6 unicornの設定

アプリケーションサーバーであるunicornのセットアップタスクを記述します。
capistranoでユニコーンを操作するためのたタスクです。
NginxとUnicornの関係が気になる方は、以下を読みましょう。
Rails開発におけるwebサーバーとアプリケーションサーバーの違い(翻訳) - Qiita

以下作業は全てローカルです、

# railsアプリのディレクトリにて
$ vi lib/capistrano/tasks/unicorn.rb

作成したunicorn.rbには以下のように記述しましょう
こちらも、以下記事参考にさせて頂きました。
(Capistrano編)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで - Qiita
各設定の意味まで詳細に解説してくださっているので大変参考になります。

#unicornのpidファイル、設定ファイルのディレクトリを指定
namespace :unicorn do
  task :environment do
    set :unicorn_pid,    "#{current_path}/tmp/pids/unicorn.pid"
    set :unicorn_config, "#{current_path}/config/unicorn/production.rb"
  end

#unicornをスタートさせるメソッド
  def start_unicorn
    within current_path do
      execute :bundle, :exec, :unicorn, "-c #{fetch(:unicorn_config)} -E #{fetch(:rails_env)} -D"
    end
  end

#unicornを停止させるメソッド
  def stop_unicorn
    execute :kill, "-s QUIT $(< #{fetch(:unicorn_pid)})"
  end

#unicornを再起動するメソッド
  def reload_unicorn
    execute :kill, "-s USR2 $(< #{fetch(:unicorn_pid)})"
  end

#unicronを強制終了するメソッド
  def force_stop_unicorn
    execute :kill, "$(< #{fetch(:unicorn_pid)})"
  end

#unicornをスタートさせるtask
  desc "Start unicorn server"
  task start: :environment do
    on roles(:app) do
      start_unicorn
    end
  end

#unicornを停止させるtask
  desc "Stop unicorn server gracefully"
  task stop: :environment do
    on roles(:app) do
      stop_unicorn
    end
  end

#既にunicornが起動している場合再起動を、まだの場合起動を行うtask
  desc "Restart unicorn server gracefully"
  task restart: :environment do
    on roles(:app) do
      if test("[ -f #{fetch(:unicorn_pid)} ]")
        reload_unicorn
      else
        start_unicorn
      end
    end
  end

#unicornを強制終了させるtask
  desc "Stop unicorn server immediately"
  task force_stop: :environment do
    on roles(:app) do
      force_stop_unicorn
    end
  end
end

続いて、unicornの設定ファイルを作成します。
まずファイルの新規作成

$ mkdir config/unicorn
$ vi config/unicorn/production.rb

ファイルの中身については以下のように記述しましょう。

#ワーカーの数
  $worker  = 2
#何秒経過すればワーカーを削除するのかを決める
  $timeout = 30
#自分のアプリケーション名、currentがつくことに注意。
  $app_dir = "/var/www/hoge_app/current"
#リクエストを受け取るポート番号を指定。後述
  $listen  = File.expand_path 'tmp/sockets/.unicorn.sock', $app_dir
#PIDの管理ファイルディレクトリ
  $pid     = File.expand_path 'tmp/pids/unicorn.pid', $app_dir
#エラーログを吐き出すファイルのディレクトリ
  $std_log = File.expand_path 'log/unicorn.log', $app_dir

# 上記で設定したものが適応されるよう定義
  worker_processes  $worker
  working_directory $app_dir
  stderr_path $std_log
  stdout_path $std_log
  timeout $timeout
  listen  $listen
  pid $pid

#ホットデプロイをするかしないかを設定
  preload_app true

#fork前に行うことを定義
  before_fork do |server, worker|
    defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
    old_pid = "#{server.config[:pid]}.oldbin"
    if old_pid != server.pid
      begin
        Process.kill "QUIT", File.read(old_pid).to_i
      rescue Errno::ENOENT, Errno::ESRCH
      end
    end
  end

#fork後に行うことを定義
  after_fork do |server, worker|
    defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
  end

3. githubへのpush

あともう少しでデプロイ完了です。
capistranoはgithubを介してサーバーにファイルを送ります。
なので今までの変更をgithubへpushしましょう。

# 変更履歴をステージング
$ git add .
# コミット
$ git commit -m 'config complete'
# githubへ送信
$ git push origin master

4. サーバーへのデプロイ

いよいよ最後。capistranoでサーバーにデプロイします。

4-1 サーバーにてアプリの用のDB作成

いざデプロイ!と実行したいのですが、まだサーバーにアプリ用のデータベースを作成していません。なので最初はサーバーのデータベース作成を行います。
おそらく私の手順が悪いのですが、少し面倒な手順をおいます。

まず、サーバー側にアクセスしvar/www/hoge_app/currentディレクトリを作成

$ ssh conoha_hoge
# 以下サーバー側にて
$ mkdir /var/www/hoge_app/current
$ exit

次にローカル側でcapistranoのタスクに登録していたコマンドを実行

$ bundle exec cap production deploy:db_create

これでDBを作成できました。

次にdeployでエラーを出さないように先程作成したサーバー側のcurrentディレクトリを削除します。

$ ssh conoha_hoge
# 以下サーバー側にて
$ sudo rm -r /var/www/hoge_app/current
$ exit

なぜこのような手順を追うかと言うと、deploy:db_createではcurrentがないとエラー。deployだとcurrentがあるとエラー。という状況にあたったからです。
もし最適な方法が分かる方いましたらコメントにて指摘お願いします。

4-2 デプロイの実行

長くなりましたがやっとデプロイです!!以下コマンドでデプロイが成功するはず!!
初回はrbenvのところで少し時間がかかるかと思います。

$ bundle exec cap production deploy

4-3 Nginxの起動

まだNginxが起動していないので、
サーバーにアクセスし、Nginxを起動しましょう。

# サーバーへログイン
$ ssh conoha_hoge

Nginxの起動

$ sudo service nginx start
$ exit

4-4 サイト確認

何事もなく完了したらサイトにアクセスしてみましょう。

$ open http://xxx.xxx.xxx.xxx # conohaのipアドレス

無事表示されればデプロイ完了です!!

最後に

私は、独学でプログラミングを学びRailsチュートリアルを参考にwebアプリを作成しました。
でもデプロイの段階で困り果て、VPSって何?なんか勝手にお金かかりそうで怖い!なんで最初からサーバーの入ってないの?っとかなり及び腰でした。

しかし、先人の方々の記事を参考に進めた所、つまづきつまづきエラーとにらめっこしながらですが、なんとかアプリ運用までたどり着きました。

今回の経験で学んだことは、
- エラーメッセージは怖くない!(英語でちゃんと理由を説明してくれているので)
- 設定ファイルはしっかり理解しよう!(コピペだとエラーのデバック時時間が無限にかかります涙)
- デプロイできるとものすごく嬉しい!(重要!)

ということです。
自分と同じように自作アプリをデプロイしようとする方の参考になれば幸いです。

また、こんなエラーが出てデプロイ出来ない!typoがある!こっちのほうが良い方法だ!という意見・質問等ございましたらコメント・編集リクエストお願いします。
出来る限り答えていきます。