Just do IT

思うは招く

Capistrano3 + Nginx + Puma + PostgreSQL で簡単な Rails アプリをデプロイしてみる

rails newで作った簡単なアプリを Capistrano でデプロイする工程をお見せする。

NginxとPumaを連携させ、静的ファイルはNginxから、動的ファイルはPumaから配信するようにする。

初めての場合はまずこれをしよう

Capistrano でのデプロイは複雑でむずかしい。

「ぶっちゃけ、Capistrano がどう動くかもあやふや・・・」という場合は、ステップを小さく分けて理解するのがおすすめ。

ローカルのproduction環境でRailsアプリを動かした経験があると、わかりやすくなると思う。

k-koh.hatenablog.com

また、最初はRailsアプリではなく、ただのファイルをリモートサーバにデプロイしてみることを強くおすすめする。そのための方法は次の記事で解説している。

k-koh.hatenablog.com

開発環境

アプリケーションサーバ(以後「APサーバ」)

なぜ PostgreSQL を使うか

練習用なので、Railsデフォルトのsqlite3でもいいのだが、実際に本番で使うことはほぼない。

「どうせやるなら実際の業務で使用されているDBを」と思い、PostgreSQLを選んだ。

前提

次の記事を読み、理解していること。

k-koh.hatenablog.com

上記の記事で説明している基本の設定ができているという前提で話を進める。(お手数ですが、ご了承ください)

条件

  • リモートサーバがあり、SSHログインできる
  • リモートサーバに次のソフトウェアがインストールされている
  • Webサーバ(Nginx)とAPサーバ(puma)の違いを理解している

とくに、WebサーバとAPサーバの違いを理解していることは、この記事を進めるにあたり非常に重要なポイントになる。

まだ理解が微妙という場合は、次の記事がわかりやすいので熟読するべし!

qiita.com

リモートサーバにPostgreSQLがない場合はこちらを参考にインストールしてほしい。

k-koh.hatenablog.com

Capistrano とはなんなのか?

Capistrano とは、デプロイに関するいろんな作業を、コマンドひとつで完了させてくれるフレームワークのこと。

Capistrano を使わなくてもデプロイはもちろんできる。

しかし、Capistrano を使えばローカルPCのターミナルでコマンドを1回打つだけで、リモートサーバへのデプロイが終わる。

Capistrano の働きを理解するには、「手動でデプロイする場合」と比べるとわかりやすい。

手動デプロイはおおまかに、次のステップを踏む。

  • ウェブアプリをGitHubへプッシュ
  • リモートサーバへ入る
  • リモートサーバにアプリのリポジトリをクローンする
  • プリコンパイルする
  • Nginxの設定をする(必要があれば)
  • APサーバを起動する

うーむ、いろんな作業があってすこし大変・・・。

ところが、Capistrano を使うと次のように変わる。

Capistranoの場合

  • ターミナルでbundle exec cap production deployを打つ
  • コーヒーでも飲んで待つ☕
  • デプロイが終わったらドヤ顔をキメる😁

便利!

あなたもドヤ顔ができるように、さっそく Capistrano でのデプロイを試していこう。

アプリをつくる(ローカル)

適当にRailsアプリを作ろう。DBにはPostgreSQLを使う。

rails new capistrano_psql -d postgresql
cd capistrano_psql

scaffold で適当なプログラムをつくる

とりあえず、Task アプリをつくる。

bin/rails generate scaffold Task title:string memo:text

次に、ルートURLにアクセスしたらアプリが表示されるようにする。

routes.rb

Rails.application.routes.draw do
  resources :tasks
  root "tasks#index"
end

ローカルで動くか確かめよう。

bin/rails db:create
bin/rails db:migrate
bin/rails s

ローカルでサーバを起動したら、localhost:3000へアクセス。

localhost:3000

こんな画面が表示されることを確認する。

f:id:K_Koh:20200408161838j:plain

