Capistranoによるデプロイの自動化
Capistrano
Capistranoは、自動デプロイツールと呼ばれるものの一種。
自動デプロイツールのメリット
自動デプロイツールを利用することによって、デプロイ時に必要なコマンド操作が1回で済むようになる。これにより、手動でデプロイする場合に起こりがちな下記の問題を解消することができる。
1. コマンドの打ち間違い、手順の間違いが発生する可能性がある
2. 手順が多く、煩わしい
さて、デプロイする際には以下の手順を踏んだ。
このためにサーバにログインしたり、何回もコマンドを打っているはず。
Capistranoを利用すれば、サーバにログインする必要すらない。ローカルのターミナルからのコマンド1つで、これらの作業をいっぺんに完了することができる。
また、一度Capistranoによるデプロイが成功してしまえば、すくなくとも打ち間違いによるデプロイの失敗は起こらなくなる。
Capistranoの導入準備
Capistranoを利用するためのGemをインストールする
必要なGemをインストールし、Capistranoを動かすために必要な設定ファイルを生成する。
Gemfileを編集し、Capistrano関連のGemをインストール
1 2 3 4 5 6 7 |
group :development, :test do
gem 'capistrano'
gem 'capistrano-rbenv'
gem 'capistrano-bundler'
gem 'capistrano-rails'
gem 'capistrano3-unicorn'
end
|
続いて、Gemfileを読み込む。
1 |
bundle install
|
gemを読み込めたら、下記のコマンドを打ち込む。
1 |
bundle exec cap install
|
すると、下記のようにファイルが生成される。
- アプリケーション名
- capfile
- config
- deploy
- production.rb
- staging.rb
- deploy.rb
- deploy
- lib
- capistrano
- tasks
- capistrano
利用するライブラリを指定するファイル
Capfile
Capistranoの機能を提供するコードはいくつかのライブラリ(Gem)に分かれている。そのため、Capistranoを動かすにはいくつかのライブラリを読み込む必要がある。Capfile
は、Capistrano関連のライブラリのうちどれを読み込むかを指定できる。
デプロイについての設定を書くファイル
deploy.rb、production.rb、staging.rb
Githubへの接続に必要なsshキーの指定、デプロイ先のサーバのドメイン、AWSサーバへのログインユーザー名、サーバにログインしてからデプロイのために何をするか、といった設定を記載する。
では、capistranoを動かすためのそれぞれのファイルを編集する。
Capfileの編集
Capfile
Capfileは、capistrano全体の設定ファイル。
1 2 3 4 5 6 7 8 9 |
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.rbcap install
コマンドを打つと、config/deploy配下にproduction.rbとstaging.rbの2種類のファイルが生成される。
これらは、デプロイする環境別の設定を記述するファイル。今回はproduction、つまり本番環境のものだけ編集。具体的には下記の内容を記述していく。
1 |
server '<用意したElastic IP>', user: 'ec2-user', roles: %w{app db web}
|
<用意したElastic IP>は自身のものを記述。
編集後は例えば、以下のようになる。
1 |
server '12.345.67.890', user: 'ec2-user', roles: %w{app db web}
|
deploy.rb
ここには、production環境、staging環境どちらにも当てはまる設定を記述する。
具体的には下記のような項目がある。
- アプリケーション名
- gitのレポジトリ
- 利用するSCM
- タスク
- それぞれのタスクで実行するコマンド
deploy.rbの記述をすべて削除し、以下のコードを貼り付ける。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# 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点は書き換えが必須。
- 3行目のは自身のバージョンを記述。
- 6行目の<自身のアプリケーション名>は自身のものを記述。
- 9行目の/<レポジトリ名>も同様に、自身のもの記述。
- 15行目の<このアプリで使用しているrubyのバージョン>は自身のものを確認して記述。
- 19行目の<ローカルPCのEC2インスタンスのSSH鍵(pem)へのパス>も同様に、自身のもの記述。
lock <Capistranoのバージョン>'
capistranoのバージョンは、gemfile.lockに記載されている。以下のように書かれている場合は、3.11.0
。
1 2 3 |
# 省略
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
の設定ファイルを編集する。
手動でUnicornを起動する場合に比べ、Capistranoを使った場合には、Railsのアプリケーションのディレクトリが1段階深くなっているため、数カ所変更を加える必要がある。
capistranoでのデプロイ後は、アプリケーションのディレクトリ直下にあるcurrentディレクトリが動く。そこで、app_pathのディレクトリ指定も一段階追加する。
また、実際に動くディレクトリであるworking_directoryの指定をcurrentにしたり、ログやpidの指定をshared以下にするなどの変更を加える。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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の設定ファイル
Nginxの設定ファイルも変更が必要。
これまでは/var/www/以下のアプリケーションに対して連携を設定していたので、/var/www/chat-space以下のcurrent、sharedなどのディレクトリと連携するように設定を変更する必要がある。
Nginxの設定ファイルの記述を編集
その後は、以下に従って自身の設定ファイルを書き換える
こちらの既存のコードをいったん全て削除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
upstream app_server {
server unix:/var/www/<アプリケーション名>/tmp/sockets/unicorn.sock;
}
server {
listen 80;
server_name <Elastic IPを記入>;
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;
}
|
↓↓↓↓↓↓↓ 以下のように変更する ↓↓↓↓↓↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
upstream app_server {
# sharedの中を参照するよう変更
server unix:/var/www/<アプリケーション名>/shared/tmp/sockets/unicorn.sock;
}
server {
listen 80;
server_name <Elastic IPを記入>;
# 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;
}
|
もう一度、記述すべき箇所が記述できているか確認。
- 3行目の<アプリケーション名> となっている箇所は、自身のものに変更。
- 11行目の<アプリケーション名>となっている箇所も同様に、自身のものに変更。
- 18行目の<アプリケーション名>となっている箇所も同様に、自身のものに変更。
Nginxの設定を変更したら、忘れずに再読込・再起動する。
1 2 |
[ec2-user@ip-172-31-25-189 ~]$ sudo service nginx reload
[ec2-user@ip-172-31-25-189 ~]$ sudo service nginx restart
|
MySQLの起動を確認
MySQLが立ち上がっていないとデプロイが失敗する。以下のコマンドで再起動をしておく。
1 |
[ec2-user@ip-172-31-25-189 ~]$ sudo service mysqld restart
|
unicornのプロセスをkillする
自動デプロイを実行する前にunicornのコマンドをkillしておく。
まず、プロセスを確認。
1 2 3 4 5 |
[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する。
1 2 |
ローカルでの修正を全てmasterにpushする
ローカルでのコードの変更が、全てmasterにpushされていることを確認。
以上で自動デプロイの準備は完了。
自動デプロイ
自動デプロイを実行
ローカルのターミナルに戻り、以下のコマンドで自動デプロイを実行する。
1 2 |
# アプリケーションのディレクトリで実行する
$ 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つでデプロイし、アプリケーションを安全に更新することが可能である。
今後のアプリケーションの更新の流れは以下。
1.アプリをローカルで更新し、リモートリポジトリにプッシュする
2.ローカルのプロジェクトのディレクトリにいる状態のターミナルでbundle exec cap production deploy
を実行する
これまでのように、サーバーに一度ログインしてリポジトリをプルし、、といった作業が全て自動化されるので、とても楽になる!