2022-12-17に更新

【AWS】Rails 6.0.4.8 + Puma + Nginx のデプロイ

読了目安:23分

1. 前提環境

  • Amazon Linux2
  • MySQL 8.0.29
  • Nginxインストール & 自動起動設定

image

2. MySQLクライアントをインストールする

MySQLクライアントをインストールする理由:
image
・また、ホスト指定(-hを付ける)をしないとローカルホストに接続することになる。
・ネイティブソフトウェアリポジトリ(Amazon Linux2に元々インストールされているリポジトリ)からMySQLをインストールする際は以下のコマンドでインストールできる。
・Note: ネイティブパッケージは多くの場合、最新バージョンから数世代遅れている。詳細は、『MySQL :: MySQL 8.0 リファレンスマニュアル :: 2.5.7 ネイティブソフトウェアリポジトリから MySQL を Linux にインストールする | dev.mysql.com』を参照。

# mysql = クライアント用ツール
$ sudo yum install -y mysql

・下記に記述するようにリポジトリを追加してから上記のコマンドを実行するとクライアントプログラムがインストールされる。
リポジトリを追加せずに上記のコマンドを実行すると、mariadbがインストールされる。(AmazonのLinuxディストリビューションに仕様)

2.1 MySQL Yum Repositoryを追加する

  1. Cloud9上にMySQLをセットアップする』の「MySQL Yum Repositoryを追加する」の項まで実施する。Amazon Linux2はEL7-based system(ディストリビューションが7系)であるとのことで、リポジトリはRed Hat 7系を選択。
# 最新のパッケージに差し替えた
$ sudo yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-6.noarch.rpm
  1. リポジトリが追加されているか確認
$ yum repolist enabled | grep "mysql.*-community.*"
mysql-connectors-community/x86_64   MySQL Connectors Community            141+51
mysql-tools-community/x86_64        MySQL Tools Community                     90
mysql80-community/x86_64            MySQL 8.0 Community Server               343
  1. 全てのMySQLのサブリポジトリを表示(最新のリリースシリーズ(今回で言えば8.0)がデフォルトで有効になっている仕様)
$ yum repolist all | grep mysql
mysql-cluster-7.5-community/x86_64           MySQL Cluster 7.5 Comm 無効
mysql-cluster-7.5-community-source           MySQL Cluster 7.5 Comm 無効
mysql-cluster-7.6-community/x86_64           MySQL Cluster 7.6 Comm 無効
mysql-cluster-7.6-community-source           MySQL Cluster 7.6 Comm 無効
mysql-cluster-8.0-community/x86_64           MySQL Cluster 8.0 Comm 無効
mysql-cluster-8.0-community-debuginfo/x86_64 MySQL Cluster 8.0 Comm 無効
mysql-cluster-8.0-community-source           MySQL Cluster 8.0 Comm 無効
mysql-connectors-community/x86_64            MySQL Connectors Commu 有効: 141+51
mysql-connectors-community-debuginfo/x86_64  MySQL Connectors Commu 無効
mysql-connectors-community-source            MySQL Connectors Commu 無効
mysql-tools-community/x86_64                 MySQL Tools Community  有効:     90
mysql-tools-community-debuginfo/x86_64       MySQL Tools Community  無効
mysql-tools-community-source                 MySQL Tools Community  無効
mysql-tools-preview/x86_64                   MySQL Tools Preview    無効
mysql-tools-preview-source                   MySQL Tools Preview -  無効
mysql57-community/x86_64                     MySQL 5.7 Community Se 無効
mysql57-community-source                     MySQL 5.7 Community Se 無効
mysql80-community/x86_64                     MySQL 8.0 Community Se 有効:    343
mysql80-community-debuginfo/x86_64           MySQL 8.0 Community Se 無効
mysql80-community-source                     MySQL 8.0 Community Se 無効
  1. 念のため5.7シリーズのサブリポジトリを無効にし、8.0シリーズを有効にするコマンドを打つ
$ sudo yum-config-manager --disable mysql57-community
$ sudo yum-config-manager --enable mysql80-community
  1. 8.0シリーズが有効になっているか確認
$ yum repolist enabled | grep mysql
mysql-connectors-community/x86_64   MySQL Connectors Community            141+51
mysql-tools-community/x86_64        MySQL Tools Community                     90
mysql80-community/x86_64            MySQL 8.0 Community Server               343

