読者です 読者をやめる 読者になる 読者になる

珈琲駆動開発

RubyとRailsがおおい

RailsアプリをNginx+UnicornなAWSにCapistranoでデプロイ

Rails Nginx Unicorn AWS Infrastructure

Special Thanks

Rails 初心者が Capistrano3 で AWS EC2 にデプロイするためにがんばったこと。

http://qiita.com/kizashi1122/items/b54b0f1033ff5d7fbd66

RailsアプリをEC2にデプロイしてみる。

http://xxxcaqui.hatenablog.com/entry/2013/06/11/042646

RailsアプリをAWSにデプロイする

https://cyllabus.jp/courses/XCP7mfjvEjNArvvaqSZ3Hg

学習方法や用語などにも触れられててよい記事。

手順

手順をまとめると、以下のようになります。

  1. 前準備
  2. nginxの設定
  3. linked filesの用意
  4. 権限のチェック
  5. Capistrano設定
  6. ssh設定
  7. デプロイ

1. 前準備 

myAppの部分は適宜読み替えてやってください。

公開鍵をサーバーに送信

$ scp -i ~/.ssh/hogehoge.pem ~/.ssh/id_rsa.pub ec2-user@ec2_server:~

サーバー

ここからサーバー上での作業です。sshで接続しておいてください。

2. nginx

今回作成するアプリ用にnginxの設定ファイルを作成します。パスはetc/nginx/conf.d/myapp.confみたいな感じです。

upstream unicorn_server {
  server unix:/tmp/unicorn.sock
  fail_timeout=0;
}
 
server {
  listen 80;
  client_max_body_size 4G;
  #server_name _; ドメイン未登録
 
  keepalive_timeout 5;
 
  root /var/www/myApp/current/public;
  access_log /var/log/nginx/myApp_access.log;
  error_log /var/log/nginx/myApp_error.log;
 
  error_page 500 502 503 504 /500.html;
 
  try_files $uri/index.html $uri @unicorn;
 
  location @unicorn {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://unicorn_server;
  }
 
  location ~ ^/assets/ {
    root /var/www/myApp/current/public;
  }
 
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /var/www/myApp/current/public;
  }
}

3. linked file

gitでignoreして管理していないsecrets.ymlとかdatabase.ymlとかあればファイルがあればここで作成しておきます。

4. 権限変更

ec2-userの/var/wwwに対する権限を変更。書き込みできるように。

$ sudo chown -R ec2-user /var/www

ローカル

こっからローカルでの作業です。

5. Capistrano

Gemfileに以下のgemを加えます。

# Gemfile
group :production, :staging do
  gem 'unicorn'
end
 
group :development do
  gem 'capistrano'
  gem 'capistrano-rails'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano3-unicorn'
end

$ bundle installで反映しておく。

次に、Capistranoで必要なファイル群を生成します。Capistranoはそのためのinstallコマンドを用意してくれてますー。

▶ 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を以下のように書きました。

# Load DSL and set up stages
require 'capistrano/setup'

# Include default deployment tasks
require 'capistrano/deploy'

# 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'
set :rbenv_type, :user
set :rbenv_ruby, '2.2.1' # TODO: Change Ruby version
require 'capistrano/rails'
# require 'capistrano/chruby'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
# require 'capistrano/passenger'
require 'capistrano/bundler'
require 'capistrano3/unicorn'

# set :linked_files, %w{config/secrets.yml config/database.yml}

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

config/deploy.rbは以下ように。

lock '3.4.0'

set :application, 'myapp' # TODO
set :repo_url, 'git@github.com:org/myapp.git' # TODO
set :deploy_to, '/var/www/myapp' # TODO
set :log_level, :debug

set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/assets}

# rbenv Ruby version
set :rbenv_ruby, '2.2.2' # TODO

# nokogiri はシステムライブラリを使うため bundle install にオプションを指定する
set :bundle_env_variables, { nokogiri_use_system_libraries: 1 }

namespace :deploy do
  desc 'restart application'
  task :restart do
    invoke 'unicorn:restart'
  end
end
after 'deploy:publishing', 'deploy:restart'

config/deploy/production.rbを編集。以下テンプレ。

# server-based syntax
# ======================
server 'xx.xx.xx.xx', user: 'ec2-user', roles: %w{web app db} # TODO

# role-based syntax
# ==================
role :app, %w{ec2-user@xx.xx.xx.xx} # TODO
role :web, %w{ec2-user@xx.xx.xx.xx} # TODO
role :db, %w{ec2-user@xx.xx.xx.xx} # TODO

# Configuration
# =============
set :stage, :production
set :unicorn_rack_env, "production"
set :branch, 'release' # TODO
set :rails_env, 'production'
set :migration_role, 'db'

# Custom SSH Options
# ==================
set :ssh_options, {
  keys: [File.expand_path('~/.ssh/sample.pem')],
  forward_agent: true,
  auth_methods: %w(publickey)
}

6. ssh設定

ssh keyを生成してssh-agentに登録。

$ ssh-keygen -t rsa -C myapp-production
$ ssh-add ~/.ssh/myapp-production

ssh-agentとは、いろんなサーバーにsshするときにパスフレーズを省略することができるOpenSSHの標準機能で、

ssh-agent が起動すると、 UNIX ドメインソケット(UNIX でプロセス間通信に使われる、ファイルシステムベースの通信経路)を作成し、その名前を環境変数 "SSH_AUTH_SOCK" に設定します。そして、 ssh コマンドなどが起動したときは、このソケット経由で通信を行い、秘密鍵が必要な認証の処理を ssh-agent に代行させます。シェルの環境変数は子プロセスからは変更できないため、代わりに新しいシェルを起動しているわけです。 http://webos-goodies.jp/archives/50672669.html

