Vultr VPSにCentOS8+Rails 6+MySQL8+nginxをインストールしてCapistrano3でデプロイ

# はじめに
[CentOS7での記事](https://qiita.com/NYC-Blue/items/79621264072685d3e009)の後、CentOS8も試したので、書き留めておきます。

Vultrの一番安い10GB SSDプランだとCentOS8ではディスクが足りなくなるので25G以上をお勧めします。以下の手順はRubyだけ古いバージョンですが、これはローカルのアプリが ruby 2.5.3 で動いているためです。2.7でも手順は同じでOKと思います。（未検証）

激安VPSの[Vultr](https://www.vultr.com/?ref=8478497)で[紹介者からのリンク](https://www.vultr.com/?ref=8478497)経由でアカウント登録すると＄100のクレジットをもらえるので、CentOS7, 8, MySQL 5.7, 8などの組み合わせをいろいろ試しました。このキャンペーンはいつ終わるか分からないので、興味のある方は以下のリンクからアカウント登録してください。あなたはクレジットをもらえるし、私にも多少のクレジットが入るらしいので、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)

# 0. OS基本設定

前回の[CentOS7での記事](https://qiita.com/NYC-Blue/items/79621264072685d3e009)の1〜6までを行って、ユーザー作成やSwap領域の設定などをやっておきます。

# 1. MySQL 8 をインストール

```
$ sudo su

Unistall MariaDB
# yum -y remove mariadb-libs
# rm -rf /var/lib/mysql/

Install MySQL 8
# yum -y install @mysql
# systemctl start mysqld
# systemctl enable --now mysqld
# systemctl status mysqld
```

```
# mysql_secure_installation

Securing the MySQL server deployment.

Connecting to MySQL using a blank password.

VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?

Press y|Y for Yes, any other key for No: y

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2
Please set the password for root here.

New password: 

Re-enter new password: 

Estimated strength of the password: 100 
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done! 
```

# 2. Ruby V2.5.3 をインストール

```
# yum groupinstall "Development Tools"
# yum -y install libyaml-devel
# yum install -y openssl-devel readline-devel zlib-devel
# 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
```

# 3. bundler 2.1.2 をインストール
自分の開発環境のBundlerが2.1.2なのでそれに合わせたが、バージョンは開発環境に合わせる。

```
# gem install bundler -v 2.1.2
```

# 4. node.js をインストール

```
# yum install -y nodejs
# node --version
v10.19.0
```

# 5. yarn をインストール

```
# npm install -g yarn
```

# 6. Nginx stable最新版をインストール
## (1)yum-utils をインストール

```
# yum install yum-utils
```

```ruby:/etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
```

## (2) インストール

```
# cat /etc/redhat-release
CentOS Linux release 8.1.1911 (Core) 

# yum --disablerepo=AppStream install -y nginx

Basic認証を使いたいのでhttpd-toolsもインストール
# yum install httpd-tools

```

## (3) 動作確認

```
nginx起動
$ sudo nginx

nginx自動起動設定
# systemctl enable nginx
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.

動作状態確認
# systemctl status nginx
● nginx.service - nginx - high performance web server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; vendor preset: disabled)
   Active: active (running) since Sun 2020-03-15 17:14:15 UTC; 5s ago
     Docs: http://nginx.org/en/docs/
  Process: 3413 ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf (code=exited, status=0/SUCCESS)
 Main PID: 3414 (nginx)
    Tasks: 2 (limit: 5066)
   Memory: 2.0M
   CGroup: /system.slice/nginx.service
           ├─3414 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
           └─3415 nginx: worker process

Mar 15 17:14:14 vultrguest systemd[1]: Starting nginx - high performance web server...
Mar 15 17:14:15 vultrguest systemd[1]: nginx.service: Can't open PID file /var/run/nginx.pid (yet?) after start: No such file or directory
Mar 15 17:14:15 vultrguest systemd[1]: Started nginx - high performance web server.
```

## (4) ブラウザでアクセス

![スクリーンショット 2020-03-15 午後1.17.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/c0b65b16-448e-95aa-7b40-030ad076e01c.png)

# 7. snapshot
ここまででクリーンインストールが終わったので、他の作業前にスナップショットを取得しておく。

![スクリーンショット 2020-03-15 午後1.21.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/32efd174-1e01-2bcc-ddd2-e8d5435e1944.png)

# 8. Rails 6 をインストール
## (1) Rails6のための事前準備

```
# yum install mysql-devel
# gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'
# gem install msgpack

```

## (2) Rails 6 をインストール

```
# gem install rails -v 6.0.2.1
...
Done installing documentation for concurrent-ruby, i18n, thread_safe, tzinfo, zeitwerk, activesupport, rack, rack-test, mini_portile2, nokogiri, crass, loofah, rails-html-sanitizer, rails-dom-testing, builder, erubi, actionview, actionpack, activemodel, activerecord, globalid, activejob, mini_mime, mail, actionmailer, nio4r, websocket-extensions, websocket-driver, actioncable, mimemagic, marcel, activestorage, actionmailbox, actiontext, thor, method_source, railties, sprockets, sprockets-rails, rails after 87 seconds
40 gems installed
```

# 9. Test rails app
動作検証用に適当なアプリを作る。Capistranoでローカル環境からデプロイする場合はこの動作検証はスキップして構わない。

## (1) directory preparation

```
$ sudo su
[root@vultrguest var]# mkdir /var/www
[root@vultrguest var]# mkdir /var/www/myapp
[root@vultrguest var]# chown -R deploy.deploy /var/www
```

## (2) Gemfile preparation

```
$ bundle init
Writing new Gemfile to /var/www/myapp/Gemfile

$ vi Gemfile
```

```ruby:/var/www/myapp/Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails"  <= remove "#" here
```

## (3) rails new

```
$ bundle install --path vendor/bundle
$ bundle exec rails new . -B -d mysql --skip-test
$ bundle install --path vendor/bundle
$ rails webpacker:install
...
Webpacker successfully installed 🎉 🍰

$ rails s
=> Booting Puma
=> Rails 6.0.2.1 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.3 (ruby 2.5.3-p105), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop
```

## (4) telnetで確認
前提条件　：Port 3000が開いている事

```
$ yum list installed | grep telnet

もしtelnetがインストールされていなければインストール
$ sudo yum -y install telnet telnet-server
```

```
$ telnet 127.0.0.1 3000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
GET / HTTP/1.1   <=Enterを2回押す事。

HTTP/1.1 403 Forbidden
Content-Type: text/html; charset=UTF-8
Content-Length: 3102

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Action Controller: Exception caught</title>
  <style>
    body {
      background-color: #FAFAFA;
....
</body>
</html>
Connection closed by foreign host.
```

これでPumaが動いていることは確認できました。外部からブラウザでアクセスするにはnginxとの連携の設定が必要です。

## (5) Snapshot

一旦、ここまででスナップショットを取っておきます。

# 10. Capistrano3でデプロイ
今度はローカルの開発環境で作ったアプリをCapistrano3でデプロイしてみます。

### 前提条件
- GitHubにSSH公開鍵でローカルからPushできていること。


## (1) Vultr VPSにアプリのデプロイ先ディレクトリを準備

```
$ sudo su
# mkdir /var/www
# mkdir /var/www/myapp
# mkdir /var/www/myapp/shared
# mkdir /var/www/myapp/shared/config

# adduser www
# chown -R www:www /var/www
# chown -R deploy /var/www/myapp
# gpasswd -a deploy www
# gpasswd -a nginx www
```

## (2) デプロイ対象外指定したファイルをコピー

```
$ scp -i ~/.ssh/vultr config/master.key deploy@SERVER_IP_ADDR:/var/www/myapp/shared/config/
$ scp -i ~/.ssh/vultr config/database.yml deploy@SERVER_IP_ADDR:/var/www/myapp/shared/config/
```

## (3) Capistrano関連Config

```ruby:Gemfileに以下を追加
# Use Capistrano for deployment
group :development do
  gem 'capistrano'
  gem 'ed25519'
  gem 'bcrypt_pbkdf'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capistrano3-puma'
end
```

#### Capfile, config/deploy.rbを生成

```ruby:ローカル環境のRails_rootで実行
$ bundle exec cap install STAGES=production
```

#### Capfile, config/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
# 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.rb
# config valid for current version and patch releases of Capistrano
lock "~> 3.12.1"

server 'Your Vultr IP ADDR', port: 22, roles: [:app, :web, :db], primary: true
set :application, 'myapp'
set :repo_url, 'git@github.YOU/myapp.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/my_app_name"
set :deploy_to, "/var/www/myapp"

# 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"
append :linked_dirs, '.bundle'
append :linked_files, "config/master.key"
append :linked_files, "config/database.yml"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets"

# 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"

set :bundle_jobs, 2      <=安いプランなので2。Defaultは4。

# debug log level
set :log_level, :debug    <=設定作業中はデバッグモードにした。
```

## (4) nginx-puma連携の事前デプロイ

#### VPS事前準備

```ruby:Vultrで作業
# mkdir /etc/nginx/sites-available
# mkdir /etc/nginx/sites-enabled
# ｃｈｇｒｐ www sites-available
# ｃｈｇｒｐ www sites-enabled
```

#### capistrano-pumaプラグインで設定ファイル自動生成
これらコマンドで、サーバ上の `/etc/nginx/sites-available` と `shared/puma.rb ` に設定ファイルが作られる。

```ruby:ローカル環境のRails_rootで実行
$ bundle exec cap production puma:nginx_config
00:00 puma:nginx_config
      Uploading /tmp/nginx_myapp_production 100.0%
      01 sudo mv /tmp/nginx_myapp_production /etc/nginx/sites-available/myapp_production
    ✔ 01 deploy@Vultr_IP_ADDR 0.200s
      02 sudo ln -fs /etc/nginx/sites-available/myapp_production /etc/nginx/sites-enabled/myapp_production
    ✔ 02 deploy@Vultr_IP_ADDR 0.223s
```

```
$ bundle exec cap production puma:config
00:00 puma:config
      Uploading /var/www/myapp/shared/puma.rb 100.0%
```

#### デプロイ実行
デプロイを実行して、pumaが起動されるところまで正常終了することを確認する。

```ruby:ローカル環境のRails_rootで実行
$ bundle exec cap production deploy
```

Tips 1:
GitHubをVultrサーバーから読み込めない場合、ローカル開発環境からGitHubにｓｓｈ接続する時に使っているSSH鍵を以下のコマンドで追加すると直る。GitHubリポジトリを再作成した場合にエラーになったら試すと良い。

```ruby:localのアプリのディレクトリで実行
ssh-add ~/.ssh/id_rsa
```

Tips 2:
OpenSSLのエラーが出た場合、 `config/master.key` で作成された `config/credentials.yml.enc` がデプロイされているか確認する。 `~/.bash_profile` に `export RAILS_MASTER_KEY="abc0123456789xyz..."` があるとRAILS_MASTER_KEYが優先されるので、キーが合わずにこのエラーになる。

```ruby:log/capistrano.log
 DEBUG [3b3d9306] 	rake aborted!
ActiveSupport::MessageEncryptor::InvalidMessage: ActiveSupport::MessageEncryptor::InvalidMessage
...
Caused by:
OpenSSL::Cipher::CipherError: 
```

## (5) nginx-puma連携の設定
nginxとpumaがソケットで連携する設定を行う。

```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:/etc/nginx/sites-available/myapp
upstream puma_myapp { 
  server unix:/var/www/myapp/shared/tmp/sockets/puma.sock fail_timeout=0;
}

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

  client_max_body_size 4G;
  keepalive_timeout 10;

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

  location @puma_myapp_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_myapp_production;
    # limit_req zone=one;
    access_log /var/www/myapp/shared/log/nginx.access.log;
    error_log /var/www/myapp/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;
  }
}
```

#### nginx設定のチェックと読み込み
nginxについては解説記事がたくさんあるのであまり細かくは書きません。

```
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

起動
$ sudo nginx

停止
$ sudo nginx -s stop

設定の再読み込み
$ sudo nginx -s reload
```

#### Basic認証

```
$ sudo htpasswd -c /etc/nginx/sites-available/.htpasswd username
New password: password
Re-type new password: password
Adding password for user username
```

#### pumaの操作

```ruby:ローカルのRails_rootで実行
起動
$ bundle exec cap production puma:start

停止
$ bundle exec cap production puma:stop

状態確認
$ bundle exec cap production puma:status
```

#### ブラウザで確認

![スクリーンショット 2020-03-14 午後5.58.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329289/e36ddec7-fb9c-9655-2ca9-b6f486920890.png)


# 参考記事
[How to Install MySQL 8.0 on CentOS 8 / RHEL 8](https://www.tecmint.com/install-mysql-on-centos-8/)
[CentOSに最新版のGitをインストール・アップデートする方法](https://www.task-notes.com/entry/20150622/1434942000)
[CentOS 8にNginx stable最新版をインストール（公式repository）](https://qiita.com/witchcraze/items/64663ec9c7981f4021d6)
[新規Railsプロジェクトの作成手順まとめ](https://qiita.com/yuitnnn/items/b45bba658d86eabdbb26)
[capistranoでいきなりgithubに繋がらなくなった時の話](https://qiita.com/ryic/items/24b3abc36f5697c945b2)