2.2 クライアントプログラムのみインストールする

$ sudo yum install mysql-community-{client,common,libs}-*

もしくは以下のコマンドでも可

$ sudo yum install -y mysql

2.3 RDSへ接続できるか確認する

$ mysql -u root -h DBインスタンスのエンドポイント -p

3. 必要なパッケージをインストールする

3.1 Node.jsをインストールする

2022年6月時点において、ネイティブリポジトリで利用できるバージョンは以下のように確認できる。

$ sudo yum list | grep node
libvirt-daemon-driver-nodedev.x86_64   4.5.0-36.amzn2.3               amzn2-core
nodejs-packaging.noarch                17-3.amzn2.0.1                 amzn2-core
texlive-pst-node.noarch                2:svn27799.1.25-38.amzn2.0.5   amzn2-core
texlive-pst-node-doc.noarch            2:svn27799.1.25-38.amzn2.0.5   amzn2-core

LTS版である16.15.1を利用したいため、Node.js公式のバイナリディストリビューションからインストールする。
node/BUILDING.md at v16.x ・nodejs/node・GitHub によると、node.jsをインストールするUnix prerequsuitesは以下:
* gcc and g++ >= 8.3 or newer
* GNU Make 3.81 or newer
* Python 3.6, 3.7, 3.8, 3.9, or 3.10 (see note above)
* For test coverage, your Python installation must include pip.

