#デプロイの自動化
これまで手動で行っていたデプロイ作業(「unicorn_railsコマンド」を使ってサーバーを立ち上げる手段)を、ローカルのターミナルからのコマンド1つで行えるようにします。そのために、「自動デプロイツール」と呼ばれるものを利用します。今回は、数ある自動デプロイツールの中でも、もっともポピュラーなCapistranoを実際に導入してみます。自動デプロイツールを利用することによって、デプロイ時に必要なコマンド操作が1回で済むようになります。
デプロイする際には以下の手順を踏みました。
「Capistrano」
を利用すればEC2サーバーにログインする必要もありません。ローカルのターミナルからのコマンド1つで、これらの作業をいっぺんに完了できます。
#Capistranoの導入準備
Capistranoを利用するためのGemをインストールしましょう。
Gemfileを以下のように編集しましょう。
(省略)
group :development, :test do
gem 'capistrano'
gem 'capistrano-rbenv'
gem 'capistrano-bundler'
gem 'capistrano-rails'
gem 'capistrano3-unicorn'
end
(省略)
ローカル環境でbundle installしましょう。
gemを読み込めたら、ローカルで下記のコマンドを打ちます。
% bundle exec cap install
すると、ファイルが生成されます。
###Capfile
「Capfile」では、Capistrano関連のライブラリのうちどれを読み込むかを指定できます。
Capistranoの機能を提供するコードはいくつかのライブラリ(Gem)に分かれています。そのため、Capistranoを動かすにはいくつかのライブラリを読み込む必要があります。
###production.rb
「production.rb」は、デプロイについての設定を書くファイルです。
GitHubへの接続に必要なsshキーの指定、デプロイ先のサーバのドメイン、AWSサーバへのログインユーザー名、サーバにログインしてからデプロイのために何をするか、といった設定を記載します。
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」によって引数としておかれた文字列が指すディレクトリが読み込まれ、その中にデプロイに際して必要な動作がひととおり記述されています。
続いて、デプロイについての設定を記載するファイルを編集します。
「config/deployフォルダ」の配下にはproduction.rbとstaging.rbの2種類のファイルが生成されています。
これらは、デプロイする環境別の設定を記述するファイルです。今回はproduction.rb、つまり本番環境のものだけ編集します。具体的には下記の内容を記述していくこととなります。
サーバーホスト名(or IPアドレス)
AWSのサーバーへログインできるユーザー名
サーバーロール(後述)
sshの設定
その他サーバーに紐づく任意の設定
それでは編集しましょう。
server '用意したElastic IP', user: 'ec2-user', roles: %w{app db web}
次は、「deploy.rb」の設定です。
こちらのファイルには、production環境、staging環境どちらにも当てはまる設定を記述することとなります。
具体的には下記のような項目があります。
アプリケーション名
gitのレポジトリ
利用するSCM
タスク
それぞれのタスクで実行するコマンド
それでは、編集しましょう。
# 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のバージョン' #このQiita通りに進めた場合、’2.6.5’ です
# どの公開鍵を利用してデプロイするか
set :ssh_options, auth_methods: ['publickey'],
keys: ['~/.ssh/ご自身のキーペア名.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に記載されています。
(省略)
capistrano (3.11.0)
(省略)
deploy.rb内の記述をよく見ると、「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によるアプリのバックアップなど、複数のディレクトリが作成されます。その中でも、とくに重要な3つのファイルについて解説します。
releasesディレクトリ
「releasesディレクトリ」には、Capistranoを通じてデプロイされたアプリがひとまとめにされます。
ここに過去分のアプリが残っていることにより、デプロイ時に何か問題が発生しても前のバージョンに戻れます。
そして、その過去分の保存数を指定しているのがdeploy.rbの「set :keep_releases」の記述となります。今回は5つ、過去のバージョンを保存するよう設定しました。
###currentディレクトリ
「currentディレクトリ」には、releasesフォルダの中で一番新しいものが自動的にコピーされているような状態になっています。
つまり、「現在デプロイされている内容=current内の内容」ということになります。
###sharedディレクトリ
バージョンが変わっても共通で参照されるディレクトリが格納されるディレクトリです。具体的には、log、public、tmp、vendorディレクトリが格納されます。
#Unicornの設定ファイルを編集
手動デプロイの時と比べ、自動デプロイ時にはRailsのアプリケーションのディレクトリが1段階深くなっています。そのため、数ヶ所変更を加える必要があります。
(編集前)⬇️
#サーバ上でのアプリケーションコードが設置されているディレクトリを変数に入れておく
app_path = File.expand_path('../../', __FILE__)
#アプリケーションサーバの性能を決定する
worker_processes 1
#アプリケーションの設置されているディレクトリを指定
working_directory app_path
#Unicornの起動に必要なファイルの設置場所を指定
pid "#{app_path}/tmp/pids/unicorn.pid"
#ポート番号を指定
listen 3000
#エラーのログを記録するファイルを指定
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
#アプリケーションの設置されているディレクトリを指定
working_directory "#{app_path}/current" # 「current」を指定
#Unicornの起動に必要なファイルの設置場所を指定
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" # 「shared」の中を参照するよう変更
(省略)
#Nginxの設定ファイルを編集
以下のコマンドを実行しエディタを立ち上げましょう。
$ sudo vim /etc/nginx/conf.d/rails.conf
vimコマンドで編集するので、まずは「i」と打ち込んで入力モードに切り替えます。
upstream app_server {
# Unicornと連携させるための設定
server unix:/var/www/アプリケーション名/shared/tmp/sockets/unicorn.sock;
}
# {}で囲った部分をブロックと呼ぶ。サーバの設定ができる
server {
# このプログラムが接続を受け付けるポート番号
listen 80;
# 接続を受け付けるリクエストURL ここに書いていないURLではアクセスできない
server_name Elastic IP;
# クライアントからアップロードされてくるファイルの容量の上限を2ギガに設定。デフォルトは1メガなので大きめにしておく
client_max_body_size 2g;
# 接続が来た際のrootディレクトリ
root /var/www/アプリケーション名/current/public;
# assetsファイル(CSSやJavaScriptのファイルなど)にアクセスが来た際に適用される設定
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
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行目 「〜アプリケーション名/shared/tmp/〜」に編集
17行目 1行丸ごと追加
24行目 1行丸ごと追加
入力を終えたら「escキー」→「:wq」の順で実行し、保存しましょう。
Nginxの設定を変更したら、忘れずに再読込・再起動をしましょう。
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl reload nginx
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl restart nginx
Nginxの設定ファイルの編集は以上です。
#データベースの起動を確認
データベースが立ち上がっていないとデプロイが失敗します。以下のコマンドでデータベースの状態を確認しておきましょう。
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl status mariadb
以下のように、「active」と表示されれば起動しています。
● mariadb.service - MariaDB database server
Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disabled)
Active: active (running) since 金 2020-07-17 03:46:51 UTC; 8s ago
Process: 17044 ExecStartPost=/usr/libexec/mariadb-wait-ready $MAINPID (code=exited, status=0/SUCCESS)
Process: 17008 ExecStartPre=/usr/libexec/mariadb-prepare-db-dir %n (code=exited, status=0/SUCCESS)
Main PID: 17043 (mysqld_safe)
CGroup: /system.slice/mariadb.service
├─17043 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
└─17206 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --log-error=/var/log/mariadb/mariadb.log --pid-file=/var/run/mariadb/mariadb.pid --so...
万が一、「active」になっていない場合はsudo systemctl start mariadbを実行しましょう。
自動デプロイをする前に、unicornのプロセスをkillする必要があります。
というのも、すでにunicornのサーバーが立ち上がっている状態で自動デプロイをすると、二重でサーバーを立ち上げることになるためです。(自動デプロイもサーバーを立ち上げる役割を担っています)
[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
# 上記の例だと「7877」
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill プロセス番号
ローカルでのコードの変更が、すべてmasterにpushされていることを確認しておきましょう。
以上で自動デプロイの準備は完了です。
最後に自動デプロイを実行しましょう。
#自動デプロイ
ローカルのターミナルに戻り、以下のコマンドで自動デプロイを実行しましょう。
# アプリケーションのディレクトリで実行しましょう
% bundle exec cap production deploy
はじめての自動デプロイでは「bundler install」のタスクで時間がかかることがあります。5分経っても動かない場合は「EC2インスタンスの再起動」を実行しましょう。
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl restart mariadb
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl restart nginx
AWSこれにて投稿終了です。