自動デプロイツール(Capistrano)導入方法

デプロイの方法の手順⑥　今後の自分用のメモに

#Capistranoの導入準備
###Capistranoを利用するためのGemをインストールしよう

・Gemfile

```ruby
group :development, :test do
  gem 'capistrano'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capistrano3-unicorn'
end
```

・ターミナル（ローカルPC）

```bash
#Gemfileを読み込む
bundle install

#gemを読み込めたら、下記のコマンドを打ちます。
bundle exec cap install
```

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

:open_file_folder:アプリケーション名
--:page_facing_up:capfile
--:open_file_folder:config
----:open_file_folder:deploy
------:page_facing_up:production.rb
------:page_facing_up:staging.rb
----:page_facing_up:deploy.rb
--:open_file_folder:lib
----:open_file_folder:capistrano
----:open_file_folder:tasks

###利用するライブラリを指定するファイル
####Capfile
Capistranoの機能を提供するコードはいくつかのライブラリ(Gem)に分かれています。そのため、Capistranoを動かすにはいくつかのライブラリを読み込む必要があります。Capfileは、Capistrano関連のライブラリのうちどれを読み込むかを指定できます。

###デプロイについての設定を書くファイル
####deploy.rb、production.rb、staging.rb
Githubへの接続に必要なsshキーの指定、デプロイ先のサーバのドメイン、AWSサーバへのログインユーザー名、サーバにログインしてからデプロイのために何をするか、といった設定を記載します。
では、capistranoを動かすためのそれぞれのファイルを編集しましょう。

###Capfileを編集しよう
・Capfile

```ruby
require "capistrano/setup"
require "capistrano/deploy"
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'

Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
```

require により引数としておかれた文字列が指すディレクトリが読み込まれ、その中にデプロイに際して必要な動作が一通り記述されています。

#デプロイについての設定ファイルを編集しよう

staging.rb/production.rb
cap installコマンドを打つと、config/deploy配下にproduction.rbとstaging.rbの2種類のファイルが生成されます。
これらは、デプロイする環境別の設定を記述するファイルです。今回はproduction、つまり本番環境のものだけ編集します。具体的には下記の内容を記述していくこととなります。

* サーバーホスト名
* AWSサーバーへのログインユーザー名
* サーバーロール(後述)
* SSHの設定
* その他サーバーに紐づく任意の設定

###production.rbを編集しましょう

・config/deploy/production.rb

```ruby
server '<用意したElastic IP>', user: 'ec2-user', roles: %w{app db web}

#<用意したElastic IP>はご自身のものを記述してください。
#編集後は例えば、以下のようになります。
server '12.345.67.890', user: 'ec2-user', roles: %w{app db web}
```

###deploy.rb
ここには、production環境、staging環境どちらにも当てはまる設定を記述することとなります。
具体的には下記のような項目があります。

* アプリケーション名
* gitのレポジトリ
* 利用するSCM
* タスク
* それぞれのタスクで実行するコマンド

###下記の指示に従い、deploy.rbを編集しましょう

・config/deploy.rb

```ruby
# config valid only for current version of Capistrano
# capistranoのバージョンを記載。固定のバージョンを利用し続け、バージョン変更によるトラブルを防止する
lock '<Capistranoのバージョン>'

# Capistranoのログの表示に利用する
set :application, '<自身のアプリケーション名>'

# どのリポジトリからアプリをpullするかを指定する
set :repo_url,  'git@github.com:<Githubのユーザー名>/<レポジトリ名>.git'

# バージョンが変わっても共通で参照するディレクトリを指定
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads')

set :rbenv_type, :user
set :rbenv_ruby, '<このアプリで使用しているrubyのバージョン>' #カリキュラム通りに進めた場合、2.5.1か2.3.1です

# どの公開鍵を利用してデプロイするか
set :ssh_options, auth_methods: ['publickey'],
                  keys: ['<ローカルPCのEC2インスタンスのSSH鍵(pem)へのパス（例：~/.ssh/key_pem.pem）>'] 

# プロセス番号を記載したファイルの場所
set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }

# Unicornの設定ファイルの場所
set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" }
set :keep_releases, 5

# デプロイ処理が終わった後、Unicornを再起動するための記述
after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end
end
```

以下の5点は書き換えが必須です。

1. 3行目のはご自身のバージョンを記述しましょう。
2.  6行目の<自身のアプリケーション名>はご自身のものを記述しましょう。
3. 9行目の/<レポジトリ名>も同様に、ご自身のもの記述してください。
4. 15行目の<このアプリで使用しているrubyのバージョン>はご自身のものを確認して記述してください。
5. 19行目の<ローカルPCのEC2インスタンスのSSH鍵(pem)へのパス>も同様に、ご自身のもの記述してください。

