yuuki blog

プログラミング をアプトプットしています。

AWS (デプロイを自動化)

デプロイの自動化

 

 Capistrano

https://tech-master.s3.amazonaws.com/uploads/curriculums//6dc39833855ffc6409888b12874167b9.png

Capistrano」とは、自動デプロイツールと呼ばれるものの一種です。

ではなぜ「自動デプロイツール」を扱うのか、その理由について学びましょう。

自動デプロイツールのメリット

自動デプロイツールを利用することによって、デプロイ時に必要なコマンド操作が1回で済むようになります。これにより、手動デプロイする場合に起こりがちな下記の問題を解消できます。

手動デプロイで起こりがちな問題
・コマンドの打ち間違い、手順の間違いが発生する可能性がある
・手順が多く、煩わしい

さて、デプロイする際には以下の手順を踏みました。

https://tech-master.s3.amazonaws.com/uploads/curriculums//d41a07a4eae1f913abeff7e6621f6c4c.png

このためにサーバへログインしたり、何回もコマンドを打っているはずです。
しかし、「Capistrano」を利用すればEC2サーバーにログインする必要もありません。ローカルのターミナルからのコマンド1つで、これらの作業をいっぺんに完了できます。また、一度Capistranoによるデプロイが成功してしまえば、少なくとも打ち間違いによるデプロイの失敗は起こらなくなります。

Capistranoの導入準備

Capistranoを利用するための準備をしよう

それでは、Capistranoを使う準備をしましょう。

以下の手順で、自動デプロイの準備をしましょう

まずは、Capistranoを利用するためのGemをインストールしましょう。
Gemfileを以下のように編集しましょう。

「group :development, :test do ~ end」がすでに記述されている場合は、「gem ~」の部分だけを追記しましょう。
Gemfile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(省略)

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

(省略)

続いて、Gemfileを読み込みましょう。

 以下2つはローカルでの作業です。
ターミナル(ローカル)
1
2
# アプリケーションのディレクトリで実行しましょう
% bundle install

gemを読み込めたら、下記のコマンドを打ちます。

ターミナル(ローカル)
1
2
# アプリケーションのディレクトリで実行しましょう
% bundle exec cap install

すると、下記のようにファイルが生成されます。

  •  アプリケーション名
    •  capfile
    •  config
      • deploy
        •  production.rb
        •  staging.rb
      •  deploy.rb
    •  lib

生成されたファイルについて説明します。

 Capfile

「Capfile」では、Capistrano関連のライブラリのうちどれを読み込むかを指定できます。
Capistranoの機能を提供するコードはいくつかのライブラリ(Gem)に分かれています。そのため、Capistranoを動かすにはいくつかのライブラリを読み込む必要があります。

 production.rb

「production.rb」は、デプロイについての設定を書くファイルです。
GitHubへの接続に必要なsshキーの指定、デプロイ先のサーバのドメインAWSサーバへのログインユーザー名、サーバにログインしてからデプロイのために何をするか、といった設定を記載します。
また、「deploy.rb」「staging.rb」も同様の役割をするファイルです。

Capfileを編集しよう

続いて、Capistrano全体の設定をしている「Capfile」を編集しましょう。

Capfileを下記のように編集しましょう

Capfile
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」によって引数としておかれた文字列が指すディレクトリが読み込まれ、その中にデプロイに際して必要な動作がひととおり記述されています。

デプロイについての設定ファイルを編集しよう

続いて、デプロイについての設定を記載するファイルを編集します。

「config/deployフォルダ」の配下にはproduction.rbとstaging.rbの2種類のファイルが生成されています。
これらは、デプロイする環境別の設定を記述するファイルです。今回はproduction.rb、つまり本番環境のものだけ編集します。具体的には下記の内容を記述していくこととなります。

  • サーバーホスト名(or IPアドレス)
  • AWSのサーバーへログインできるユーザー名
  • サーバーロール(後述)
  • sshの設定
  • その他サーバーに紐づく任意の設定

それでは編集しましょう。

production.rbの一番下に、下記のように編集しましょう

config/deploy/production.rb
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を編集しましょう

 deploy.rbの記述をすべて削除し、以下のコードを貼り付けます。
 以下の部分は適宜ご自身のものに置き換えましょう。
