Rails5+Puma+Nginxな環境をCapistrano3でEC2にデプロイする(前編)

Rails5系で新しく開発しているウェブアプリケーションを、AWSのEC2で稼働している本番環境にCapistranoでデプロイすることになったときのお話です。これまで個人でなにかを開発する際にはRails4系でアプリケーションサーバにUnicornを採用していましたが、今回からはPumaを採用することにしたのでその手順です。前回の手順はこちらです。
Rails4 & Unicorn & Nginx & EC2でサーバー構築

前提

  • Rails 5.0.0
  • Ruby 2.4.0
  • rbenv 0.4.0
  • Puma
  • Nginx
  • Capistrano 3.7.0
  • Amazon Linux AMI 2017.03.0 (HVM)

手順

  • 1. 必要なGemを追加
  • 2. Capistranoの設定を追加
  • 3. デプロイ実行ユーザを作成
  • 4. アプリケーション用のディレクトリを作成
  • 5. 接続に必要な鍵を作成
  • 6. AgentFowardを設定
  • 7. 接続を確認
  • 8. おもむろにデプロイしてみる

1. 必要なGemを追加

まずはデプロイに必要なGemを追加する。capistrano-rbenv-vars はPumaで環境変数を読むために使っている。

Gemfile
# Use Puma as the app server
gem 'puma', '~> 3.0'

group :development do
  gem 'capistrano', '3.7.0'
  gem 'capistrano-rails'
  gem 'capistrano-bundler'
  gem 'capistrano-rbenv'
  gem 'capistrano-rbenv-vars'
end
$ bundle exec bundle install

2. Capistranoの設定を追加

インストールのためのコマンドがあるので使う。コマンドを使わずに手動でファイルを作成してもよいが、今回はこちらのコマンドで生成する。

$ bundle exec cap install

以下のファイルがざっと生成される。 deploy.rb に共通の設定を書いておいて、ステージング環境と本番環境の固有の設定は staging.rbproduction.rb にそれぞれ記述していく。

  • Capfile
  • config/deploy.rb
  • config/deploy/staging.rb
  • config/deploy/production.rb
  • lib/capistrano/tasks

以下にデプロイを成功させるための最低限の設定を記載しておく。ほぼ初期設定のためおそらくこのままではアプリケーションはきっと動かないので、自身の環境に合わせてよきに追加、削除する。まずは デプロイが正常に終了する ことにゴールとして進める。

Capfile
require "capistrano/setup"
require "capistrano/deploy"
require "capistrano/scm/git"

install_plugin Capistrano::SCM::Git

Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
deploy.rb
lock "3.7.0"

set :application, "my-app-name"
set :repo_url, "git@github.com:kappy/my-app-name.git"
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

namespace :deploy do
  desc "Make sure local git is in sync with remote."
  task :confirm do
    on roles(:app) do
      puts "This stage is '#{fetch(:stage)}'. Deploying branch is '#{fetch(:branch)}'."
      puts 'Are you sure? [y/n]'
      ask :answer, 'n'
      if fetch(:answer) != 'y'
        puts 'deploy stopped'
        exit
      end
    end
  end

  desc 'Initial Deploy'
  task :initial do
    on roles(:app) do
      invoke 'deploy'
    end
  end

  before :starting, :confirm
end
production.rb
server "xx.xxx.xxx.xxx", user: "deploy", roles: %w{app db web}

set :ssh_options, {
  keys: %w(~/.ssh/id_rsa),
  forward_agent: true,
  auth_methods: %w(publickey)
}

3. デプロイ実行ユーザを作成(EC2)

Capistranoでデプロイを行うために、デプロイ用のユーザを作成してデプロイ対象のEC2に対しsshで接続できるように権限を与える。

まずは既存のユーザでログインする。

$ ssh -i "~/.ssh/xxxxx.pem" ec2-user@ec2-xx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com

デプロイ用のユーザを作成し、sudoコマンドが実行できるように権限を与える。 /etc/sudoers ファイルを編集すればよいのだが $ sudo vi /etc/sudoers では読み取り専用になってしまうため $ sudo visudo で行なっている。

$ sudo su -
$ useradd deploy
$ passwd deploy

$ sudo visudo
root   ALL=(ALL) ALL
deploy ALL=(ALL) ALL ← この1行を追加

$ exit

exitしてec2-userに戻り作成したdeployユーザで入れるか確認する。

$ su - deploy

4. アプリケーション用のディレクトリを作成(EC2)

デプロイ対象となるアプリケーション用のディレクトリを準備する。Capistranoのデフォルト設定では /var/www/my_app_name となっているため、特に理由がなければこちらのパスに沿うようにする。my_app_name は自身のアプリ名で置き換えればよい。

$ su - deploy