####lock ＜Capistranoのバージョン＞'
capistranoのバージョンは、gemfile.lockに記載されています。以下のように書かれている場合は、3.11.0です。

・例gemfile.lock

```ruby
# 省略
capistrano (3.11.0)
# 省略
```

####capistrano内のDSLについて
ファイル内をよく見ると、set: 名前, 値といった記述が多く使われています。これは、DSLの一種です。

###DSL
DSLとは、ある特定の処理における効率をあげるために特化した形の文法を擬似的に用意したプログラムです。
上記のset :名前, 値について、これは言わば変数のようなものです。
例えばset: Name, 'value' と定義した場合、fetch Name とすることで 'Value'が取り出せます。
また、一度setした値はdeploy.rbやproduction.rbなどの全域で取り出すことができます。

また、ファイル内には、desc '◯◯'やtask:XX doといった記述がよく見受けられます。これは、先ほどCapfileでrequireしたものに加えて追加のタスクを記述している形です。ここで記述したものもcap deploy時に実行されることとなります。

###Capistranoによる自動デプロイ後のディレクトリ構成について
一度Capistranoによる自動デプロイを実行すると、本番環境のアプリケーションのディレクトリが変化します。
Capistranoによるアプリのバックアップなど、複数のディレクトリが作成されます。その中でも特に重要なのが、releases、current、sharedディレクトリです。

####releasesディレクトリ
capistranoを通じてデプロイされたアプリは、releasesというフォルダにひとまとめにされます。
ここに過去分のアプリが残っていることにより、デプロイ時に何か問題が発生しても一つ前のバージョンに戻ったりすることができます。
そして、その過去分の保存数を指定しているのがdeploy.rbのset :keep_releasesの記述となります。今回は5つ、過去のバージョンを保存するよう設定しました。

####currentディレクトリ
releasesフォルダの中で一番新しいものが、自動的にcurrentというフォルダ内にコピーされているような状態になります。
そのため、このcurrent内に入っているアプリの内容が、現在デプロイされている内容ということになります。

####sharedディレクトリ
バージョンが変わっても共通で参照されるディレクトリが格納されるディレクトリです。具体的には、log、public、tmp、vendorディレクトリが格納されます。

###unicorn.rbの記述を編集しましょう

手動でUnicornを起動する場合に比べ、Capistranoを使った場合には、Railsのアプリケーションのディレクトリが1段階深くなっています。そのため、数カ所変更を加える必要があります。

capistranoでのデプロイ後は、アプリケーションのディレクトリ直下にあるcurrentディレクトリが動きます。そこで、app_pathのディレクトリ指定も一段階追加します。
また、実際に動くディレクトリであるworking_directoryの指定をcurrentにしたり、ログやpidの指定をshared以下にするなどの変更を加えます。

・config/unicorn.rb

```ruby
app_path = File.expand_path('../../', __FILE__)

worker_processes 1

working_directory app_path
pid "#{app_path}/tmp/pids/unicorn.pid"
listen "#{app_path}/tmp/sockets/unicorn.sock"
stderr_path "#{app_path}/log/unicorn.stderr.log"
stdout_path "#{app_path}/log/unicorn.stdout.log"
```

↓↓↓↓↓↓ 以下のように変更 ↓↓↓↓↓↓

```ruby
# ../が一つ増えている
app_path = File.expand_path('../../../', __FILE__)

worker_processes 1
# currentを指定
working_directory "#{app_path}/current"

# それぞれ、sharedの中を参照するよう変更
listen "#{app_path}/shared/tmp/sockets/unicorn.sock"
pid "#{app_path}/shared/tmp/pids/unicorn.pid"
stderr_path "#{app_path}/shared/log/unicorn.stderr.log"
stdout_path "#{app_path}/shared/log/unicorn.stdout.log"
```

###nginxの設定ファイル
同様に、Nginxの設定ファイルも変更が必要です。

これまでは/var/www/以下のアプリケーションに対して連携を設定していたので、/var/www/chat-space以下のcurrent、sharedなどのディレクトリと連携するように設定を変更する必要があります。

###Nginxの設定ファイルの記述を編集しましょう

・ターミナル（EC2サーバ）

```bash
$ sudo vim /etc/nginx/conf.d/rails.conf
```

以下に従って自身の設定ファイルを書き換えて下さい。
こちらの既存のコードをいったん全て削除しましょう。

・rails.conf

```ruby
upstream app_server {
  server unix:/var/www/<アプリケーション名>/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name <Elastic IPを記入>;

# クライアントからアップロードされてくるファイルの容量の上限を2ギガに設定。デフォルトは1メガなので大きめにしておく
  client_max_body_size 2g;

  root /var/www/<アプリケーション名>/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    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;
}
```