要注意の箇所 行番号
Capistranoのバージョン 2
アプリケーション名 5
GitHubのユーザー名 , レポジトリ名 8
このアプリで使用しているrubyのバージョン 14
ご自身のキーペア名 18
config/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
# 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.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に記載されています。以下のように書かれている場合は「3.11.0」です。
【例】Gemfile.lock
1
2
3
4
5
(省略)

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つのファイルについて解説します。

https://tech-master.s3.amazonaws.com/uploads/curriculums//f74c8bc0428cc935bb48c99a08400ed9.png

 releasesディレクト

「releasesディレクトリ」には、Capistranoを通じてデプロイされたアプリがひとまとめにされます。
ここに過去分のアプリが残っていることにより、デプロイ時に何か問題が発生しても前のバージョンに戻れます。
そして、その過去分の保存数を指定しているのがdeploy.rbの「set :keep_releases」の記述となります。今回は5つ、過去のバージョンを保存するよう設定しました。

 currentディレクト

「currentディレクトリ」には、releasesフォルダの中で一番新しいものが自動的にコピーされているような状態になっています。
つまり、「現在デプロイされている内容=current内の内容」ということになります。

 sharedディレクト

バージョンが変わっても共通で参照されるディレクトリが格納されるディレクトリです。具体的には、log、public、tmp、vendorディレクトリが格納されます。

ここまで整理できたら、次は「unicornの設定ファイル」を編集します。

Unicornの設定ファイルを編集しよう

手動デプロイの時と比べ、自動デプロイ時にはRailsのアプリケーションのディレクトリが1段階深くなっています。そのため、数ヶ所変更を加える必要があります。

それでは順に編集しましょう。

以下の手順で、unicorn.rbの記述を編集しましょう

【編集前】config/unicorn.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#サーバ上でのアプリケーションコードが設置されているディレクトリを変数に入れておく
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"

(省略)

↓↓↓↓↓↓ 以下のように編集 ↓↓↓↓↓↓

【編集後】config/unicorn.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#サーバ上でのアプリケーションコードが設置されているディレクトリを変数に入れておく
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の設定ファイル」を編集しましょう。

Nginxの設定ファイルを編集しよう

Unicornの設定ファイルと同様、手動デプロイの時と比べ、自動デプロイ時にはRailsのアプリケーションのディレクトリが1段階深くなっています。そのため、数ヶ所変更を加える必要があります。

それでは編集しましょう。

 Nginxの設定ファイルを編集しましょう

以下のコマンドを実行しエディタを立ち上げましょう。

ターミナル(EC2内で実行)
1
$ sudo vim /etc/nginx/conf.d/rails.conf

次に、以下の箇所を編集しましょう。

行番号 作業内容
3 「〜アプリケーション名/shared/tmp/〜」に編集
17 1行丸ごと追加
24 1行丸ごと追加

vimコマンドで編集するので、まずは「i」と打ち込んで入力モードに切り替えます。

/etc/nginx/conf.d/rails.conf
 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
35
36
37
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ファイル(CSSJavaScriptのファイルなど)にアクセスが来た際に適用される設定
  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;
}

入力を終えたら「escキー」→「:wq」の順で実行し、保存しましょう。

そして、Nginxの設定を変更したら、忘れずに再読込・再起動をしましょう。

ターミナル(EC2内で実行)
1
2
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl reload nginx
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl restart nginx

Nginxの設定ファイルの編集は以上です。

 データベースの起動を確認しましょう

データベースが立ち上がっていないとデプロイが失敗します。以下のコマンドでデータベースの状態を確認しておきましょう。

ターミナル(EC2内で実行)
1
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl status mariadb 

以下のように、「active」と表示されれば起動しています。

ターミナル(EC2)
1
2
3
4
5
6
7
8
9
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のプロセスをkillする必要があります。
というのも、すでにunicornのサーバーが立ち上がっている状態で自動デプロイをすると、二重でサーバーを立ち上げることになるためです。(自動デプロイもサーバーを立ち上げる役割を担っています)

まずは、プロセスを確認します。

ターミナル(EC2内で実行)
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します。

ターミナル(EC2内で実行)
1
2
# 上記の例だと「7877」
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill プロセス番号

ローカルでの修正をすべてmasterにpushしましょう

ローカルでのコードの変更が、すべてmasterにpushされていることを確認しておきましょう。