GitHubリポジトリをつくる

(ローカルでの作業)

git init
echo "# はじめてのCapistrano" > README.md
git add .
git commit -m "first commit"

# ここでGitHubリポジトリ作成しておく

git remote add origin git@github.com:GitHubユーザーネーム/リポジトリ名.git
git push -u origin master

関連:git init から git push までの流れ - オランウータンとぼく

今回は練習なので、masterブランチ上で作業をする。(もちろん、ブランチを切りたい方はご自由に!)

デプロイ用のユーザーをつくる(リモートサーバ)

次の記事を参考に、デプロイ用のユーザーをリモートサーバにつくる。

Capistrano 超初心者がなんとなく理解するために Rails アプリではなくファイルをリモートサーバにデプロイしてみる」の記事をやっていればあらたに設定する必要はないが、もし目を通していない場合、上記記事の

  • リモートサーバにデプロイ専用ユーザをつくる
  • /var/www があるか確認する
  • リモートサーバに公開鍵を設置
  • ssh-agentの設定

をおこない、こちらの記事に戻ってきてほしい。 (リモートサーバ上で作業する点に注意!)

今後、リモートサーバへはdeployユーザでログインする。

デプロイユーザーを PostgreSQL のロールに追加(リモートサーバ)

いま作ったdeployユーザーを、PostgreSQLのロール(ユーザー)に追加する。

このとき、パスワードも同時に設定するオプション(-P)もつける。パスワードは自由に設定し、忘れないようにメモしておこう。

# パスワードつきでdeployロールを作る
sudo createuser -d -P deploy

Node.js と Yarn はシステムにインストールする(リモートサーバ)

アセットのプリコンパイルをするため、Node.jsとYarnが必要になる。

個人的に2日くらいハマったポイント😱

リモートサーバにanyenvを使い、nodenvをインストールしたところ、アセットのプリコンパイル時にエラーが出た。

関連:Capistrano3 デプロイ時に Yarn requires Node.js 4.0 or higher to be installed エラーで落ちる - オランウータンとぼく

「ちゃんとインストールしてるのに、なぜ!?」とイライラしたが、システムにインストールすると解決した。

よってこれらをインストール。

# nodejsをインストール
sudo apt install -y nodejs

# yarnをインストール
sudo apt-get update && sudo apt-get install yarn

必要なgemをインストール(ローカル)

ローカルのアプリのディレクトリにもどろう。

ようやくCapistranoフレームワークや、いくつかのプラグインをインストールする。

Capistranoを使うには、まずcapistranoを入れる。さらに、capistranoを便利にしてくれるプラグインも使う。

ここらへんは人によって変わるが、今回は「デプロイの手順を学ぶ」のが目的なため、基本的なプラグインしか使わない。

Gemfile

# 以下を追記
group :development do
  gem 'capistrano'
  gem 'capistrano-safe-deploy-to'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capistrano3-puma'
end

それぞれを説明する。

これを今読んでもわかりづらいと思う。なのでとにかく今は先に進んで、デプロイに成功してから読み直そう。

gemをbundle installする。

bundle install

Capistrano の設定をする(ローカル)

以下のコマンドでCapistranoの設定ファイルを作成できる。

bundle exec cap install

# 結果
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/staging.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified

いろんなファイルが生成される。

それぞれの使い方

  • Capfile: Capistranoに関する設定
  • deploy.rb: 基本設定
  • staging.rb: ステージング環境に関する設定
  • production.rb: production環境に関する設定

