##Capistrano
Capistranoは、自動デプロイツールと呼ばれるものの一種です。
自動デプロイツールのメリット
自動デプロイツールを利用することによって、デプロイ時に必要なコマンド操作が1回で済むようになります。これにより、手動でデプロイする場合に起こりがちな下記の問題を解消することができます。
- コマンドの打ち間違い、手順の間違いが発生する可能性がある
- 手順が多く、煩わしい
Capistranoを利用すれば、サーバにログインする必要はありません。ローカルのターミナルからのコマンド1つで、これらの作業をいっぺんに完了することができます。
また、一度Capistranoによるデプロイが成功してしまえば、打ち間違いによるデプロイの失敗は起こらなくなります。
##Capistranoの導入
まずは、必要なGemをインストールします。
Gemfile
group :development, :test do
gem 'capistrano'
gem 'capistrano-rbenv'
gem 'capistrano-bundler'
gem 'capistrano-rails'
gem 'capistrano3-unicorn'
end
ターミナル(ローカルPC)
bundle install
ターミナル(ローカルPC)
bundle exec cap install
アプリケーション名
capfile
config
deploy
production.rb
staging.rb
deploy.rb
lib
capistrano
tasks
上記のファイルが生成されます。
###Capfile
Capistranoの機能を提供するコードはいくつかのライブラリ(Gem)に分かれています。そのため、Capistranoを動かすにはいくつかのライブラリを読み込む必要があります。Capfileは、Capistrano関連のライブラリのうちどれを読み込むかを指定できます。
###デプロイについての設定を書くファイル
deploy.rb、production.rb、staging.rb
Githubへの接続に必要なsshキーの指定、デプロイ先のサーバのドメイン、AWSサーバへのログインユーザー名、サーバにログインしてからデプロイのために何をするか、といった設定を記載します。
###Capfile
Capfileは、capistrano全体の設定ファイルです。
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 }
require により引数としておかれた文字列が指すディレクトリが読み込まれ、その中にデプロイに際して必要な動作が一通り記述されています。
次は、デプロイについての設定を記載するファイルを編集します。
cap installコマンドを打つと、config/deploy配下にproduction.rbとstaging.rbの2種類のファイルが生成されます。
これらは、デプロイする環境別の設定を記述するファイルです。今回はproduction、本番環境のものだけ編集します。
編集する内容としては
サーバーホスト名
AWSサーバーへのログインユーザー名
サーバーロール(後述)
SSHの設定
その他サーバーに紐づく任意の設定
を編集していきます。
server '<用意したElastic IP>', user: 'ec2-user', roles: %w{app db web}
<用意したElastic IP>はご自身のElastic IPを入れてください。ちなみにElastic IPを入れるときに<>は不要です。
次に、production環境、staging環境どちらにも当てはまる設定を記述します。
下記のような項目を記述していきます。
アプリケーション名
gitのレポジトリ
利用するSCM
タスク
それぞれのタスクで実行するコマンド
deploy.rbの記述をすべて削除し、以下のコードを貼り付けます。
# 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などです
# どの公開鍵を利用してデプロイするか
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
<自身のアプリケーション名>
/<レポジトリ名>
<このアプリで使用しているrubyのバージョン>
<ローカルPCのEC2インスタンスのSSH鍵(pem)へのパス>
をご自身のに変えてください。変える際には、<>は削除してください。
capistranoのバージョンは、gemfile.lockに記載されています。
例gemfile.lock
# 省略
capistrano (3.11.0)
# 省略
この場合はcapistranoのバージョンは3.11.0になります。
###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を編集
capistranoでのデプロイ後は、アプリケーションのディレクトリ直下にあるcurrentディレクトリが動きます。そこで、app_pathのディレクトリ指定も一段階追加します。
また、実際に動くディレクトリであるworking_directoryの指定をcurrentにしたり、ログやpidの指定をshared以下にするなどの変更を加えます。
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"
↓↓↓↓↓↓↓ 以下のように変更 ↓↓↓↓↓↓
# ../が一つ増えている
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の設定ファイルの変更をします。
これまでは/var/www/以下のアプリケーションに対して連携を設定していたので、/var/www/アプリケーション以下のcurrent、sharedなどのディレクトリと連携するように設定を変更します。
ターミナル(EC2サーバ)
$ sudo vim /etc/nginx/conf.d/rails.conf
rails.confの既存のコードがある場合、いったん削除したほうが楽だと思うので、新しく変えていきます。
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;
}
<アプリケーション名> のなってるところほご自身のに変更してください。変更する際には<>は不要です。
Nginxの設定を変更したら、再読込・再起動をします。
ターミナル(EC2サーバ)
[ec2-user@ip-172-31-25-189 ~]$ sudo service nginx reload
[ec2-user@ip-172-31-25-189 ~]$ sudo service nginx restart
MySQLが立ち上がっていないとデプロイが失敗するので。再起動をします。
ターミナル(EC2サーバ)
[ec2-user@ip-172-31-25-189 ~]$ sudo service mysqld restart
自動デプロイを実行する前にunicornのコマンドをkillしておきます。
まず、プロセスを確認です。
ターミナル(EC2サーバ)
[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サーバ)
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill <確認したunicorn rails masterのPID(上記では17877)>
ローカルでのコードの変更が、全てmasterにpushされているかを確認してください。
以上までが自動デプロイの準備です。
##自動デプロイ
これで、ターミナル(ローカル)からコマンド一発でデプロイできるようになりました。実際に、自動デプロイを実行していきます。
ローカルのターミナルで、以下のコマンドで自動デプロイを実行します。
ターミナル(ローカルPC)
# アプリケーションのディレクトリで実行する
$ bundle exec cap production deploy
もし、途中でエラーが出て止まってしまった場合は、以下を試してください。
そのままもう一度同じコマンドを実行してみる
特に、bundle installのタスクの際に、初めて自動デプロイを実行する場合は、メモリ不足で落ちることがあります
記述ミスが無いか
手順を飛ばしていないか
bundle installなどのコマンド実行を忘れていないか確認しましょう
エラーがなければ、ブラウザからElastic IPでアクセスすると、アプリケーションにアクセスできます(:3000をつける必要はありません)。
###IPアドレスにアクセスしてもエラーが出る時
ローカル側(localhost3000)においてエラーが出ていないか
サーバー側で、/var/www/<レポジトリ名>/current/log/unicorn.stderr.logをlessまたはcatコマンドで確認しエラーが出ていないか確認する(下に行くほど最新のログです。時刻表記がUTCであることに注意してください)
ローカルでの編集のpushやpullを忘れていないか
サーバー側のmysqlやnginxの再起動を行ってみる
EC2インスタンスの再起動を行ってみる(※本番環境にてmysqlとnginxの起動も必要です。)
などが考えられます。
以上で自動デプロイの設定は完了です。
あとはローカルのターミナルからコマンド1つでデプロイし、アプリケーションを安全に更新することが可能です。
今後のアプリケーションの更新の流れは以下です。
1.アプリをローカルで更新し、リモートリポジトリにプッシュする
2.ローカルのプロジェクトのディレクトリにいる状態のターミナルでbundle exec cap production deploy を実行する