Capistrano + AWS(EC2) + Rails で簡単デプロイ

# はじめに
Capistranoを使って、自作しているRailsアプリをデプロイしてみました。

基本的には以下の記事を参考にしました。
[(Capistrano編)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで](https://qiita.com/naoki_mochizuki/items/657aca7531b8948d267b)

本記事では、参考記事に沿ってエラーで詰まった部分をまとめたので参考になれば嬉しいです。

# 作成するファイル
ローカル側
- Gemfile
- Capfile
- config/deploy.rb
- config/deploy/production.rb
- lib/capistrano/tasks/unicorn.rake
- config/unicorn/production.rb


サーバー側(EC2)
- shared/config/settings.yml
- shared/config/database.yml
- shared/.env
- /etc/nginx/conf.d/app.conf


# ソースコード
## Gemfile
インストールするGem一覧です。

```ruby:(local)Gemfile
group :development, :test do
 gem 'capistrano'
 gem 'capistrano-bundler'
 gem 'capistrano-rails'
 gem 'capistrano-rbenv'
end

group :production, :staging do
  gem 'unicorn'
end
```
他の文献だと'capistrano3-unicorn'というgemを使用している事がありますが、詳細な設定をするためここでは使用しません。

## Capfile

capistrano全体の設定ファイルです。

```ruby:(local)Capfile
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
# taskを記述したファイルを読み込むよう設定。場所と拡張子を指定。
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
```
参考記事では最終行が"~/*.rb"となっていますが、現在のバージョンではrakeがデフォルトになっているのでそちらを使用します。

後ほど作成する lib/capistrano/tasks/unicorn.rake のファイル形式は**.rake**になっていることに注意してください。設定したファイル形式を同じファイル形式にしましょう。（筆者はここで少し詰まりました。）

##config/deploy/production.rb

本番環境の設定を記述するファイルです。

```ruby:(local)config/deploy/production.rb
# EC2サーバーのIP、EC2サーバーにログインするユーザー名、サーバーのロールを記述
server '**.***.***.***', user: 'yuki', roles: %w[app db web]

# デプロイするサーバーにsshログインする鍵の情報を記述
set :ssh_options, keys: '~/.ssh/app_key_rsa'

```

## config/deploy.rb

ここには、production環境、stading環境どちらにも当てはまる設定を記述します。

```ruby:(local)config/deploy.rb
# capistranoのバージョン固定
lock '3.14.1'

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

# cloneするgitのレポジトリ
set :repo_url, 'git@github.com:app/app_aws.git'

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

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

# secret_base_keyを読み込ませるため追記
set :linked_files, %w[config/master.key]

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

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

# 保持するバージョンの個数。過去５つまで履歴を保存。
set :keep_releases, 5

# rubyのバージョン
set :rbenv_ruby, '2.5.1'

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

namespace :deploy do
  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
          execute :bundle, :exec, :rake, 'db:create'
        end
      end
    end
  end

  desc 'Run seed'
  task :seed do
    on roles(:app) do
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:seed'
        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

```

参考記事に加えて追記したコードが以下の二つです。

ここでは、シンボリックリンク（gitignoreに設定するようなオープンにしたくないファイル）に
- master.key
- database.yml
- .env
を加えることでデプロイ時に必要なkeyや環境変数を読み込ませています。


```ruby
# secret_base_keyを読み込ませるため追記
set :linked_files, %w[config/master.key]

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


## lib/capistrano/tasks/unicorn.rake

unicornのセットアップタスクを記述するファイルです。


```ruby:(local)lib/capistrano/tasks/unicorn.rake
# 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

```

Capfileのところでも説明しましたが、unicorn.rakeになっていることに注意してください。
Capfileで.rbで設定した場合はunicorn.rbとなります。



# config/unicorn/production.rb
unicornの設定ファイルです。


```rb:(local)config/unicorn/production.rb

# ワーカーの数。後述
$worker = 2
# 何秒経過すればワーカーを削除するのかを決める
$timeout = 30
# 自分のアプリケーション名、currentがつくことに注意。
$app_dir = '/var/www//rails/golfour/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
```


# shared/config/settings.yml



```yml:(server)shared/config/settings.yml
production:
  secret_key_base: jr934ugr89vwredvu9iqfj394vj9edfjcvnxii90wefjc9weiodjsc9o i09fiodjvcijdsjcwejdsciojdsxcjdkkdsv 
(#生成した乱数を貼り付け)
```


# shared/config/database.yml

```yml:(server)shared/config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: golfour_development
  username: yuki
  password: password
  host: db
  socket: /tmp/mysql.sock

test:
  <<: *default
  database: golfour_test
  username: yuki
  password: password
  host: db-test
  socket: /tmp/mysql.sock

production:
  <<: *default
  database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  host: <%= ENV['DB_HOSTNAME'] %>
```

# shared/.env

ここでdatacbase.ymlで使っている環境変数を記述します。

```(server)shared/.env
DB_NAME=*****
DB_USERNAME=*****
DB_PASSWORD=*****
DB_HOSTNAME=*****

```



# /etc/nginx/conf.d/app.conf

```conf:(server)/etc/nginx/conf.d/app.conf
#各種ログのディレクトリ設定
  error_log  /var/www/rails/mumu/current/log/nginx.error.log;
  access_log /var/www/rails/mumu/current/log/nginx.access.log;
#処理を受け取る最大許容量
  client_max_body_size 2G;
  upstream app_server {
# 連携するunicornのソケットのパス
    server unix:/var/www/rails/mumu/current/tmp/sockets/.unicorn.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 127.0.0.1; # 自分のIPアドレスに変えてください！
#次のリクエストが来るまでの待ち時間（秒
    keepalive_timeout 5;
#静的ファイルを読みに行くディレクトリ
    root /var/www/rails/mumu/current/public;
#キャッシュのディレクトリ
    try_files $uri/index.html $uri.html $uri @app;
    location @app {
      # HTTP headers
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass http://app_server;
    }
#エラーページを設置する場所
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/mumu/current/public;
    }
  }
```

## デプロイコマンド

```
bundle exec cap production deploy
```




# エラー一覧
## メモリ不足
デプロイ実行後に発生。
EC2のメモリが足りないために発生していた。

```console:console
virtual memory exhausted
```
###解決策：スワップファイルを作成する
使用していないメモリを一時的に別の場所に移動させることで、使用可能なにメモリを増幅させる方法です。
以下の記事を参考にすれば、簡単に実装できるのでぜひ。

参考：
[スワップファイル (swap file)とは](https://wa3.i-3-i.info/word1722.html)
[[Rails] CapistranoでEC2へデプロイ：EC2仮想メモリ不足トラブルシュート](https://qiita.com/HrsUed/items/c156ed69e927b6165717)

## Missing secret_key_base for 'production' environment

こちらもデプロイを実行した際に発生したエラーです。

原因はmaster.keyファイルがなかったためでした。
そもそもsecret_key＿base はcredentials.yml.encファイルに記載されています。
しかしこのファイルは暗号化されていて、読み込むにはmaster.keyが必要になるのですが、それがなかったためエラーが発生していました。

## 解決策

・shared/master.keyを作成
・シンボリックリンクにmaster.keyを追加

することでエラーがなくなりました。

参考：[Capistranoで自動デプロイすると"Missing secret_key_base for 'production' environment"のエラーが出る](https://teratail.com/questions/288696)