AWSを使ってアプリケーションを公開する手順(7)Capistranoによる自動デプロイ

#はじめに
AWSを使ってアプリケーションを公開する手順を記載していく。
この記事ではCapistranoを使ってデプロイ作業を自動化する。

#Capistranoの導入
CapistranoはRubyで書かれており、Gemが公開されている。
この記事ではrailsにCapistranoを導入する手順を記載するが、PHPなどの別のフレームワークでも使用できるらしい。

##Capistrano関連のGemをインストールする
Gemfileを以下のように編集する。

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

ターミナルで以下のコマンドを実行し、Gemfileを読み込む。

```
bundle install
```

以下のコマンドを実行し、Capistranoの関連ファイルを生成する。

```
bundle exec cap install
```

以下のファイルが生成される。各ファイルの詳細は後述する。

- Capfile
- config/deploy.rb
- config/deploy/production.rb
- config/deploy/staging.rb

##Capfileを編集する
Capistranoを動作させるにはいくつかのライブラリ(Gem)を読み込む必要がある。Capfileとは、Capistrano関連のライブラリのうちどれを読み込むか指定するためのファイルである。

Capfileを以下のように編集する。
これによりデプロイに必要な動作が記述されたファイルが入ったディレクトリを読み込む。
[参考](https://github.com/capistrano/capistrano)

```ruby:Capfile
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 }
```

##production.rbを編集する
config/deployディレクトリにproduction.rbとstaging.rbが作成された。
これらのファイルはデプロイについての設定を記載するファイルである。
production.rbは本番環境の設定ファイル、staging.rbはステージング環境の設定ファイルである。
production.rb(staging.rb)には下記の内容を記述する。

- サーバホスト名
- AWSサーバのログインユーザ名
- サーバロール
- SSHの設定
- その他サーバに紐づく設定

production.rbを以下のように変更する。
(アプリケーションのElastic IPが12.345.67.890の場合)

```ruby:config/deploy/production.rb
server '12.345.67.890', user: 'ec2-user', roles: %w{app db web}
```

###開発環境、テスト環境、ステージング環境、本番環境とは
- 開発環境
    - ローカルで動作確認などを行う。ここで問題なければテスト環境での検証を行う。
- テスト環境
    - 誤記やリンクのミス、不具合がないかを検証する。
- ステージング環境
    - 本番環境の前に動作や表示に問題がないかを検証する。テスト環境は本番環境とサーバの構成が異なるのに対し、ステージング環境は本番環境とサーバの構成が同じである。
- 本番環境
    - ステージング環境で問題なければアップロードする。

##deploy.rbを編集する
configディレクトリに作成されたdeploy.rbには本番環境、ステージング環境共通の設定を記述する。
具体的には以下を記述する。

- アプリケーション名
- gitのリポジトリ
- 利用するSCM(Software Configuration Management,ソフトウェア構成管理)
- タスク
- それぞれのタスクで実行するコマンド

deploy.rbの記述を削除し、以下のように変更する。
(ここでは例としてCapistranoのバージョンが「3.11.0」、アプリケーション名が「testapp」、Githubのuser名が「test1234」、リポジトリ名が「testapp」、rubyのバージョンが「2.5.1」、ローカルPCのEC2インスタンスのSSH鍵(pem)へのパスが「~/.ssh/xxx.pem」とする。)

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

# Capistranoのログの表示に利用する
set :application, 'testapp'

# どのリポジトリからアプリをpullするかを指定する
set :repo_url,  'git@github.com:test1234/testapp.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, '2.5.1'

# どの公開鍵を利用してデプロイするか
set :ssh_options, auth_methods: ['publickey'],
                  keys: ['~/.ssh/xxx.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
```

###Capistranoのバージョン確認方法
gemfile.lockというファイルに記載されている。

###DSL(Domain Specific Language)について
DSLとは、特定の処理の効率を上げるために擬似的に用意されるプログラムである。
例えば、set :name, 'value'という記述がある。このとき、fetch nameとすることで'value'を取り出すことができる。setした値はdeploy.rbやproduction.rbでも取り出すことができる。
また、task :xx do〜endという記述がある。これはCapfileでrequireしたものに加えてタスクを追加している。ここで記述したものはcap deploy時に実行される。

###Capistranoによる自動デプロイ後のディレクトリ構成
Capistranoによる自動デプロイが実行されると、本番環境のディレクトリ構成が変化する。Capistranoによるアプリケーションのバックアップなど、複数のディレクトリが作成される。
例えば以下のようなディレクトリが作成される。

- releasesディレクトリ
    - Capistranoを通じてデプロイされたアプリケーションはreleasesというディレクトリにまとめられる。ここに過去のアプリケーションが残っているため、デプロイ時に何か問題が発生した時、以前のバージョンに戻すことが可能。deploy.rbのset :keep_releasesの記述は保存しておく数を指定しており、今回は5回分のバージョンを保存しておくように設定している。 
- currentディレクトリ
    - releasesディレクトリの中で最新のものが自動的にこのディレクトリにコピーされる。つまり、このディレクトリにあるアプリケーションの内容が、現在デプロイされているアプリケーションの内容ということになる。
- sharedディレクトリ
    - バージョンが変わっても共通で参照されるディレクトリが格納される。具体的には、log,public,tmp,vendorディレクトリが格納される。

##unicorn.rbを編集する
Capistranoの導入によって、本番環境のディレクトリ構成が変わるのでそれに伴いunicorn.rbの記述も変更する。

unicorn.rbを以下のように変更する。

```ruby:config/unicorn.rb
#サーバ上でのアプリケーションコードが設置されているディレクトリを変数に入れておく
#変更：階層を一個深くする
app_path = File.expand_path('../../../', __FILE__)

#アプリケーションサーバの性能を決定する
worker_processes 1

#アプリケーションの設置されているディレクトリを指定
#変更：currentを指定
working_directory "#{app_path}/current"

#Unicornの起動に必要なファイルの設置場所を指定
#変更：sharedディレクトリ追記
pid "#{app_path}/shared/tmp/pids/unicorn.pid"

#ポート番号を指定
#変更：sharedディレクトリ追記
listen "#{app_path}/shared/tmp/sockets/unicorn.sock"

#エラーのログを記録するファイルを指定
#変更：sharedディレクトリ追記
stderr_path "#{app_path}/shared/log/unicorn.stderr.log"

#通常のログを記録するファイルを指定
#変更：sharedディレクトリ追記
stdout_path "#{app_path}/shared/log/unicorn.stdout.log"

#Railsアプリケーションの応答を待つ上限時間を設定
timeout 60

preload_app true
GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true

check_client_connection false

run_once = true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) &&
    ActiveRecord::Base.connection.disconnect!

  if run_once
    run_once = false # prevent from firing again
  end

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exist?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH => e
      logger.error e
    end
  end
end

after_fork do |_server, _worker|
  defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection
end
```

##Nginxの設定ファイル(rails.conf)を変更する
ディレクトリ構成が変わったのでrails.confも以下のように変更する。
(アプリケーション名が「testapp」、Elastic IPが「12.345.67.890」の場合を例として記載する)

```ruby:rails.conf
upstream app_server {
  # sharedの中を参照するよう変更
  server unix:/var/www/testapp/shared/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name 12.345.67.890;

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

  # currentの中を参照するよう変更
  root /var/www/testapp/current/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
    # currentの中を参照するよう変更
    root   /var/www/testapp/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;
}
```

Nginxの設定を変更したらEC2インスタンスにSSH接続し、
下記のコマンドを実行して再読み込み・再起動を行う。

```
sudo service nginx reload
```

```
sudo service nginx restart
```

##MySQLを再起動する

MySQLが立ち上がっていないとデプロイできないので、
下記のコマンドを実行し念のためMySQLも再起動する。

```
sudo service mysqld restart
```

##unicorn masterのプロセスをkillする
自動デプロイを実行する前にunicorn masterのプロセスをkillしておく。
まずは下記のコマンドを実行しunicorn masterのプロセスIDを確認する。

```
ps aux | grep unicorn
```

下記のコマンドで確認したプロセスをkillする。
(今回はunicorn masterのプロセスIDが17877だったとする)

```
kill 17877
```

##ローカルでの修正内容をmasterにプッシュする
今回編集したファイルを全てmasterブランチにプッシュしておく。

#自動デプロイを実行する
ローカル環境で下記のコマンドを実行し自動デプロイを行う。
エラーがでなければ成功。

```
bundle exec cap production deploy
```

##エラーが出た時に確認すること
- もう一度実行する
- 記述ミスがないか
- 手順を飛ばしていないか

#ブラウザで確認する
ブラウザのURL欄にElastic IPを入力するとアプリケーションにアクセスできる(:3000をつける必要はない)。

##エラーが出た時に確認すること
- 開発環境でエラーが出ていないか
- /var/www/testapp/current/log/unicorn.stderr.logでエラーがないか(リポジトリ名が「testapp」の場合)
- プッシュやプルを忘れていないか
- MySQLやNginxの再起動を行ってみる
- EC2インスタンスの再起動を行ってみる

# 関連記事
[AWSを使ってアプリケーションを公開する手順(1)AWSアカウントの作成](https://qiita.com/osawa4017/items/78ee5912084dd83c5a0d)
[AWSを使ってアプリケーションを公開する手順(2)EC2インスタンスの作成](https://qiita.com/drafts/ec5aa2965a89320b074d)
[AWSを使ってアプリケーションを公開する方法(3)EC2インスタンスの環境構築]
(https://qiita.com/osawa4017/items/8dc09203f84e04bf0e66)
[AWSを使ってアプリケーションを公開する手順(4)データベースの作成]
(https://qiita.com/osawa4017/items/7dba25f4fa30ab0b1246)
[AWSを使ってアプリケーションを公開する手順(5)アプリケーションを公開する]
(https://qiita.com/osawa4017/items/6f3125fcc21f73024311)
[AWSを使ってアプリケーションを公開する手順(6)Nginxを導入する]
(https://qiita.com/osawa4017/items/9b707baf6ddde623068c)