基本的には、

  • インストールしたCapistranoプラグインをCapfileにrequireで記述し、使えるようにする
  • deploy.rbで共通の設定を書く
  • config/deploy/*.rbに個別の設定を書く

という流れで設定する。

Capfile

もとの記述はすべて消し、以下を追記する。

require "capistrano/setup"
require "capistrano/deploy"
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
require "capistrano/safe_deploy_to"
require "capistrano/rbenv"
require "capistrano/bundler"
require "capistrano/rails/assets" # アセットをプリコンパイルするため
require "capistrano/rails/migrations" # マイグレーションをするため

require "capistrano/puma"
install_plugin Capistrano::Puma # CapistranoとPumaを連携
install_plugin Capistrano::Puma::Nginx # PumaとNginxを連携

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

Gemfileにインストールしたgemをここで使えるようにしている。

config/deploy.rb

もとの記述はすべて消し、以下を追記。

# config valid for current version and patch releases of Capistrano
lock "~> 3.12.1"

# アプリ名(なんでもOK)
set :application, "capistrano_psql"

# GitHubリポジトリ情報
set :repo_url, "git@github.ユーザーネーム/capistrano_psql.git"

# ユーザーはdeployにする
set :user, "deploy"

# rbenvをユーザーレベルでインストール
set :rbenv_type, :user

# rubyのバージョンを指定
set :rbenv_ruby, File.read('.ruby-version').strip
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"

# 並列数
set :bundle_jobs, 2

# リリース間で共有するリソースのファイルパスを書く
append :linked_files, "config/master.key"

# 各リリースが共通で読み込むディレクトリを設定する
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets",  '.bundle'
  • アプリ名はディレクトリ名を書いてもいいし、なんでもいい
  • repo_urlリポジトリURLのことで、GitHubで確認できる
  • リモートサーバで設定したdeployユーザーを設定
  • リモートサーバで使うRubyの設定
  • append :linked_dirsでは、各リリースが共通で読み込むディレクトリを設定する
  • append :linked_filesでは、各リリースが共通で読み込むファイルを設定する

「え?全然わかんないんだけど・・・」と思うかも知れない。

安心してほしい。最初は何がなんだかわからないかもしれないが、デプロイを経験すると、「あ〜こういうことだったのか」となんとなく理解する(と思う)。

もっともわかりにくいのは、appendだと思う。

Capistranoでデプロイをすると、アプリを「リリース」という単位で管理するようになる。

リリースが作られるとき、たくさんのファイルが新たに用意されるのだが、config/master.keyなど、1回読み込めたらいいファイルもある。各リリースに、これらの同じファイルを配置するのは非効率だ。

よって、どこか一箇所に保管して、各リリースで共通して読み込むようにする。

それを、appendで指定することができる。appendで指定したディレクトリやファイルは、デプロイ後のsharedディレクトリ配下に設置される。そして、各リリースからはシンボリックリンクで参照されるようになる。

今はわからなくても、デプロイしたら「あ〜、そういうことだったのか」とわかるはず。なので今は前に進もう。

config/deploy/production.rb

もとの記述はすべて消し、以下を追記。

# サーバの設定
server "サーバのIPアドレス", user: "deploy", roles: %w{web db app}

# ssh接続設定
set :ssh_options, {
  user: fetch(:user),
  port: ポート番号,
  keys: %w(~/.ssh/id_rsa),
  forward_agent: true,
  auth_methods: %w[publickey]
}
  • IPアドレスには、契約しているリモートサーバ(今回はさくらVPS)に割り当てられたIPアドレスを書く
  • 接続時のポート番号を変更している場合、それも書く
  • keys:%w(~/.ssh/id_rsa)には、自分が秘密鍵を設置しているファイルパスを記入
  • forward_agent: trueを設定することで、リモートサーバからGitHubSSH接続をしてリポジトリをクローンできるようになる

user: fetch(:user)は、実際には以下と同じ意味になる。

# deployユーザをfetchで取得しているだけ
user: "deploy"

Railsアプリをデプロイした後は、serverに指定したIPアドレスにアクセスすることで、アプリが表示される。

独自ドメインを割り当てたい場合

練習用ならIPアドレスだけでもいいが、自分で取得した独自ドメインにアクセスして表示できるようにしたい場合、IPアドレスの部分にドメインを書く。

config/deploy/production.rb

server "hoge.com", user: "deploy", roles: %w{web db app}

これで、デプロイ成功後にhoge.comへアクセスすると、自分が作ったRailsアプリが見れる。

githubへpushする(ローカル)

ひとまず、今までの設定をGitHubにpushしておこう。

git add .
git commit -m "capistrano-test"
git push origin master

デプロイしてみる

本番環境にデプロイしてみよう。

# 本番環境にデプロイする
bundle exec cap production deploy

※ちなみに、ステージング環境にデプロイする場合はこうなる。

# ステージング環境にデプロイする
bundle exec cap staging deploy

コマンドを打っただろうか? おそらく、「master.keyを読み込めてねぇよ!」的なエラーで止まるはずだ。 (騙したみたいでごめんなさい💦)

master.keyをリモートサーバに転送する

本番環境でRailsアプリを動かす場合、master.keyが必要になる。

しかし、今のリモートサーバにはmaster.keyがない。デプロイをするとき、リモートサーバはGitHubからコードの変更をクローンするが、そこにはmaster.keyは含まれていない。

なぜなら、 .gitignoreファイルにはmaster.keyが記述されており、GitHub上にmaster.keyがアップされないから。rails newした場合、.gitignoreにはデフォルトでmaster.keyが記述されている。

よって、リモートサーバに手動でmaster.keyを設置する。(おそらく、方法はいろいろあると思うが今回はこの方法でやる)

ローカルからscpコマンドを使ってmaster.keyを転送する

設置する手段としては、おもに次の2つ。

  • 手動でリモートサーバに入り、master.keyファイルを作ってペースト
  • ローカルからscpコマンドを使って転送

scpコマンドを使ったほうが圧倒的にラクなので、そうしよう。

ローカルの今回作成しているアプリのディレクトリへ移動し、次のコマンドを叩く。

scp -P ポート番号 config/master.key deploy@リモートサーバのIPアドレス:/var/www/capistrano_psql/shared/config/master.key

VPSのポート番号を変更しているなら、-Pオプションでポート番号を指定する。Pは大文字な点に注意。

scpコマンドがうまくできない場合

うまくできない場合、無理に使う必要はない。リモートサーバへ入って手動でファイルを作成すればよい。

#リモートサーバへログインし、アプリ名のディレクトリへ移動
cd /var/www/capistrano_psql/

# ls でshared/configがあるか確認
ls

# あれば移動
cd shared/config

# なければつくる
mkdir -p shared/config/

# config下にmaster.keyファイルを作成し、ローカルのmaster.keyの内容をコピペ
sudo vim master.key

database.yml(ローカル、リモートサーバ)

本番環境でDBを動かすには、ユーザーネームとパスワードが必要になる。ユーザーネームは、さきほど作ったdeployにする。PostgreSQLでdeployロールを作成したとき、パスワードも設定した。そのパスワードをリモートサーバ内の環境変数に設定する。

config/database.yml

production:
  <<: *default
  database: capistrano_psql_production
  username: deploy
  # 環境変数で読み込む
  password: <%= ENV['CAPISTRANO_PSQL_PASSWORD'] %> 

リモートサーバにログインし、環境変数を設定する。

# .bash_profileを開く
sudo vim ~/.bash_profile

# bash_profileに環境変数を書き込む
export CAPISTRANO_PSQL_PASSWORD=パスワード

# 再読み込み
source ~/.bash_profile

これで、CAPISTRANO_PSQL_PASSWORDという環境変数に設定したパスワードを、database.ymlで読み込めるようになる。

PostgreSQLにデータベースをつくる(リモートサーバ)

本番環境で使うデータベースを作成するため。deployユーザでリモートサーバへログイン。

データベース名は、database.ymlに書いたのと同じ名前にする。

$ createdb capistrano_psql_production

データベースの確認をするには、psqlを起動し、\lを打つ。

$ psql postgres
postgres=# \l

                                          List of databases
            Name            |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges   
----------------------------+----------+----------+-------------+-------------+-----------------------
 capistrano_psql_production | deploy   | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 

ちなみに、今回は手動でデータベースを作成したり、環境変数で読み込ませたりしているが、Capistranoプラグインを使えばもっと簡単に設定できる。

しかし、基本的な流れを確認するため、この記事では手動でやっている。

Pumaの設定

Capfileで

require 'capistrano/puma'
install_plugin Capistrano::Puma

のように設定していると、CapistranoでPumaを動かせるようになる。

以下のコマンドで、Pumaの設定ファイルを作成してくれるので入力しよう。

bundle exec cap production puma:config

これでPumaの設定ファイルができた。ローカルからコマンドを打って、Pumaを起動・停止することができるようになった。

ためしに、Pumaを起動してみよう。

# Pumaを起動
bundle exec cap production puma:start

# Pumaのステータスを確認
bundle exec cap production puma:status

# Pumaを停止
bundle exec cap production puma:stop

Pumaの起動が確認できたら、停止せずにそのままで次へ進もう。

PumaとNginxを連携する

あとはPumaとNginxの連携の設定をしたら終わり。頑張ろう! (なお、独自ドメインSSLで表示させたい場合、ここを飛ばして次の「独自ドメインSSLで表示させたい場合」を参考にしよう)

Capfile

install_plugin Capistrano::Puma::Nginx

これがあることで、PumaとNginxの連携を可能にしている。

「練習用だから、ひとまず今動くことを確認できたらそれでいい。SSLじゃなくていい。」という場合、次のコマンドを打つだけでいい。

bundle exec cap production puma:nginx_config

これでPumaとNginxの連携が自動でおこなわれ、Nginxの設定ファイルが生成される。

Nginxを設定したあとは、nginx -tで設定ファイルに間違いがないかどうか確認し、再起動させよう。

# 設定ファイルが正しいかチェック  
$ sudo nginx -t

# 起動
$ sudo nginx

独自ドメインSSLで表示させたい場合

練習用に取得した独自ドメインSSLで表示したい場合、以下の設定をしよう。

今回の場合、Let's EncryptでSSL対応をしている。すでにSSL対応済みと想定して進める。まだSSL対応をしていない場合、次のページが参考になる。

参考

config/deploy/production.rb に以下を追記する。

# nginx
set :nginx_config_name, "#{fetch(:application)}"
set :nginx_server_name, "IPアドレス or ドメイン"
set :nginx_use_ssl, true
set :nginx_ssl_certificate, "/etc/letsencrypt/live/ドメイン/fullchain.pem"
set :nginx_ssl_certificate_key, "/etc/letsencrypt/live/ドメイン/privkey.pem"

次のコマンドでNginxの設定ファイルを作成する。

# Nginxの設定ファイルを作成
bundle exec cap production puma:nginx_setup 

これでCapistranoが自動でNginxの設定ファイルを作成してくれる。

具体的には、/etc/nginx/sites-availableに設定ファイルが作成され、/etc/nginx/sites-enabledにシンボリックリンクが貼られる。

最後に、Nginxの設定をチェックし、起動しよう。

# 設定ファイルが正しいかチェック  
$ sudo nginx -t

# 起動
$ sudo nginx

これでようやく準備完了!

デプロイに再挑戦

デプロイコマンドを打とう。

bundle exec cap production deploy

おそらく、今回はエラーもなく、デプロイが完成するはず。

設定したIPアドレスもしくはドメインにアクセスしてみよう。表示されていたら、Capistranoでのデプロイは成功です!やったね!

エラーが出る場合

大丈夫、みんなハマる。でも、絶対できる。

私は5日ほどハマり続け、あらゆるエラーを経験した。エラーに関する記事を残しているので、参考になれば!

Capistrano カテゴリーの記事一覧 - オランウータンとぼく