Vultr VPSにCentOS7, Ruby on Rails 6, Puma, Capistrano3でのProduction環境デプロイ

# はじめに
Amazon EC2は良いけど高いので、Railsアプリを動かすのに安いVPSを探していたところ、[Vultr](https://www.vultr.com/?ref=8493145-6G)というVPSを見つけました。月額3.50ドルから、と格安で使えますし、東京リージョン（こちらは月額$5から）もあります。

激安VPSの[Vultr](https://www.vultr.com/?ref=8478497)で[紹介者からのリンク](https://www.vultr.com/?ref=8478497)経由でアカウント登録すると＄100のクレジットをもらえるので、早速作ってみました。このキャンペーンはいつ終わるか分からないので、興味のある方は以下のリンクからアカウント登録してください。あなたはクレジットをもらえるし、私にも多少のクレジットが入るらしいので、Win-Winです。

### [**このリンク**](https://www.vultr.com/?ref=8478497)で＄100もらえます
![スクリーンショット 2020-03-09 午後7.03.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/f3fb7f23-feb3-e19f-e0c3-0ee7ff8f6e81.png)

私もリファラー経由でアカウントを作ったので＄100もらえました。これで最初にあれやこれや試すのは無料でできます。
![スクリーンショット 2020-03-09 午後7.17.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/527634e5-40b0-1fac-ecc2-6e3fe7db41b5.png)

## 使った環境
サーバOS: CentOS7 (Kernel 3.10.0-1062.12.1.el7.x86_64)
ローカルOS: macOS Catalina Version 10.15.3

Ruby 2.5.3 on rbenv
Rails 6.0.2.1
puma 4.3.3
bundler 2.1.2
Capistrano 3.12.0
MySQL 5.7.29

# 1. Vultr VPSインスタンス作成
（1）Products→Instances→＋でインスタンス追加画面を出します。
![スクリーンショット 2020-03-09 午後5.54.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/224c6f02-fd42-3aae-cd1b-8aeafb8c9ff4.png)

（2）VPSをメニューから選びます。
今回は一番安い$3.50のプランにしたかったのでアメリカ New YorkをLocationに選びました。

![スクリーンショット 2020-03-09 午後6.00.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/ee1c63c9-3812-ad03-dc9b-3b52d8c60311.png)

（3）OSは情報を検索しやすいCentOS7にしました。

![スクリーンショット 2020-03-09 午後6.01.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/67c60029-45de-3436-e29c-5a782c969d02.png)

（4）IP V6はたぶん使わないのですが、無料なので選んでおきました。

![スクリーンショット 2020-03-09 午後6.03.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/4dd7cd67-50df-5ff2-9b5d-f6143d38ff54.png)

（5）Start UpスクリプトにはGitHubのSSH公開鍵を読み込むスクリプトを仕込みました。これは後でCapistranoでデプロイする時にGitHubからコードを持って来るのに必要となります。

![スクリーンショット 2020-03-09 午後6.04.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/4ac68d90-3404-c5b5-d762-1f1accb909bf.png)

（6）SSH公開鍵を仕込んでおくと後で楽です。

![スクリーンショット 2020-03-09 午後6.06.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/c1e7ff60-aabc-7cb8-6b2f-298370151c66.png)

（7）SSH鍵の作り方
暗号強度の強い`ed25519`で作りました。以下はMacの例です。

```
$ ssh-keygen -t ed25519 -N "" -f ~/.ssh/vultr -C あなたのメルアド
Generating public/private ed25519 key pair.
Your identification has been saved in /Users/あなた/.ssh/vultr.
Your public key has been saved in /Users/あなた/.ssh/vultr.pub.

念の為、ssh-addしておきます。
$ ssh-add
Identity added: /Users/あなた/.ssh/id_rsa (/Users/あなた/.ssh/id_rsa)
```

（8）インスタンス作成
ここまで来たら`Deploy Now`ポチッと押します。

![スクリーンショット 2020-03-09 午後6.09.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/8331befd-44bf-2107-443a-99471d153090.png)

インスタンスにOSインストールが終わるまで数分待ちます。

![スクリーンショット 2020-03-09 午後6.09.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/9c756026-efed-4577-640c-7047a04d8af7.png)

OSインストールが終わって`Status`が`Running`になればインスタンス生成は完了です。

# 2. ログインアカウントの作成

（1）OSインストールが終わって`Status`が`Running`になったら`・・・`をクリックして`Server Details`画面を出します。
![スクリーンショット 2020-03-09 午後6.12.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/21afb2b8-f8ca-a49e-f71d-5112a2b8d0cb.png)

（2）画面右上の`View Console`アイコンをクリックしてコンソールを出します。
![スクリーンショット 2020-03-09 午後6.16.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/e00c4d5f-ab07-fe8b-16bc-f28d6a3b3f4a.png)
![スクリーンショット 2020-03-09 午後6.16.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/1aad2c86-e072-4236-df57-b29d0c61b000.png)

（3）コンソールが出たら`Server Details`画面のrootパスワードでrootログインします。
![スクリーンショット 2020-03-09 午後6.17.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/d7aca00d-f544-c3d8-7be0-f447ddbc6672.png)
![スクリーンショット 2020-03-09 午後6.19.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/251f4df5-e348-ccf9-f4de-393cd995208f.png)

（4）後でCapistranoで使うアカウントを追加します。

```ruby:Vultrのコンソール
# adduser deploy
# passwd deploy
# gpasswd -a deploy wheel
# mkdir /home/deploy/.ssh
# chmod 700 /home/deploy/.ssh
# vi /home/deploy/.ssh/authorized_keys
> 公開鍵を貼り付ける（Macで作った/Users/あなた/.ssh/vultr.pubの中身を貼り付けます。）

# chmod 600 /home/deploy/.ssh/authorized_keys
# chown -R deploy:deploy /home/deploy/.ssh/
```

（5）SSHでログインできるか確認する。

```ruby:Macのターミナル
$ ssh -i ~/.ssh/vultr deploy@あなたのVPSのIPアドレス
deploy@あなたのVPSのIPアドレス's password: さっき作ったdeployアカウントのパスワード
Last login: Mon Mar  9 22:25:20 2020 from あなたのLocalマシンのFQDN
-bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory
[deploy@vultr ~]$ 　ログインできました。
```

# 3. SSHの基本設定
いちいちパスワードを入れるのは面倒だし、Capistranoでパスワード入力をスキップできるようにsshd構成を変更します。

(1)ローカルマシンからSSHログイン

```ruby:Macのターミナル
$ ssh -i ~/.ssh/vultr deploy@あなたのVPSのIPアドレス
```

(2)VPSにログイン後の作業

```ruby:VPSにログインして作業
$ sudo vi /etc/ssh/sshd_config

> 以下を変更
PermitRootLogin no         # rootのリモートログインを不許可
PasswordAuthentication no  # パスワード無しでSSHログイン
ClientAliveInterval 10     # SSHがタイムアウトしないように10秒毎にAlive確認
ClientAliveCountMax 6      # 上記を最大6回（つまり60秒）繰り返す

> 変更を反映
$ sudo systemctl reload sshd
```

(3)ローカルマシンからパスワード無しでSSHログイン

```ruby:Macのターミナル
$ ssh -i ~/.ssh/vultr deploy@あなたのVPSのIPアドレス
```

もし、間違ってリモートからSSHできなくなってしまった時は`View Console`アイコンをクリックしてコンソールを出してrootでログインし、復旧させる。

# 4. sudoの設定
deployユーザーにパスワードなしでsudoコマンドを実行できるように設定しました。お好みで設定しても、しなくても良いと思います。

(1)visudoコマンドでsudo権限を付与します。

```ruby:visudoを実行
$ sudo visudo
```

```ruby:visudo
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
deploy  ALL=(ALL)       ALL  <=この行を追加

## Same thing without a password
# %wheel        ALL=(ALL)       NOPASSWD: ALL
%deploy         ALL=(ALL)       NOPASSWD: ALL  <=この行を追加
```

deployグループのユーザーはパスワード無しでsudoできます。

```ruby:一応確認
[deploy@vultr ~]$ sudo whoami
root
```

パスワードを聞かれずに`whoami`できました。

# 5. Firewallの設定

```
$ sudo yum remove -y firewalld
$ sudo yum install -y iptables-services
$ sudo systemctl start iptables
$ sudo systemctl enable iptables
$ sudo /usr/libexec/iptables/iptables.init save
$ sudo vi /etc/sysconfig/iptables

> Web用に８０と433をあける。
# Generated by iptables-save v1.4.21 on Tue Mar 10 01:44:41 2020
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [17:1954]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT  <=この行を追加
-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT  <=この行を追加
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
# Completed on Tue Mar 10 01:44:41 2020

> 上記の変更を反映させます。
$ sudo systemctl reload iptables
```

# 6. Swap領域の設定
今回作成したインスタンスはメモリが0.5GBと少なく、メモリを多く必要とするRailsではメモリ不足になる可能性が高いので、Swap領域を設定します。ページングが多発するとパフォーマンスが悪くなりますが、その際はVultrのプランをUpgradeすれば良いです。（ただし、一度UpgradeするとDowngradeはできません。）　今回は4GBと多めにSwap領域を確保します。

```ruby:rootにsuして実行
[deploy@vultr ~]$ su
Password: 
[root@vultr deploy]# dd if=/dev/zero of=/swapfile bs=1M count=4096
4096+0 records in
4096+0 records out
4294967296 bytes (4.3 GB) copied, 8.98244 s, 478 MB/s
[root@vultr deploy]# mkswap /swapfile
Setting up swapspace version 1, size = 4194300 KiB
no label, UUID=5b48c0e8-9f76-4cb1-8826-1063a95e51eb
[root@vultr deploy]# swapon /swapfile
swapon: /swapfile: insecure permissions 0644, 0600 suggested.
# vi /etc/fstab 

> 以下を一番下に追加
/swapfile swap swap defaults 0 0

> /etc/fstabの変更を保存したら、自動マウントされるか試すためにサーバを再起動する。
[root@vultr deploy]# reboot
```

再起動後、`top`コマンドでSwap領域を確認。

```
[deploy@vultr ~]$ top

top - 01:55:14 up 0 min,  1 user,  load average: 0.00, 0.00, 0.00
Tasks:  68 total,   2 running,  66 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.3 us,  0.0 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :   498648 total,   351784 free,    59388 used,    87476 buff/cache
KiB Swap:  4194300 total,  4194300 free,        0 used.   417552 avail Mem 
```

# 7. MySQLインストールと構成
DBはお好きなものを使えば良いと思いますが、私のローカルMacはMySQL5.7が入っているので同じバージョンを入れました。MySQL8が2倍速いという話もあり、試したのですがRails6だとmysqlクライアントを動かすのが素直に行かず、面倒臭いので5.7にしました。

## (1)MySQLインストール
DefaultでMariaDBが入っているのでアンインストールしてからMySQLをインストールします。

```ruby:インストール作業
# yum -y remove mariadb-libs
# rm -rf /var/lib/mysql/
# yum -y localinstall http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm

> バージョンを確認
# yum info mysql-community-server
Failed to set locale, defaulting to C
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: centos.mirror.constant.com
 * extras: centos.mirror.constant.com
 * updates: centos.mirror.constant.com
mysql-connectors-community                                                                          | 2.5 kB  00:00:00     
mysql-tools-community                                                                               | 2.5 kB  00:00:00     
mysql57-community                                                                                   | 2.5 kB  00:00:00     
(1/3): mysql-connectors-community/x86_64/primary_db                                                 |  53 kB  00:00:00     
(2/3): mysql57-community/x86_64/primary_db                                                          | 198 kB  00:00:00     
(3/3): mysql-tools-community/x86_64/primary_db                                                      |  69 kB  00:00:00     
Available Packages
Name        : mysql-community-server
Arch        : x86_64
Version     : 5.7.29  <=5.7の現時点での最新版
Release     : 1.el7
Size        : 175 M
Repo        : mysql57-community/x86_64
Summary     : A very fast and reliable SQL database server
URL         : http://www.mysql.com/

> インストールしてmysqldの起動と自動起動有効化
# yum -y install mysql-community-server mysql-devel
# systemctl enable mysqld.service
# systemctl start mysqld.service
```

## (2)MySQL構成

```ruby:Config作業
# cat /var/log/mysqld.log | grep "A temporary password"

> 表示されるパスワードをメモ
> 上記のMySQL rootのパスワードを使ってインストール。いろいろ聞かれるが、基本的に全部YesでOK。

# mysql_secure_installation

# vi /etc/my.cnf

> 以下追記
character-set-server = utf8
default_password_lifetime = 0
```

## (3)MySQLユーザー作成
Railsで使うDBを作り、DBユーザーを作成してGrantします。

```ruby:DB&ユーザー作成
# mysql -u root -p

mysql> CREATE DATABASE あなたのアプリ名_production;
mysql> CREATE USER 'DBユーザ名'@'localhost' IDENTIFIED BY 'パスワード';
mysql> GRANT ALL PRIVILEGES ON あなたのアプリ名_production.* TO 'DBユーザ名'@'localhost';
mysql> FLUSH PRIVILEGES;
mysql> exit;
```

# 8. Rubyインストールと基本構成
Rubyもいろいろバージョンがあるが、新し過ぎるバージョンはRails6側の各種Gemが未対応だったりとバージョン依存関係が面倒臭くなるので、Mac上のRailsアプリで動作が確認できている2.5.3を使うことにした。もちろん、rbenvも使う。

Rubyのインストール（`rbenv install 2.5.3`）のところは安いプランだとパフォーマンスが悪いので結構時間がかかる。インストールには時間の余裕を持ってやった方が良い。また、途中でSSHがタイムアウトしないように前述のタイムアウトの設定を確認してからやること。

## (1)Rubyのインストール
```
# yum -y install git gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel bzip2
# cd /usr/local
# git clone git://github.com/sstephenson/rbenv.git rbenv
# git clone git://github.com/sstephenson/ruby-build.git rbenv/plugins/ruby-build

# vi /etc/profile.d/rbenv.sh

> 以下を記述してパスを通しておく。
export RBENV_ROOT="/usr/local/rbenv"
export PATH="${RBENV_ROOT}/bin:${PATH}"
eval "$(rbenv init --no-rehash -)"

# source /etc/profile.d/rbenv.sh
# rbenv install 2.5.3  <=これが時間がかかる。
# rbenv global 2.5.3
# rbenv rehash
```

## (2)bundlerのインストール
ローカルのbundlerと同じバージョンをインストールする。

ローカルのbundlerのバージョン確認。私の環境は2.1.2であった。

```ruby:ローカルのMacターミナル
$ gem list bundler

*** LOCAL GEMS ***

bundler (2.1.2, default: 1.17.3)
capistrano-bundler (1.6.0)
```

bundlerインストール

```ruby:Vultrターミナル
# gem install bundler -v 2.1.2
```

Railsで必要となるNodejsをインストールしておく。

```ruby:Vultrターミナル
# vi /etc/environment

> 次の2行を追加
LANG=en_US.utf-8
LC_ALL=en_US.utf-8

> EPELリポジトリからNode.jsをインストール
# yum install epel-release
# yum install nodejs npm
# npm install -g n
# n stable　　#=> 安定版インストール

  installing : node-v12.16.1
       mkdir : /usr/local/n/versions/node/12.16.1
       fetch : https://nodejs.org/dist/v12.16.1/node-v12.16.1-linux-x64.tar.xz
   installed : v12.16.1 to /usr/local/bin/node
      active : v6.17.1 at /bin/node

> nodejsのバージョン確認
# node -v
v6.17.1
```

Yarnもインストールしておく。

```
# npm install -g yarn
/usr/bin/yarn -> /usr/lib/node_modules/yarn/bin/yarn.js
/usr/bin/yarnpkg -> /usr/lib/node_modules/yarn/bin/yarn.js
/usr/lib
└── yarn@1.22.4 
```

# 9. Nginxのインストール

```ruby:Vultrターミナル
> リポジトリの準備
# vi /etc/yum.repos.d/nginx.repo

> nginx.repoは空のはずなので、以下を追加。
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=1

> インストール実行
# yum -y update
# yum -y install nginx
# systemctl start nginx
# systemctl enable nginx

プロセス確認
# ps aux|grep nginx
root     16535  0.0  0.1  46456   972 ?        Ss   02:48   0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nginx    16536  0.0  0.3  46848  1664 ?        S    02:48   0:00 nginx: worker process
```

動作確認のため、ブラウザでVultr VPSのIPアドレスにアクセス。以下の画面が出ればNginxのインストールとFirewallの変更は成功している。

![スクリーンショット 2020-03-09 午後11.08.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/bc1d94c8-aa57-9fd2-f49f-a888a21e9365.png)

# 10. 新しくRailsアプリをRubyMineで生成
今回Vultrで動かすアプリをローカルのMac側で新規に生成して準備しておく。説明は不要と思うが、作ったアプリは以下の画面の通り。

Rails 6.0.2.1
Ruby 2.5.3

## (1) rails new

![スクリーンショット 2020-03-14 午後5.00.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/a1cae107-e86f-fdbb-2e0c-7af78cf3ca67.png)

![スクリーンショット 2020-03-14 午後5.01.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/8d3fe6f2-ff3a-dd18-7b13-fc183c76f603.png)

## (2) bundle install
Rails 6にしてから依存関係は微妙に面倒臭くなった。Rails newしたらGemをクリーンインストールできるか確認してからアプリを作った方がいいね。Gemのインストール時は明示的にPathを指定した方が良い。

```
$ bundle clean --force
$ bundle install --path vendor/bundle

An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2
```

ｍacOS Catalina V10.15.3 ではお馴染みのエラー。対処方法は次の通り。

```
$ brew info openssl@1.1
openssl@1.1: stable 1.1.1d (bottled) [keg-only]
...
For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
...
```

上記ののpathを`bundle config`で設定してから`bundle install`を再実行。

```
$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
$ bundle install --path vendor/bundle
```

## (3) database.ymlを修正
ローカルのMacのMySQLに接続するようdatabase.ymlを修正。

```ruby:config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('DATABASE_USER', '') %>
  password: <%= ENV.fetch('DATABASE_PASSWORD', '') %>
  host: <%= ENV.fetch('DATABASE_HOST', '') %>

development:
  <<: *default
  database: Capistrano3ToVultr_development

test:
  <<: *default
  database: Capistrano3ToVultr_test

production:
  <<: *default
  database: Capistrano3ToVultr_production
  username: Capistrano3ToVultr
  password: <%= ENV['CAPISTRANO3TOVULTR_DATABASE_PASSWORD'] %>
```

## (4) msgpackをインストール
rakeを使う前にmsgpackをインストール

```
$ gem install msgpack
```

## (5) db:create & migration
DB作成してマイグレーション

```ruby:LocalのMacターミナル
$ rake db:create
Created database 'Capistrano3ToVultr_development'
Created database 'Capistrano3ToVultr_test'
$ rake db:migrate
```

## (6) webpackerをインストール

```
$ rails webpacker:install
✨  Done in 10.47s.
Webpacker successfully installed 🎉 🍰
```

## (7) Railsアプリを起動してブラウザで確認。

![スクリーンショット 2020-03-14 午後5.58.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/a745fc67-69a4-6cb2-aa8f-0ced4fada138.png)


# 11. RailsアプリをGitHubにPush
## (1) GitHubに新規にリポジトリを作成

![スクリーンショット 2020-03-14 午後6.05.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/5112fdd6-40eb-a10f-9dc5-0b62ef49b17f.png)

リポジトリを作成すると次のような画面になる。

![スクリーンショット 2020-03-14 午後6.09.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/5b364a1a-5b7d-08f6-b3d5-5972e547ef00.png)


## (2) RailsアプリのディレクトリでGitを初期化
上記の画面の`create a new repository on the command line`に従って初期化する。

```
$ cd アプリのroot directory
$ git init
Reinitialized existing Git repository in /Users/あなたのRailsディレクトリ/Capistrano3ToVultr/.git/
```

## (3) `.gitignore`にGitHubにPushしたくないファイルを指定

```ruby:私の.gitignoreの例
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle
/vendor/bundler/

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore uploaded files in development.
/storage/*
!/storage/.keep

/public/assets
.byebug_history

# Ignore master key for decrypting credentials and more.
/config/master.key
/config/settings.local.yml
/config/settings/*.local.yml
/config/environments/*.local.yml

/public/packs
/public/packs-test
/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity

.pryrc
.DS_Store
```

## (4) Git Commit & Push to GitHub
GitHubにpushして入れる。

```ruby:pushしてGitHubに入れる
$ cd アプリのroot directory
$ git add .
$ git commit -m "first commit"  <=好きなコメントでOK
$ git remote add origin git@github.com:あなたのリポジトリ名.git
$ git push -u origin master
```

Push後、GitHubの該当リポジトリに以下のようにファイルが入っていればOK。
![スクリーンショット 2020-03-09 午後11.51.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/55d19de0-aaa9-6e3e-018a-b31dd8c74cf5.png)

## (5) GitHubにデプロイ用ユーザーのSSH公開鍵を登録
`Setting` > `Deploy keys` > `Add deploy keys`の順にクリック。

![スクリーンショット 2020-03-14 午後7.15.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/9edde66f-be29-8d7f-a747-fde7177d2d65.png)

以下の画面になったら、ローカルMacの`/Users/あなた/.ssh/vultr.pub`の中身を入れて`Add key`する。

![スクリーンショット 2020-03-14 午後7.18.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/1a8cf14f-7156-c257-91ff-ce7fb753b495.png)


# 12. Capistrano3を構成する
上記で新規に作ったRailsアプリのConfigを次のように変更。

## (1) Capistrano関連Gemのインストール

```ruby:Gemfile
> 以下を追加
# Use Capistrano for deployment
group :development do
  gem 'capistrano'
  gem 'ed25519'  <=参考記事4には無いが、SSH鍵でed25519を使ったので指定。
  gem 'bcrypt_pbkdf'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capistrano3-puma'
end
```

```ruby:ローカルMacターミナル
$ bundle install --path vendor/bundle
...
Bundle complete! 21 Gemfile dependencies, 77 gems now installed.
Bundled gems are installed into `./vendor/bundle`
Post-install message from capistrano3-puma:

    All plugins need to be explicitly installed with install_plugin.
    Please see README.md
```

## (2) Capistranoの構成ファイルの生成
前のステップで`bundle install`したら`cap install`を実行してCapfileを生成する。VultrにはProduction環境だけ入れるので、Productionの分しか作らない。

```ruby:ローカルMacターミナル
$ bundle exec cap install STAGES=production
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified
```

ここで、`{Rails root}/Capfile`と`config/deploy.rb`が生成されている事を確認する。

## (3) Capistranoの構成
上記で生成された`Capfile`と`deploy.rb`を編集する。

```ruby:Capfile
# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

# Load the SCM plugin appropriate to your project:
#
# require "capistrano/scm/hg"
# install_plugin Capistrano::SCM::Hg
# or
# require "capistrano/scm/svn"
# install_plugin Capistrano::SCM::Svn
# or
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#   https://github.com/capistrano/passenger
#
# require "capistrano/rvm"
require "capistrano/rbenv"
# require "capistrano/chruby"
require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano/puma'
install_plugin Capistrano::Puma
install_plugin Capistrano::Puma::Nginx

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
```

```ruby:config/deploy/production.rb
server 'VultrサーバーのIPアドレス', user: "deploy", roles: %w{app db web}
```

```ruby:config/deploy.rb
# config valid for current version and patch releases of Capistrano
lock "~> 3.12.0"

server 'VultrサーバーのIPアドレス', port: 22, roles: [:app, :web, :db], primary: true
set :application,     "あなたのアプリ名"
set :repo_url,        "git@github.com:あなたのGitHubリポジトリ名.git"
set :user,            'deploy'
set :ssh_options,     {
  forward_agent: true,
  user: fetch(:user),
  keys: %w(~/.ssh/vultr)
}

# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# Default deploy_to directory is /var/www/my_app_name
set :deploy_to, "/var/www/#{fetch(:application)}"

# Default value for :format is :airbrussh.
# set :format, :airbrussh

# You can configure the Airbrussh format using :format_options.
# These are the defaults.
# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto

# Default value for :pty is false
# set :pty, true

# Default value for :linked_files is []
# append :linked_files, "config/database.yml"

# Default value for linked_dirs is []
# append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }

# Default value for local_user is ENV['USER']
# set :local_user, -> { `git config user.name`.chomp }

# Default value for keep_releases is 5
# set :keep_releases, 5

# Uncomment the following to require manually verifying the host key before first deploy.
# set :ssh_options, verify_host_key: :secure

# rbenv
set :rbenv_type, :system
set :rbenv_ruby, File.read('.ruby-version').strip
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} #{fetch(:rbenv_path)}/bin/rbenv exec"

# bundler
append :linked_dirs, '.bundle'
set :bundle_jobs, 2  <=一番低いスペックのVultrプランなので2にした。Defaultは4。

append :linked_files, "config/master.key"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets"
```

Capistranoはdeployユーザー権限で実行するので、Vultrに作ったVPSの`/home/deploy/.bash_profile`を編集しておく。

```ruby:/home/deploy/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
	. ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/.local/bin:$HOME/bin

export PATH

export EDITOR=vi
export DATABASE_USER="上記で作ったDBユーザー名"
export DATABASE_PASSWORD="上記で作ったDBユーザーのパスワード"
```

## (4) Vultr VPSにアプリのデプロイ先ディレクトリを準備
`deploy.rb`でデプロイ先を`set :deploy_to, "/var/www/#{fetch(:application)}"`としているので、VPSに`/var/www/Capistrano3ToVultr`ディレクトリを作成する。

```
$ ssh -i ~/.ssh/vultr deploy@Vultr VPSのIPアドレス

以下はVultr側
$ sudo su
# mkdir /var/www
# mkdir /var/www/Capistrano3ToVultr
# mkdir /var/www/Capistrano3ToVultr/shared
# mkdir /var/www/Capistrano3ToVultr/shared/config
# chown -R deploy.deploy /var/www
```

## (5) Vultr VPSにSharedディレクトリに入れるファイルをコピーしておく。
以下でrailsがリリース間で共有するリソースを定義しており、でデプロイ時に自動的に作られるが、ファイルは作られない。`config/master.key` は予めサーバのshared以下にコピーしておく。

```ruby:config/deploy.rb
append :linked_files, "config/master.key"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets"
```

```
$ scp -i ~/.ssh/vultr config/master.key deploy@VPSのIPアドレス:/var/www/Capistrano3ToVultr/shared/config/
master.key             100%   32     2.7KB/s   00:00
```

### 参考記事4に明確には書いてないけど必要だった事
- deployユーザーはパスワード無しでSSHできる事が必要。そうでないと`bundle exec cap production puma:nginx_config`でコケる。
- SSH鍵を複数のGitHubリポジトリで使い回しはできない。

## (4) Capistranoでデプロイ実行
`cap production deploy`でデプロイを実行するとGitHubからファイルを取ってきて指定した`/var/www/アプリ名`ディレクトリにデプロイされる。デプロイ前にGitHubにPushしておくこと。

```ruby:ローカルMacのRailsルートディレクトリで実行
$ bundle exec cap production deploy
00:00 git:wrapper
      01 mkdir -p /tmp
途中省略

```

デプロイが成功すればreleasesディレクトリにタイムスタンプがディレクトリ名になり、その下にファイルがデプロイされ、/var/www/Capistrano3ToVultr/currentにリンクされる。

```ruby:Vultrにログインしてデプロイを確認
[deploy@vultr releases]$ pwd
/var/www/Capistrano3ToVultr/releases
[deploy@vultr releases]$ ls -l
total 4
drwxrwxr-x 12 deploy deploy 4096 Mar 15 00:15 20200315001550
```


# 12. Nginx と Puma の連携
分かってしまえばどうと言うことはないのだが、ここでハマった。CapistranoもNginxも正常動作しているように見えた（エラーは何も出なかった）のだが、Railsの画面が出ず、Nginxの初期画面しか出なかった。原因はディレクトリやファイルのパーミッション設定とNginxとPumaの各プロセスの実行ユーザーの指定だった。これに気づくまで時間を要してしまったので、細かく書こうと思う。

## (1) Config
以下は動作している各Configである。Defaultと変えたところだけコメントを付けた。ディレクトリやファイルのパーミッション、ユーザー、グループに注意。

```ruby:/var/wwwのパーミッション
drwxr-xr-x   3 deploy deploy  4096 Mar  7 22:09 www
```

```ruby:/var/www/Capistrano3のパーミッション(Capistrano3は今回作ったRailsアプリ名）
drwxr-xr-x 5 deploy deploy 4096 Mar  8 04:55 Capistrano3
```

```ruby:/var/www/Capistrano3/shared/puma.rb
#!/usr/bin/env puma

directory '/var/www/Capistrano3/current'
rackup "/var/www/Capistrano3/current/config.ru"
environment 'production'

tag ''

pidfile "/var/www/Capistrano3/shared/tmp/pids/puma.pid"
state_path "/var/www/Capistrano3/shared/tmp/pids/puma.state"
stdout_redirect '/var/www/Capistrano3/shared/log/puma_access.log', '/var/www/Capistrano3/shared/log/puma_error.log', true

threads 0,16

bind 'unix:///var/www/Capistrano3/shared/tmp/sockets/puma.sock'

workers 0

restart_command 'bundle exec puma'

prune_bundler

on_restart do
  puts 'Refreshing Gemfile'
  ENV["BUNDLE_GEMFILE"] = ""
end
```

```ruby:/etc/nginx/nginx.conf
#user  nginx;
user  deploy; <=重要。NginxとPumaは同じユーザーでプロセスを起動する。
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*; <=RailsアプリのConfigをここでインクルードしている。
}
```

```ruby:以下のディレクトリを手動作成
drwxr-xr-x 2 root root 4096 Mar  8 16:58 sites-available
drwxr-xr-x 2 root root 4096 Mar  8 16:58 sites-enabled

[deploy@vultr sites-available]$ pwd
/etc/nginx/sites-available
[deploy@vultr sites-available]$ ls -l
total 4
-rw-r--r-- 1 deploy deploy 1614 Mar  8 17:18 Capistrano3_production

[deploy@vultr sites-enabled]$ pwd
/etc/nginx/sites-enabled
[deploy@vultr sites-enabled]$ ls -l
total 0
lrwxrwxrwx 1 root root 49 Mar  8 03:39 Capistrano3_production -> /etc/nginx/sites-available/Capistrano3_production  <=リンクを貼っただけ
```

```ruby:/etc/nginx/sites-available/Capistrano3_production
upstream puma_Capistrano3_production { 
  server unix:/var/www/Capistrano3/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
  listen 80;
  server_name あなたのVultrサーバーのIPアドレス;
  root /var/www/Capistrano3/current/public;
  try_files $uri/index.html $uri @puma_Capistrano3_production;

  client_max_body_size 4G;
  keepalive_timeout 10;

  error_page 500 502 504 /500.html;
  error_page 503 @503;

  location @puma_Capistrano3_production {
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header X-Forwarded-Proto http;
    proxy_pass http://puma_Capistrano3_production;
    # limit_req zone=one;
    access_log /var/www/Capistrano3/shared/log/nginx.access.log;
    error_log /var/www/Capistrano3/shared/log/nginx.error.log;

    # Basic認証
    auth_basic "Restricted"; <=テスト用のサーバーなのでBasic認証を付けた
    auth_basic_user_file /etc/nginx/sites-available/.htpasswd;　<=テスト用のサーバーなのでBasic認証を付けた
  }

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  location = /50x.html {
    root html;
  }

  location = /404.html {
    root html;
  }

  location @503 {
    error_page 405 = /system/maintenance.html;
    if (-f $document_root/system/maintenance.html) {
      rewrite ^(.*)$ /system/maintenance.html break;
    }
    rewrite ^(.*)$ /503.html break;
  }

  if ($request_method !~ ^(GET|HEAD|PUT|PATCH|POST|DELETE|OPTIONS)$ ){
    return 405;
  }

  if (-f $document_root/system/maintenance.html) {
    return 503;
  }
}
```

## (2) Railsアプリの動作確認
### Puma起動

```ruby:ローカルのMacからcapコマンドでPuma起動
$ bundle exec cap production puma:status
00:00 puma:status
      WARN  Puma not running
$ bundle exec cap production puma:start
00:00 puma:start
      using conf file /var/www/Capistrano3/shared/puma.rb
      01 RBENV_ROOT=/usr/local/rbenv /usr/local/rbenv/bin/rbenv exec bundle exec puma -C /var/www/Capistrano3/shared/puma.rb --daemon
      01 Puma starting in single mode...
      01 * Version 4.3.3 (ruby 2.5.3-p105), codename: Mysterious Traveller
      01 * Min threads: 0, max threads: 16
      01 * Environment: production
      01 * Daemonizing...
    ✔ 01 deploy@あなたのサーバーIPアドレス 0.940s
$ bundle exec cap production puma:status
00:00 puma:status
      01 RBENV_ROOT=/usr/local/rbenv /usr/local/rbenv/bin/rbenv exec bundle exec pumactl -S /var/www/Capistrano3/shared/tmp/pids/puma.state -F /var/www/Capistrano3/shared/puma.rb status
      01 Puma is started
    ✔ 01 deploy@あなたのサーバーIPアドレス 0.576s
```

### Railsアプリ画面確認
とりあえず`rails g`で作った画面が表示された。
![スクリーンショット 2020-03-10 午前0.56.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/3dd665e7-9964-5523-9af1-6035245cb119.png)

# 終わりに
今後、本番のRailsアプリをデプロイする時にはサーバーを二重化してVultrのロードバランサを使って耐障害性を高めるなどやりたいと思う。MySQLの二重化とか、TokyoサーバーとNew Yorkサーバーでのクラスタリングとか遅延がある中でどこまで実運用に耐えられるのか試したい。一定規模のユーザーが増えて、収益が十分確保できればAWSに移行するだろうが、それまでは月々のRunning Costをいかに安く抑えるかが重要なのでVultrは強い味方と言えると思う。ただし、インフラ関係のスキルは必要だが。

### 今後やりたいこと
- SMTPの構成
- バックアップ＆リストア検証
- ロードバランサーで二重化
- MySQLクラスタリング

# 参考記事一覧
1. [CentOS7, Ruby on Rails 5, PumaでProducrion環境構築@VPS + Capistranoでのデプロイ](https://qiita.com/eiei19/items/7c08d358c82cb538def0)
- [CentOS 7に最新版のNode.jsをインストールしてみよう！](http://exrecord.net/how-to-install-latest-nodejs#NodeSource)
- [NodeSource Node.js Binary Distributions](https://github.com/nodesource/distributions/blob/master/README.md)
- [Capistrano 3によるRails 5.2 + puma + nginxのデプロイをステップバイステップで学ぶ](https://qiita.com/tkykmw/items/a34441aae142e0e41b65)