↓↓↓↓↓↓ 以下のように変更 ↓↓↓↓↓↓

・rails.conf

```ruby
upstream app_server {
  # sharedの中を参照するよう変更
  server unix:/var/www/<アプリケーション名>/shared/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name <Elastic IPを記入>;

# クライアントからアップロードされてくるファイルの容量の上限を2ギガに設定。デフォルトは1メガなので大きめにしておく
  client_max_body_size 2g;

  # currentの中を参照するよう変更
  root /var/www/<アプリケーション名>/current/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
    # currentの中を参照するよう変更
    root   /var/www/<アプリケーション名>/current/public;
  }

  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    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;
}
```

もう一度記述すべき箇所が記述できているか確認しましょう。

1. 3行目の<アプリケーション名> となっている箇所は、ご自身のものに変更してください。
2. 8行目の＜Elastic IPを記入＞となっている箇所も同様に、ご自身のものに変更してください。
3. 14行目の<アプリケーション名>となっている箇所も同様に、ご自身のものに変更してください。
4. 21行目の<アプリケーション名>となっている箇所も同様に、ご自身のものに変更してください。
5. Nginxの設定を変更したら、忘れずに再読込・再起動をしましょう。

・ターミナル（EC2サーバ）

```bash
[ec2-user@ip-172-31-25-189 ~]$ sudo service nginx reload
[ec2-user@ip-172-31-25-189 ~]$ sudo service nginx restart
```

###MySQLの起動を確認しましょう
MySQLが立ち上がっていないとデプロイが失敗します。以下のコマンドで再起動をしておきましょう。

・ターミナル（EC2サーバ）

```bash
[ec2-user@ip-172-31-25-189 ~]$ sudo service mysqld restart
```

###unicornのプロセスをkillしましょう
自動デプロイを実行する前にunicornのコマンドをkillしておきましょう。
まず、プロセスを確認します。

・ターミナル（EC2サーバ）

```bash
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn

ec2-user 17877  0.4 18.1 588472 182840 ?       Sl   01:55   0:02 unicorn_rails master -c config/unicorn.rb -E production -D
ec2-user 17881  0.0 17.3 589088 175164 ?       Sl   01:55   0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D
ec2-user 17911  0.0  0.2 110532  2180 pts/0    S+   02:05   0:00 grep --color=auto unicorn

#プロセスをkillします。
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill <確認したunicorn rails masterのPID（上記では17877）>
...
```

###ローカルでの修正を全てmasterにpushしましょう
ローカルでのコードの変更が、全てmasterにpushされていることを確認しておきましょう。

以上で自動デプロイの準備は完了です。

#自動デプロイ
これで、ローカルのターミナルからコマンド一発でデプロイできるようになりました。実際に、自動デプロイを実行していきます。

###自動デプロイを実行してみましょう

・ターミナル（ローカルPC）

```bash
# アプリケーションのディレクトリで実行する
$ bundle exec cap production deploy
```

####もし、途中でエラーが出て止まってしまった場合は、以下を試します。

* そのままもう一度同じコマンドを実行してみる
特に、bundle installのタスクの際に、初めて自動デプロイを実行する場合は、メモリ不足で落ちることがあります

* 記述ミスが無いか
特に、本カリキュラムにおいて、修正したファイルに注目して確認しましょう

* 手順を飛ばしていないか
bundle installなどのコマンド実行を忘れていないか確認しましょう

#ブラウザで確認しよう
###ブラウザからElastic IPでアクセスしましょう
ブラウザからElastic IPでアクセスすると、アプリケーションにアクセスできます（:3000をつける必要はありません）。正しく動いていることを確認しましょう。

#IPアドレスにアクセスしてもエラーが出る時

IPアドレスにアクセスするとエラーが表示されたり、想定通りの画面が表示されないことがあります。

* ローカル側（localhost3000）においてエラーが出ていないか
* サーバー側で、/var/www/<レポジトリ名>/current/log/unicorn.stderr.logをlessまたはcatコマンドで確認しエラーが出ていないか確認する（下に行くほど最新のログです。時刻表記がUTCであることに注意してください）
* カリキュラム通りの記載ができているか
* ローカルでの編集のpushやpullを忘れていないか
* サーバー側のmysqlやnginxの再起動を行ってみる
* EC2インスタンスの再起動を行ってみる(※本番環境にてmysqlとnginxの起動も必要です。)

###今後のアプリケーションの更新の流れは以下です。

1. アプリをローカルで更新し、リモートリポジトリにプッシュする

2. ローカルのプロジェクトのディレクトリにいる状態のターミナルでbundle exec cap production deploy を実行する