Amazon Linux2には自分でコンパイルする必要のあるソフトウェアの中に、makegccautoconfなどがある。(参照元:Amazon Linux インスタンスでのソフトウェアのコンパイルの準備

$ sudo yum install python3 gcc-c++ make python3-pip

Note: 今回上記のコマンドを実行した結果、以下のようにgcc-c++以外は元からインストール済みであった:

パッケージ python3-3.7.10-1.amzn2.0.1.x86_64 はインストール済みか最新バージョンです
パッケージ 1:make-3.82-24.amzn2.x86_64 はインストール済みか最新バージョンです
パッケージ python3-pip-20.2.2-1.amzn2.0.3.noarch はインストール済みか最新バージョンです

インストール:
  gcc-c++.x86_64 0:7.3.1-15.amzn2

依存性関連をインストールしました:
  cpp.x86_64 0:7.3.1-15.amzn2                         gcc.x86_64 0:7.3.1-15.amzn2            glibc-devel.x86_64 0:2.26-58.amzn2       glibc-headers.x86_64 0:2.26-58.amzn2
  kernel-headers.x86_64 0:5.10.118-111.515.amzn2      libatomic.x86_64 0:7.3.1-15.amzn2      libcilkrts.x86_64 0:7.3.1-15.amzn2       libitm.x86_64 0:7.3.1-15.amzn2
  libmpc.x86_64 0:1.0.1-3.amzn2.0.2                   libmpx.x86_64 0:7.3.1-15.amzn2         libquadmath.x86_64 0:7.3.1-15.amzn2      libsanitizer.x86_64 0:7.3.1-15.amzn2
  mpfr.x86_64 0:3.1.1-4.amzn2.0.2

NodeSourceが提供するNode.js公式のバイナリディストリビューションからインストールする。

$ curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -

# 今後のためにここにメモしている
+ rpm -q --whatprovides redhat-release || rpm -q --whatprovides centos-release || rpm -q --whatprovides cloudlinux-release || rpm -q --whatprovides sl-release || rpm -q --whatprovides fedora-release
+ uname -m

## Confirming "el7-x86_64" is supported...

+ curl -sLf -o /dev/null 'https://rpm.nodesource.com/pub_16.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm'

## Downloading release setup RPM...

+ mktemp
+ curl -sL -o '/tmp/tmp.KHWr6b5vli' 'https://rpm.nodesource.com/pub_16.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm'

## Installing release setup RPM...

+ rpm -i --nosignature --force '/tmp/tmp.KHWr6b5vli'

## Cleaning up...

+ rm -f '/tmp/tmp.KHWr6b5vli'

## Checking for existing installations...

+ rpm -qa 'node|npm' | grep -v nodesource

## Run `sudo yum install -y nodejs` to install Node.js 16.x and npm.
## You may run dnf if yum is not available:
     sudo dnf install -y nodejs
## You may also need development tools to build native addons:
     sudo yum install gcc-c++ make
## To install the Yarn package manager, run:
     curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
     sudo yum install yar
$ sudo yum install -y nodejs
$ node -v
v16.15.1

Note: nvmを利用してNode.jsをインストールする方法は以下の通り(チュートリアル: Amazon EC2 インスタンスでの Node.js のセットアップを参照)

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
$ . ~/.nvm/nvm.sh
$ nvm install 16.15.1

3.2 yarnを使用可能にする

コマンドはCloud9上にRails環境を構築する ( Amazon Linux2 ) を参照。

$ corepack enable

Note: Node.jsをnvmのようなパッケージマネージャーを使わずインストールしたため、下記のような権限エラーが発生する(nvmでインストールした方が良かったかな?)

Internal Error: EACCES: permission denied, symlink '../lib/node_modules/corepack/dist/pnpm.js' -> '/usr/bi
yarn -vn/pnpm'
Error: EACCES: permission denied, symlink '../lib/node_modules/corepack/dist/pnpm.js' -> '/usr/bin/pnpm'

root権限で打ち直せば解決できる

$ sudo corepack enable
# yarnコマンドか利用可能になったのでデフォルトのバージョンを確認する
$ yarn -v
1.22.15
$ corepack prepare [email protected] --activate

3.3 Gitをインストールする

$ sudo yum install -y git

 初期設定
$ git config --global user.name "ローカル環境のユーザー名"
$ git config --global user.email "ローカル環境のeメールアドレス"
# 確認
$ git config --list

3.4 rbenv/ruby-build/rubyをインストールする

  1. rbenvをインストールする(rbenvの公式GitHubリポジトリを参照)
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ ~/.rbenv/bin/rbenv init
# Load rbenv automatically by appending
# the following to ~/.bash_profile:

eval "$(rbenv init - bash)"
$ echo 'eval "$(rbenv init - bash)"' >> ~/.bash_profile
  1. ruby-buildをインストールする
$ mkdir -p "$(rbenv root)"/plugins
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
  1. rubyをインストールする
# アプリの環境に合わせること
$ rbenv install 2.6.3
$ rbenv rehash
$ rbenv global 2.6.3

Note: 今回以下のエラーでビルドが失敗した。

ERROR: Ruby install aborted due to missing extensions
Try running `yum install -y openssl-devel readline-devel zlib-devel` to fetch missing dependencies.

原因はruby-buildの "Suggested build environment" に関して、prerequisitesがミッシングしていたこと。
エラー文で示された yum install -y openssl-devel readline-devel zlib-develを打ってビルドし直した。

3.5 bundlerをインストールする

# アプリの環境に合わせること
# gem install bundler -v 2.2.33

4. アプリのデプロイ

4.1 GitHubとのSSH接続を可能にする

$ mkdir .ssh    #(既に生成されている場合もある)
$ chmod 700 .ssh
$ cd .ssh   # (鍵を入れるフォルダに移動)
$ ssh-keygen -t rsa     #(鍵を生成。オプションは特になくても構わない)
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa):(ファイル名は任意)
Enter passphrase (empty for no passphrase):(Enterを押す)
Enter same passphrase again:(Enterを押す)
Your identification has been saved in /home/ec2-user/.ssh/id_rsa.
Your public key has been saved in /home/ec2-user/.ssh/id_rsa.pub.

Note: ファイル名を指定しない場合、勝手に~/.ssh内にファイルを生成してくれるが、指定する場合は~/.ssh内にいないと当該ディレクトリに生成しないので注意。
Note: ファイル名を指定した場合、~/.ssh/configを作成してconfigの設定を書く。

$ vim config

Host github.com
  HostName github.com
  IdentityFile ~/.ssh/your_file_name
  User git

なぜ設定をするのかというと、ssh接続の際「~/.ssh/id_rsa」、「~/.ssh/id_dsa」、「~/.ssh/identity」しかデフォルトでは見に行かないため、ssh接続が上手くいかない可能性がある。(逆に言えばファイル名がデフォルトのままであればそのままssh接続を行なっても問題ないということである)

GitHubに公開鍵を登録してSSH接続をテストする。

$ cat ~/.ssh/id_rsa.pub
$ ssh -T [email protected]
Hi <your git user name>! You've successfully authenticated, but GitHub does not provide shell access.

