Railstutorial番外編part1 〜 VMにアプリをdeployするまで
About
RailsTutorialを以前一通りやったのですが、やって終わりになってしまっていたので何かやろうと考えました。アプリを拡張するのもいいのですが、私はインフラの人間なので、実際に運用できる相当の状態にまで持って行こうと思ってやってみました。長くなりすぎたので、一旦VMへのデプロイまでをこの記事のスコープとします。
目標
- VMでwebサーバ/dbサーバを作る
- APPサーバとしてunicornを導入する
- dbサーバをpostgresqlに変更
- capistranoを使ってVMにデプロイする
- webサーバにnginxを導入し、リバースプロキシさせる
実際やったPRはこちら。
https://github.com/krossblack/sample_app/pull/1
https://github.com/krossblack/sample_app/pull/2
殴り書きしながらやったので、要点を整理しながら記事にします。実際の順番と前後する部分がありますが、次やるときはこうしたいなという思いを込めて変更しています。
サーバを作る
VMの準備
何はともあれデプロイ先のサーバがないと始まらないので、先に作っておきます。Vagrantは1.7.4を利用しています。
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure(2) do |config| config.vm.box = "bento/centos-7.1" config.ssh.insert_key = false config.vm.define :web do |web| web.vm.network "private_network", ip: "192.168.34.40" web.cache.scope = :box if Vagrant.has_plugin? 'vagrant-cachier' end config.vm.define :db do |db| db.vm.network "private_network", ip: "192.168.34.41" db.cache.scope = :box if Vagrant.has_plugin? 'vagrant-cachier' end end
vagrant1.7.x以降デフォルトの鍵が各machine毎に変更されるようになってしまい、面倒なので私はconfig.ssh.insert_key = false
の設定を入れて、鍵はどのVMでも共通のものを利用するようにしています。
Railsが動く環境を変更する
本番運用を考えた際に、WEBrick/sqlite3の構成はパフォーマンスや運用の面で実用に耐えない可能性があります。そこでまずは構成を見直します。今回はよく利用されるUnicorn/Postgresqlに変更してみます。
Unicornを導入する
$ mkdir -p config/unicorn $ touch config/unicorn/development.rb $ cat config/unicorn/development.rb
# Railsのルートパスを求める。(RAILS_ROOT/config/unicorn.rbに配置している場合。) rails_root = '/var/www/my_app_name' # RAILS_ENVを求める。(RAILS_ENV毎に挙動を変更したい場合に使用。今回は使用しません。) # rails_env = ENV['RAILS_ENV'] || "development" # 追記に記載してます。入れた方がいいです。 ENV['BUNDLE_GEMFILE'] = rails_root + "/Gemfile" # Unicornは複数のワーカーで起動するのでワーカー数を定義 # サーバーのメモリなどによって変更すること。 worker_processes 2 # 指定しなくても良い。 # Unicornの起動コマンドを実行するディレクトリを指定します。 # (記載しておけば他のディレクトリでこのファイルを叩けなくなる。) working_directory "#{rails_root}/current" # 接続タイムアウト時間 timeout 30 # Unicornのエラーログと通常ログの位置を指定。 stderr_path 'log/unicorn_stderr.log' stdout_path 'log/unicorn_stderr.log' # Nginxで使用する場合は以下の設定を行う。 # listen File.expand_path('../../tmp/sockets/unicorn.sock', __FILE__) # とりあえず起動して動作確認をしたい場合は以下の設定を行う。 listen 8080 #listen "#{rails_root}/current/tmp/sockets/unicorn.sock" # ※「backlog」や「tcp_nopush」の設定もあるけど、よくわかって無い。 # プロセスの停止などに必要なPIDファイルの保存先を指定。 pid "#{rails_root}/current/tmp/pids/unicorn.pid" # 基本的には`true`を指定する。Unicornの再起動時にダウンタイムなしで再起動が行われる。 preload_app true # 効果なしとの記事を見たので、コメントアウト。 # GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true # USR2シグナルを受けると古いプロセスを止める。 # 後述するが、記述しておくとNginxと連携する時に良いことがある。 before_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! old_pid = "#{server.config[:pid]}.oldbin" if old_pid != server.pid begin sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU Process.kill(sig, File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH end end end after_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection end
こちらを参考にしました。
path周りの設定が参考にした記事と違ったので、変更しています。またlistenの設定はUnixドメインソケットではなくポートでアクセスするようにしています。後々nginxと接続する予定ですが、この時点でUnixドメインソケットにしてしまうと稼働確認が面倒になるので、一旦ポートにしておくべきです。
設定ファイルができたら稼働確認します。
$ bundle exec unicorn_rails -c config/unicorn/development.rb -D
実際にブラウザからアクセスして確認しましょう。 http://localhost:8080
Postgresqlを導入する
次にDBを変更します。まずは手元のMacで動作確認したいので、Postgresqlをインストールします。
$ brew install postgres $ ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents $ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
私の環境ではすでにpostgresql設定してあったので、こちらを参考にしました。
Mac OS X YosemiteにPostgreSQLをインストール - Qiita
$ psql postgres
アプリからpostgresqlに接続するためにgemを追加します。
gem 'pg'
そして、database.ymlを修正します。
# SQLite version 3.x # gem install sqlite3 # # Ensure the SQLite 3 gem is defined in your Gemfile # gem 'sqlite3' # default: &default pool: 5 adapter: postgresql encoding: unicode user: <%= ENV['DATABASE_USER'] %> password: <%= ENV['DATABASE_PASSWORD'] %> host: <%= ENV['DATABASE_HOST'] %> development: <<: *default database: sample_app_development
ハードコードするのではなくて、環境変数から読めるようにするとstage毎に設定変更がいらないので楽ですし、publicなリポジトリに公開する際に認証情報をコードに持たせなくてよいのでおすすめです。dotenvやdirenvを使うといいと聞きましたが、まだ未検証です。
この時点でうまく動くか検証しましょう。Postgresqlの場合、最初にinitdbコマンドを実行する必要があります。brewでインストールする場合は不要かもしれません。
$ service postgresql initdb $ be rake db:create $ be rake db:migrate
この時点でアプリを動かしてみます。dbへのアクセスを確認したいので、sign up
で新規ユーザを作るなどして動作確認します。テストを動かすでもいいかもしれません。
RailsアプリがVMにデプロイできるようにする
ミドルウェアをVMにインストールする前にアプリをデプロイする準備をします。どんなミドルウェアが必要かわからなかったので、先にデプロイの仕組みづくりをした後にアプリを動かしながら、必要なミドルウェアをインストールしていきました。
デプロイにはcapistranoを利用します。Gemfileに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
VMにデプロイするように設定ファイルを作成します。
$ cat config/deploy/development.rb
ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call set :url, 'http://localhost/' set :repo_url, 'https://github.com/krossblack/sample_app' set :ssh_options, { user: 'app', keys: %w(~/.vagrant.d/insecure_private_key), forward_agent: false, auth_methods: %w(publickey) } role %w(web db), "192.168.34.40"
capistrano実行時にbundle install
したいので、設定を追加します。
# Gemfile gem 'capistrano-bundler'
# Capfile require 'capistrano/bundler'
Railsを動かすのに必要なミドルウェアを導入する
cookbookの準備
準備ができたので、cookbookを作っていきます。
$ mkdir chef && cd chef $ bundle exec knife init . $ bundle exec knife cookbook <cookbook> -o site-cookbooks
上記のように作成していきます。普段ならアプリとサーバ側でGitHubリポジトリを分けるのですが、気楽にやっているのでchefというディレクトリを掘ってそこで開発することにしています。
cookbookの開発
- ruby
- git
- nginx
- postgresql
- rails関連のレシピ
今回rbenvを使ってrubyを導入しようとしましたがデフォルトで導入されるrubyで諦めました。
VMで動かせるようになるまでに起きたトラブルまとめ
unicornが起動しない
/usr/local/share/gems/gems/bundler-1.11.2/lib/bundler/runtime.rb:80:in `rescue in block (2 levels) in require': There was an error while trying to load the gem 'uglifier'. (Bundler::GemRequireError)
このエラーはuglifierが利用しているjsのエンジンがないということらしいです。今回はtherubyracer
を導入することで解決しました。
postgresqlにアクセスできない
これは2点はまってました。1点目はそもそもリモートからのアクセスを拒否する設定になっていました。デフォルトではlocalhostからの接続のみ許可するようです。
Enable remote connection · krossblack/sample_app@98f06b3 · GitHub
2点目は認証の部分です。postgresqlの認証には複数種類のパターンがあります。今回は特に認証を設けないことにしました。最終的にAWSで運用するとした際にDBはプライベートネットワークにあり、外部からのネットワークは基本的にない想定なので。いろいろ認証見たのですが、やるならssl認証かなと思いましたがどれがいいのでしょうかね。
unicornがcapistranoから起動しない
deploy後に特定のタスクの実行を定義することでdeploy後に起動するようになります。
Fix task · krossblack/sample_app@7334f50 · GitHub
nginxを使ってリバースプロキシさせる
ここまででVM上でアプリが動く状態になりましたが、unicornが直接リクエストをさばいている状態です。静的コンテンツなどの配信はnginxに分散させunicornはアプリの処理に集中させます。その前にunixドメインソケットでnginx -> unicornをつなぐようにします。
config/unicorn/development.rb
を下記のように修正します。
listen "#{rails_root}/current/tmp/sockets/unicorn.sock"
upstream unicorn{ server unix:/var/www/my_app_name/current/tmp/sockets/unicorn.sock; } server { listen 80; server_name sample_app; access_log /var/log/nginx/sample_app.access.log ltsv; error_log /var/log/nginx/sample_app.error.log; location / { try_files $uri $uri/index.html $uri.html @unicorn; } location ~* \.(js|html|txt|ico)$ { root /var/www/my_app_name/current/public; } location /heatbeat { allow all; try_files $uri $uri/index.html $uri.html @unicorn; } location @unicorn { satisfy any; allow all; proxy_pass http://unicorn; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; proxy_set_header Host $http_host; proxy_redirect off; } }
上記のようにunicornのsocketの場所をnginx側で指定します。
これで細かい部分を除き、VM上でアプリを動かす所まで行き着きました。
まとめ
普段デプロイ周りはバックエンドエンジニアに任せてしまっているので、非常に苦労しました。一人でアプリを作って運用まで持っていくという経験は大切だと思うので、可能な限り本番相当の環境(AWS)にのせる所までやりたいなと思います。