$ pwd
/home/deploy

$ sudo mkdir www && cd www
$ sudo mkdir my_app_name && cd my_app_name
$ sudo chown deploy:deploy my_app_name

5. 接続に必要な鍵を作成(EC2)

ローカルマシン(以下、ローカル)からEC2に接続するための鍵をつくる。作成した鍵を authorized_keys に名前を変えているのはAWS側のSSHの初期設定に沿うようにするため。こちらの設定は /etc/ssh/sshd_configAuthorizedKeysFile に記述されているが、変更する理由もないのでこちらに合わせる。

$ pwd
/home/deploy

$ sudo mkdir .ssh && cd .ssh
$ ssh-keygen -t rsa
$ mv id_rsa.pub authorized_keys

$ chmod 600 authorized_keys
$ chmod 700 ../.ssh

$ cat id_rsa

後にこの秘密鍵を使用してEC2に接続するため、作成した秘密鍵(id_rsa)はローカルの ~/.ssh/id_rsa などに保存しておく。

6. AgentFowardを設定

今回の設定ではローカルに保存した秘密鍵を使用してEC2に接続し、EC2に保存した秘密鍵を使用してGithubに接続してソースコードを取得する。Githubへの接続はローカルの秘密鍵を使用するため、ローカルでAgentFowardを設定する。

まずは現在の設定状態を確認する。

$ ssh-add -l

先ほど作成した秘密鍵を追加する。-K オプションを付けているのはKeychainに設定を保存しておくため。この設定がないと再起動するたびに設定が消えてしまう。

$ ssh-add -K ~/.ssh/id_rsa

7. 接続を確認

鍵の登録が正しく行われていれば以下のようなコマンドで鍵の指定をせずに接続ができるようになる。パスフレーズを入力し公開鍵認証での接続が成功していることを確認する(パスフレーズを設定せず鍵の作成を行えばいいよと書いている記事があるがそれは間違い)。

$ ssh deploy@xx.xxx.xxx.xxx

8. おもむろにデプロイしてみる

ここまでで、とりあえずデプロイを実行してみる。きっと失敗するががんばってエラーを逐一解消していく。エラーログはちゃんと読みましょう。

$ bundle exec cap production deploy

以下のデプロイタスクが正常に完了すればCapistrano側の設定はひとまずおわり。

  • git:wrapper
  • git:check
  • deploy:check:directories
  • git:clone
  • git:update
  • git:create_release
  • deploy:set_current_revision
  • deploy:symlink:release
  • deploy:log_revision

後半はこちらです。
Rails5+Puma+Nginxな環境をCapistrano3でEC2にデプロイする(後編)

おまけ

エラーログをちゃんと読めばだいたいすぐに解決すると思うが、念のため想定されるエラーと解決方法を載せておく。

1. 権限がなくscpできない

cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as deploy@xx.xxx.xxx.xxx: scp: /tmp/git-ssh-xxx-xxx-production-xxx.sh: Permission denied

→ ローカルから本番環境にscpでデプロイ実行ファイルを配置しようとしたときにPermission deniedでこけてる。別のユーザ(ec2-user)などですでにscpでファイルがつくられいる状態で、デプロイユーザ(deploy)でscpしようとしたときなどに起こる。 ls -la /tmp/ などでファイルを確認してすでに存在すれば削除する。

2. gitが入っていない

cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as deploy@xx.xxx.xxx.xxx: git exit status: 127
git stdout: Nothing written
git stderr: /usr/bin/env: git: No such file or directory

→ gitが入っていないよ。とおこられている。素直に入れる。
$ sudo yum install git

3. 権限がなくGithubに接続できない

cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as deploy@xx.xxx.xxx.xxx: git exit status: 128
git stdout: Nothing written
git stderr: Warning: Permanently added 'github.com,xxx.xx.xxx.xxx' (RSA) to the list of known hosts.
Permission denied (publickey).
fatal: Could not read from remote repository.

→ 権限がなくてリポジトリが見れないよ。とおこられている。正しい鍵が登録されているか、AgentFowardの設定は正しいかといった点を確認する。

4. 権限がなくディレクトリが作成できない

cap aborted!
SSHKit::Runner::ExecuteError: Exception while executing as deploy@xx.xxx.xxx.xxx: mkdir exit status: 1
mkdir stdout: Nothing written
mkdir stderr: mkdir: cannot create directory ‘/var/www/my-app-name/shared’: Permission denied
mkdir: cannot create directory ‘/var/www/my-app-name/releases’: Permission denied

→ 権限がなくて /var/www/my-app-name/ 配下に必要なディレクトリ(/shared/releases)が作成できないよ。とおこられている。
$ sudo chown deploy:deploy -R /var/www/my-app-name

参考