RailsアプリをNginx+UnicornなAWSにCapistranoでデプロイ
Special Thanks
追記@2016/04/29
最近は環境やconfファイルのsetupはitamaeでやるようになりました。めっちゃ楽ですitamae。。
PostgreSQLの設定は、以下を参考にしていつも自分でやってます。
手順
手順をまとめると、以下のようになります。
- 前準備
- nginxの設定
- 設定ファイルの用意
- 権限変更
- Capistrano設定
- Unicorn設定
- ssh設定
- デプロイ
myApp
の部分は適宜読み替えてやってください。また、設定ファイルのうち編集する必要がある場所にTODO
とコメントを入れてます。
ローカル
1の前準備はローカルで行ってください。
1. 前準備
公開鍵をサーバーに送信しておきます。
$ scp -i ~/.ssh/hogehoge.pem ~/.ssh/id_rsa.pub ec2-user@ec2_server:~
サーバー
ここからサーバー上での作業です。sshで接続しておいてください。
2. nginx
今回作成するアプリ用にnginxの設定ファイルを作成します。パスはetc/nginx/conf.d/myapp.conf
みたいな感じです。
デフォルトで作成されてるnginx.conf
でconf.d
以下の*.conf
を全て自動でincludeする設定になってます。
upstream unicorn_server { server unix:/tmp/unicorn.sock fail_timeout=0; } server { listen 80; client_max_body_size 4G; server_name example.com; # TODO keepalive_timeout 5; root /var/www/myApp/current/public; # TODO access_log /var/log/nginx/myApp_access.log; # TODO error_log /var/log/nginx/myApp_error.log; # TODO 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; # TODO } error_page 500 502 503 504 /500.html; location = /500.html { root /var/www/myApp/current/public; # TODO } }
3. 設定ファイルの用意
gitでignoreして管理していないsecrets.yml
とかdatabase.yml
とかのファイルがあればここで準備しておきます。
自分は/var/www/myApp/shared
以下に.env
とかdatabse.yml
を突っ込んでおいて、Capistranoのタスクでデプロイのフローでcurrentにcpしてくる設定にしています。Capistranoの設定のところでやります!
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.3.0' # 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'
以下のようにCapistranoのタスクを作っておいて自動でshared
以下の.env
やdatabase.yml
をcpしてくる的なことをするのも結構便利なのでオススメです。
# config/deploy.rb # ... namespace :deploy do desc "Copying database.yml" task :config_database do on roles(:all) do within release_path do db_config_file = "/var/www/myApp/shared/database.yml" execute :cp, "#{db_config_file} ./config/database.yml" end end end desc "Set Environment Values" task :set_env_values do on roles(:all) do within release_path do env_config = "/var/www/myApp/shared/.env" execute :cp, "#{env_config} ./.env" end end end desc "Restarting application" task :restart do invoke "unicorn:restart" end end before "deploy:updated", "deploy:config_database" before "deploy:updated", "deploy:set_env_values" 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, 'deployment/production' # TODO set :rails_env, 'production' set :migration_role, 'db' # Custom SSH Options # ================== set :ssh_options, { keys: [File.expand_path('~/.ssh/sample.pem')], # TODO forward_agent: true, auth_methods: %w(publickey) }
6. Unicorn設定
config/unicorn/production.rb
を以下のようにします。
# production.rb root = "/var/www/myApp/current" # TODO working_directory root pid "#{root}/tmp/pids/unicorn.pid" stderr_path "#{root}/log/unicorn.log" stdout_path "#{root}/log/unicorn.log" listen "/tmp/unicorn.sock" worker_processes 3 timeout 30 preload_app true before_exec do |server| ENV["BUNDLE_GEMFILE"] = "#{root}/Gemfile" end before_fork do |server, worker| # the following is highly recomended for Rails + "preload_app true" # as there"s no need for the master process to hold a connection if defined?(ActiveRecord::Base) ActiveRecord::Base.connection.disconnect! end # Before forking, kill the master process that belongs to the .oldbin PID. # This enables 0 downtime deploys. old_pid = "#{root}/tmp/pids/unicorn.pid.oldbin" if File.exists?(old_pid) && server.pid != old_pid begin Process.kill("QUIT", File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH # someone else did our job for us end end end after_fork do |server, worker| # the following is *required* for Rails + "preload_app true", if defined?(ActiveRecord::Base) ActiveRecord::Base.establish_connection end end
他にもstagingなどの環境ごとの設定ファイルを必要に応じてconfig/unicorn
以下に作成してください。
7. ssh設定
$ 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にDeploy Keyを登録
GitHubで、先ほど生成した公開鍵をDeploy Keyとして登録します。
登録したら、初回だけsshでの接続確認が必要っぽいので通しておきます。
$ ssh -T git@github.com
既存のssh鍵の記憶が邪魔になるときは、以下で記憶を削除できます。
$ ssh-add -D
8. デプロイ
ということでデプロイ。
$ bundle exec cap production 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までうまくいけました。
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
andsecret_key_base
for 'production' environment, set these values in config/secrets.yml (RuntimeError)
デプロイ後反映されないので、Railsのapp/unicorn.rb
を確認すると、secret_tokentとsecret_key_baseがないよと。
そうだった。環境変数から読み込む設定にしてたんだった。設定する。
以下に従って環境変数を定義すれば大丈夫になりました。
ttyが必要
以下で解決しました。