確認できればcdでホームディレクトリに戻っておく。

4.2 アプリの配置ディレクトリを作成する

$ sudo mkdir -p /var/www
-pオプションは/var及び/var/wwwの各ディレクトリがまだ存在しない場合、これらのディレクトリを作成する意

念の為、先ほど作ったディレクトリの所有者とグループを確認してみた。

$ ls -l /var/
drwxr-xr-x  2 root root    6  6月 24 11:39 www

所有者・グループ共にrootだったので変更する。

$ sudo chown -R user_name:user_name /var/www

アプリをクローンする。

$ cd /var/www
$ git clone <リポジトリのURL>

ホームディレクトリに戻っておく。

4.3 Nginxの設定

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

server_names_hash_bucket_size 128;

upstream app_server {
    server unix:///var/www/alpha_blog/tmp/sockets/puma.sock;
}

server {
    listen 80;  # 80番ポートを許可
    server_name ec2-12-345-678-90.ap-northeast-1.compute.amazonaws.com; # ホスト名(Public IPやDNSなど)

    keepalive_timeout 5;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    root /var/www/alpha_blog/public;    # path for static files

    location / {
        try_files $uri/index.html $uri @alpha_blog;
    }

    location @alpha_blog {
        proxy_redirect off;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://app_server;
    }

    location ^~ /packs/ {
        gzip_static on;
        add_header Cache-Control public;
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/alpha_blog/public;
    }
}

◆ 以下解説
1. server_names_hash_bucket_size 128
* nginx起動を試行した際、下記のエラーが発生したため追記した。

nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 64
  • 当該エラーは多数のサーバー名や異常に長いサーバー名が定義されている(server_nameに該当する部分)と発生するため、エラー文が指示するようにserver_names_hash_bucket_sizeを増やす必要がある。
  • ちなみに、server_names_hash_bucket_sizeの定義はhttpディレクティブで行う必要があるが、/etc/nginx/nginx.conf(nginx default config file)include 配下の設定ファイルはすでに http ディレクティブ配下にあるので、httpディレクティブを記述する必要はない(さもなくば二重に http ディレクティブを書いていることになる)
  1. upstream

* ロードバランサーの設定。
* ELBの target group のように upstrem ディレクティブに任意のグループ名(今回ではapp_server)を定義しターゲット(バックエンドのwebサーバー)を追加することでnginxをロードバランサーとして設定できる。
* pumaとwebサーバー(今回の場合だとnginx)との連携にはソケット通信を利用するため、ターゲットにはpumaのソケットファイルを指定している

  1. keepalive_timeout 5

* webサーバーとクライアント間の通信時にHTTP通信を切断せずに待機する秒数
* 回線が遅い場合などTCP接続がすぐタイムアウトしてエラー扱いされるの防ぐ

  1. location / { try_files $uri/index.html $uri @alpha_blog; }

* リクエストが来た際、左から順に「/」配下を探索してレスポンスをする
* URIのパスに対するファイル(静的コンテンツ)が存在すれば、そのファイル返す。
* 存在しなければ、動的コンテンツとして@alpha_blogに内部リダイレクトする
* 前提として、nginxがプロキシされたサーバーへリクエストを渡すにはlocationの中にproxy_passディレクティブを指定する、ということがある。詳細はNGINX リバースプロキシ | NGINX を参照。

  1. location @alpha_blog { }

* nginxのリバースプロキシ設定
* 上述の@alpha_blogが呼び出された場合のみ{ }内の設定が読み込まれる
* クライアントからserver_name宛のリクエストをnginxが受け取った際、proxy_passで指定されたhttp://app_serverへ転送する
* loactionで処理されるリクエストはproxy_passで指定されたアドレスのサーバーへ渡される
* このアドレスはドメイン名またはIPアドレスとして指定することができる

4.4 アプリの設定を本番環境用に編集する

4.4.1. credentials.yml を編集するにはrailsコマンドを使う必要があるため、まずbundle installする。

$ cd /var/www/alpha_blog
$ bundle config set without 'development:test'
$ bundle config
Settings are listed in order of priority. The top value will be used.
without
Set for the current user (/home/ec2-user/.bundle/config): [:development, :test]
$ bundle install
# 今回mysql2のインストールで失敗したのでエラーメッセージの通りに以下のコマンドを実行してやり直した
$ sudo yum install mysql-devel