という仕組みになってるらしい。

GitHubでの設定

GitHubで、先ほど生成したssh keyをDeploy Keyとして登録します。

初回だけsshでの接続確認が必要っぽいです。

$ ssh -T git@github.com

7. デプロイ

ということでデプロイ。

$ bundle exec cap producton deploy

Unicornは以下のようにして起動できます。

$ bundle exec unicorn -D -c /var/www/myApp/current/config/unicorn/production.rb -E production

直面した問題

デプロイ作業中のエラーログです。上での設定ファイルのテンプレなどにはここでの修正も反映してまっす。

rbenvのパス問題

リモートで使うRubyのversionが指定できてなかった。capistrano/rbenvの設定方法を確認して指定する。

# config/deploy.rb
# rbenv Ruby version
set :rbenv_ruby, '2.2.2'

それでも見つからないと思ったら、どうやらrbenvのパスが違うみたい。 multistage 環境で capistrano-rbenv を使うときは rbenv_path の扱いに注意!とかも読んだのですが結局わからず。

パスがどうしても変わらないので...シンボリックリンクを貼るという荒技。完全によくない解決方法なのはわかってますが解決できませんでした...

$ ln -s /usr/local/rbenv/* ~/.rbenv

pg

bundle stdout: An error occurred while installing pg (0.18.2), and Bundler cannot continue

ひとまず$ gem isntall pg試してみる

'mkmf.rb can't find header files for ruby'

的なエラーが出てくる。ぐぐる

The first link you’ve posted is exactly your case: there is no ruby development environment installed. Development env is needed to compile ruby extensions, which are mostly written in C. http://stackoverflow.com/questions/20559255/error-while-installing-json-gem-mkmf-rb-cant-find-header-files-for-ruby

らしい。ということでRubyのdevelopment環境で走らせるためのライブラリをインストール。

$ sudo yum install ruby-devel

ちょっと進んだ、次のエラーは...

No pg_config... trying anyway. If building fails, please try again with...

pgのconfigファイルがないよと。

Install PostgreSQL on AWS EC2(Amazon Linux AMI 2013.03.1)を参考にPostgreSQLのインストール手順を見直してみる。

pgに関しては、後半になってこんなエラーも出た。

PG::ConnectionBad: could not connect to server: No such file or directory

Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

うん、PostgreSQLが走ってなかったみたいね。

PostgreSQLでは必ず最初に初期化処理することー。

$ sudo /sbin/service postgresql initdb

そして起動、停止、再起動は以下のようにすればok。

## 起動・停止・再起動
$ sudo /sbin/service postgresql start
Starting postgresql service:                               [  OK  ]
$ sudo /sbin/service postgresql stop
Stopping postgresql service:                               [  OK  ]
$ sudo /sbin/service postgresql restart
Stopping postgresql service:                               [  OK  ]
Starting postgresql service:                               [  OK  ]

ユーザー・DBの作成は以下のようにすればmigrationまでうまくいけました。

http://totutotu.hatenablog.com/entry/2015/08/30/PostgreSQL%E3%81%AF%E3%81%98%E3%82%81%E3%81%AE%E3%81%84%E3%81%A3%E3%81%BD

JSのRuntime

ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime.

指定してるブランチで反映するの忘れてた。Gemfileでtherubyracerを使うよう宣言しときます。

と思ったらまだエラー。nodejsをインストールしないといけなかった。

ところが普通にyumでインストールしようとするも見つからず。How to yum install Node.JS on Amazon Linuxを参考にこんな感じのコマンドでインストールします。

$ sudo yum install nodejs npm --enablerepo=epel

The option --enablerepo=epel causes yum to search for the packages in the EPEL repository.

ということでEPELのパッケージも検索してくれます。EPELは確かもっといろんなパッケージを置いてる場所だったよね。この文章が詳しくてよい。

EPEL (Extra Packages for Enterprise Linux) is open source and free community based repository project from Fedora team which provides 100% high quality add-on software packages for Linux distribution including RHEL (Red Hat Enterprise Linux), CentOS, and Scientific Linux. Epel project is not a part of RHEL/Cent OS but it is designed for major Linux distributions by providing lots of open source packages like networking, sys admin, programming, monitoring and so on. Most of the epel packages are maintained by Fedora repo.

Via http://www.tecmint.com/how-to-enable-epel-repository-for-rhel-centos-6-5/

Bundler

SSHKit::Runner::ExecuteError: Exception while executing as ec2-user@54.65.96.201: bundle...bundle stdout: bundler: command not found: unicorn Install missing gem executables with bundle install

Bundlerは入ってるっぽい。

$ bundle -v
Bundler version 1.10.6

サーバーに接続して$ bundle install|grep unicornとかやってもどうやらインストールでいてない。ちゃんとGemfileみてみたら、そうだ、こっちはGitHubからfetchしてるから内容はリモートのHEADになるんだった。

あまりの凡ミスに悲しくなるが、一応つまずく人もいるかな(未来の自分ではないことを願う...笑)と思い書き留めておく。

secret_key

app error: Missing secret_token and secret_key_base for 'production' environment, set these values in config/secrets.yml (RuntimeError)

デプロイ後反映されないので、Railsapp/unicorn.rbを確認すると、secret_tokentとsecret_key_baseがないよと。

そうだった。環境変数から読み込む設定にしてたんだった。設定する。 

以下に従って環境変数を定義すれば大丈夫になりました。

http://stackoverflow.com/questions/23180650/how-to-solve-error-missing-secret-key-base-for-production-environment-on-h

ttyが必要

以下で解決しました。

Sudo: Sorry, You Must Have a Tty to Run Sudo - Shell Tips!

Capistranoでsudo実行時にttyエラー - Qiita