以上で自動デプロイの準備は完了です。

それでは、最後に自動デプロイを実行しましょう。

自動デプロイ

これで、ローカルのターミナルからコマンド一発でデプロイできるようになりました。実際に、自動デプロイを実行しましょう。

自動デプロイを実行しましょう

ローカルのターミナルに戻り、以下のコマンドで自動デプロイを実行しましょう。

ターミナル(ローカル)
1
2
# アプリケーションのディレクトリで実行しましょう
% bundle exec cap production deploy

上記コマンドを実行し、以下の画像のようにエラーが表示されずに完了したら自動デプロイは完了です。
https://tech-master.s3.amazonaws.com/uploads/curriculums//809540b10e599864031a58c3ce6cff07.png

 はじめての自動デプロイでは「bundler install」のタスクで時間がかかることがあります。5分経っても動かない場合は「EC2インスタンスの再起動」を実行しましょう。
 EC2インスタンスの再起動後は、必ず「データベース」「Nginx」の再起動も行いましょう。
ターミナル(EC2内で実行)
1
2
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl restart mariadb 
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl restart nginx

ブラウザで確認

ブラウザからElastic IPでアクセスしましょう。

 「http://<Elastic IP>:3000/」ではないので注意しましょう。

アプリケーション修正から自動デプロイまでの流れ

アプリケーションの修正を行った際、自動デプロイをする前にやるべき事を以下の表で確認しましょう。

状況 自動デプロイ前にやること 必要性の有無
①ローカルでVSCodeを修正した場合 変更点をリモートリポジトリにcommit→pushする
(ブランチを切っている場合はmergeまで実行)
該当時のみ
②ローカルでデータベース関連の内容を修正した場合 本番環境で「rails db:drop RAILS_ENV=production」「rails db:create RAILS_ENV=production」を実行
(※実行する際、「DISABLE_DATABASE_ENVIRONMENT_CHECK=1」というオプションが必要です)
該当時のみ
③Nginxを修正した場合 「sudo systemctl restart nginx」を実行 該当時のみ
④再度自動デプロイを実行する場合
(本番環境でサーバー再起動をする場合)
一度プロセスをkillした上で「bundle exec cap production deploy」を実行 ①②の場合のみ

IPアドレスにアクセスしてもエラーが出る時

下図のように、ブラウザ上で「We’re sorry , but ~」が表示された場合、EC2のターミナルに接続しましょう。

image

自動デプロイ後は、currentディレクトリの中にproduction.logが入ってくることに注意してください。

以下のコマンドを順に入力し、エラーログを確認してみましょう。

ターミナル(EC2内で実行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 自身のアプリケーションのディレクトリに移動
[ec2-user@ip-172-31-23-189~]$ cd /var/www/アプリケーション名

# currentディレクトリに移動
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ cd current

# logディレクトリに移動
[ec2-user@ip-172-31-23-189 current]$ cd log

# logの中に入っているファイルの情報を表示
[ec2-user@ip-172-31-23-189 log]$ ls
production.log  unicorn.stderr.log  unicorn.stdout.log

# production.logの最新のログを10行分表示
[ec2-user@ip-172-31-23-189 log]$ tail -f production.log 
 production.logが出力されていない場合、「EC2のRailsを起動しよう(手動デプロイ)」で行ったGemfileのrails_12factorのコメントアウトがされていない可能性が高いので確認しましょう。

下図は、コマンドを実行した時のエラーログの例になります。

image

このログを参考にエラーが発生している箇所を探しましょう。

その他、チェックポイントは以下の通りです。

 メンターに質問をする前に必ず以下の点を確認しましょう。
 どんなエラーも、最終的には自身で解決できるようになる必要があります。メンターが対応させていただく際は、以下のチェックリストを網羅したものとして対応いたします。
チェックリスト



・ローカルでは問題なく動いているか
・ローカル→GiuHubへのpushのし忘れはないか(mergeまで確認)
GitHubからEC2への反映(git pull origin master)のし忘れはないか
・EC2サーバー側でエラーログの内容を確認し、原因を見つけたか
・カリキュラム通りの記載ができているか
・Nginxは正しく起動しているか
・EC2サーバー側の環境変数は正しく設定できているか
・EC2インスタンスの再起動を行ってみる