4.4.2. credential(秘密情報)ファイルを編集する。(詳しい説明は「Railsのcredentialについて」を参照)

秘密情報を本番環境で編集する方法には以下の選択肢がある:
master.keyを開発環境からコピーしてくる
本番環境用のcredentialsファイルとマスターキーのペアを作成する方法

開発環境からmaster.keyをコピーしてくる場合

Amazon Linuxの設定ファイル(/etc/environment)にmaster.keyの内容を環境変数として設定する

$ cd
$ sudo vim /etc/environment

# マスターキーを追記
RAILS_MASTER_KEY="4678vbf~~~~~" # ローカルの/config/master.keyから値をコピーする

#環境変数を反映するために一度EC2インスタンスからログアウトして再接続する。
# 値が取得できるか確認する
env | grep RAILS_MASTER_KEY

Note: マスターキーを紛失してしまった場合

$ rm config/credentials.yml.enc
config.require_master_key = true (ENV["RAILS_MASTER_KEY"]環境変数またはconfig/master.keyファイルでマスターキーを取得できない場合はアプリを起動しないようにする)を有効化する。

config/environments/production.rb

# 下記コメントアウトを外す
config.require_master_key = true

$ EDITOR=vim rails credentials:edit
config/credentials.yml.encファイルにセキュリティ情報などを記述する。

# インデントに注意する
db:
  username: ユーザーネーム
  password: RDSのパスワード
  host: RDSのエンドポイント

# awsのアクセスキーやシークレットキーのコメントアウトを外す
aws:
  access_key_id: 123
  secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 8be8e637d755f79c799048bed8be0c...

$ vim config/database.yml

production:
  <<: *default
  adapter: mysql2
  encoding: utf8mb4
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  host: <%= Rails.application.credentials.db[:host] %>
  socket: /var/lib/mysql/mysql.sock
  database: alpha_blog_production

4.4.3. 本番環境用のデータベースを作成する

$ bundle exec rails db:create RAILS_ENV=production # RDS作成時に既に生成してあれば必要ない
$ bundle exec rails db:migrate RAILS_ENV=production

もし下記のようなエラーが発生したら、config/database.ymlを編集してproduction環境のみ記述するようにすれば今回は問題なく動いた。

Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)
Couldn't create 'alpha_blog_production' database. Please check your configuration.
rails aborted!
Mysql2::Error::ConnectionError: Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)
bin/rails:4:in `<main>'
Tasks: TOP => db:create
(See full trace by running task with --trace)

config/database.yml

# default/development/test環境の記述を全て削除する
production:
  adapter: mysql2
  encoding: utf8mb4
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  host: <%= Rails.application.credentials.db[:host] %>
  socket: /var/lib/mysql/mysql.sock
  database: alpha_blog_production
  pool: 5

4.4.4. Pumaの設定ファイルを本番環境用に編集する

$ vim config/puma/production.rb

config/puma/production.rb

root_dir = '/var/www/alpha_blog'

max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
min_threads_count = ENV.fetch('RAILS_MIN_THREADS', max_threads_count)
threads min_threads_count, max_threads_count

worker_timeout 60

# pumaが通信を待ち受ける場所を記述
bind "unix://#{root_dir}/tmp/sockets/puma.sock"

environment 'production'

pidfile File.expand_path('tmp/pids/server.pid')

stdout_redirect File.expand_path('log/puma_access.log'), File.expand_path('log/puma_error.log'), true

plugin :tmp_restart

daemonize

4.4.5. アセットをコンパイルする

$ bundle exec rails assets:precompile RAILS_ENV=production

4.4.6. サーバーを起動させてサイトにアクセスする

# nginxを再起動する
$ sudo systemctl restart nginx

# 下記のコマンドでconfig/puma/production.rbを参照してくれる
# Pumaでデーモン化の設定を行っている場合、バックグラウンドでRailsが起動する
$ bundle exec rails s -e production

「http://パブリックIPまたはドメイン名」にアクセスできれば成功している。

5. 参考

nginxやpumaの設定ファイルの記述は以下のサイトを主に参考にした:

ツイッターでシェア
みんなに共有、忘れないようにメモ

光の勢力

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント