実践で学ぶRuby on rails 〜仮説千本ノック〜

プログラマーとして独立するため日々スキルアップに励んでいます。優れたプログラマは仮説を立てるのがうまい。そこを目指して仮説を立てては検証する日々です!!

AWSを使ったデプロイ〜Capistranoによる自動デプロイ〜

デプロイするには、以下のような作業が必要で、様々なコマンドを実行する必要がありますが、一度Capistranoによる自動デプロイの設定を行えば、ローカルにおいて、コマンドを一つ実行すればすべての作業が自動で実行できます。

▫️ローカルからリモートリポジトリへのpush

▫️(SSH接続した上で)EC2において実行するリモートリポジトリからのpull

▫️EC2におけるアセットコンパイル

▫️EC2におけるunicorn再起動

 

 

Capistranoをインストール

ローカルにて、Gemfileの開発環境にCapistrano関連のgem群をインストール

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

 

bundle installしたら、引き続き、ローカルにて、このコマンドを実行するとCapstrano関連のファイルが生成される。

bundle exec cap install

 

ライブラリのうちどれを読み混むのかCapfileで設定

Capsitranoは、いくつかのライブラリを読み込んで動作し、

どのライブラリを読み込むかは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 }

 ※Dir.glob(tasks/*.rake)=tasksディレクトリにある.rakeファイルを、eachで人ずつimportする的な意味。ファイルがたくさんあっても、順番にすべて処理してくれる。

 

デプロイに関する設定を行う

先ほどのcap install小窓では、デプロイに関するCapstranoの設定を記述するファイル、production.rbとstaging.rbの二つが生成されている。

 

本番環境固有の設定を行うにあたっては、production.rbに次のように記述してください。

 

なお、production.rbはconig/environment/production.rbとconfig/deploy/production.rbの二つがあるが、自動デプロイの設定は後者に記述する。

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

server = サーバーホスト名(Elastic IPのこと)

user = AWSサーバのログインユーザ名(今回はec2-user)

role = 複数サーバーがあるときに、一括で処理したい場合に役に立つ。例えば、サーバが10個あって、5個はrole: app、残りの5個別はrole:webとしていた場合、appに所属する5個に一括である処理を実行することなどができる。 一種のグルーピング。

 

production環境staging環境いずれにも共通の設定はconfig/deploy.rbに記述する。

# capistranoのバージョンを記載。固定のバージョンを利用し続け、バージョン変更によるトラブルを防止する
lock '〇.〇〇.〇'# capstranoのバージョンを記述

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

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

# releasesに保存される様々なバージョンのソース、いずれからも共通で参照するディレクトリを指定
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" }
# 自動デプロイ実行時に生成されるreleasesディレクトリ内にためておくソースの世代数(多すぎると容量消費すうるので注意) set :keep_releases, 5 # デプロイ処理が終わった後、Unicornを再起動するための記述 after 'deploy:publishing', 'deploy:restart' namespace :deploy do task :restart do invoke 'unicorn:restart' end end

 ▫️Capistranoのバージョン確認

gemfile.lockを確認。

▫️set: 名前, 値の記法

変数のようなもので、一度定義すれば、ファイル内で、名前を書けば、値を取り出すことができる。

▫️task :〇〇〇 do

capistranoでデプロイ実行時に行われるタスクはcapfileでrequireしたライブラリだが、追加でおこないたいタスクをここで記述している。

 

 

自動デプロイによるディレクトリ構造の変化

Capistranoによる自動デプロイを実行すると、アプリケーションの本番環境のディレクトリ構造が変化します。

 

重要なのは、アプリケーション名のディレクトリの下にappディレクトリなどと同じ階層に並ぶ次の三つのディレクトリです。

 

▫️releases

バックアップデータが保存されます。

デプロイされたアプリは、keep_releasesで指定した世代分、バージョンが保存されます。

 

▫️current

現在デプロイさえているアプリを格納します。

と言っても、実は、releases内の最新のものにシンボリックリンクが貼られているのが実態です。

 

▫️shared

どの世代のアプリからも共通で参照されるディレクトリが格納されています。(わざわざ世代ごとに紐つける必要がない)

log、public、tmp、vendorディレクトリなど。

 

 

自動デプロイによるディレクトリ構造の変化に伴う設定の修正

自動デプロイ前は、アプリとして動くのは、

アプリ名ディレクト

でしたが、

 

自動デプロイ後は、

アプリ名ディレクトリ--app

                                            --current←1階層不覚にあるこれが動く

 

また、sharedなど、新規で作成されるディレクトもあるのでunicornについての設定を記述するunicorn.rbにあるパス の修正が必要となる。

 

自動デプロイ実行前のパス設定

#unicorn.rbから見たアプリケーショんディレクトリのパス
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"

# tmp、pidsなどが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/アプリケーション名ディレクトリを参照していたが、アプリケーション名ディレクトリの中にできるcurrent、sharedディレクトリを参照することになるので、設定ファイルrails.confを変更。

 

EC2にてrails.confをvimで編集

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

 

自動デプロイ前

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;
}

 

自動デプロイ後

# upstreamとはブラウザを下流、サーバーを上流とした場合の上流。
 webサーバーのNginxからみて上流にあるappサーバーunicornに関する設定
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; }

 

設定を反映させるため、再読み込み、再起動を行う。

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

自動デプロイ前の確認事項

ここまでで、準備は整いました。

最後に、下記の3点について確認しましょう。

 

▫️MySQLを起動する

起動していないと、自動デプロイは行えません。再起動しておきましょう。

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

 

▫️unicornを停止する

unicornは自動デプロイの中で動かすので、事前に止めておきましょう。(masterプロセスのkillにより停止)

 

プロセスのpidの確認

[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)>
...

▫️Githubのリモートリポジトリが最新の状態か確認

ローカルリポジトリからpushしてない変更があれば自動デプロイ前にしておきましょう。

 

でないと、EC2がGithubのリモートリポジトリからコードをpullした場合、最新のものが反映できません。

 

 

自動デプロイ前の準備

ローカルのターミナルのアプリディレクトリに置いて、本番環境の自動デプロイを実行しましょう。

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

 

エラーが出た際には次の2点をチェックしましょう

 

▫️再度同じコマンドを実行

初めての自動デプロイは負荷がかかるので、設定に問題がなくてもエラーが生じることがあります。

 

▫️設定に誤りがないか

設定はしたけど、設定を反映させるコマンドを実行し忘れることがあるので、チェックしましょう。

 

なお、今後アプリを更新する際には、

▫️ローカルで行った変更をリモートリポジトリへpush

▫️ローカルのアプリのディレクトリにおいて自動デプロイコマンド

bundle exec cap production deployを実行する