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

techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

ChefでNginx + Unicorn + Railsの開発環境を構築する

Berkshelf Chef Ruby on Rails nginx

これまでMacにRailsの開発環境を構築していましたが、久しぶりに触ると環境がおかしくなってたり、いちいち環境構築し直したりがめんどくさ過ぎたので、Chefで環境の構築と管理ができれば幸せになれそうな気がしてました。
また、できるだけ本番に近い環境で開発を行っておけば、デプロイも簡単にできそうです。

ということで、今回はChefとVagrantを利用して、仮想環境にNginx + Unicorn + Railsの環境を構築してみました。

Chefリポジトリを準備する

まずはChefリポジトリに必要なCookbookを揃えていきます。
前々回を参考にしつつ、Chef Supermarketを利用します。
Berkshelfファイルを以下のように編集します。

   source "https://api.berkshelf.com"
   
   cookbook 'rvm', '~> 0.9.4'
   cookbook 'sqlite', '~> 1.1.3'
   cookbook 'git'
   cookbook 'build-essential'
   cookbook 'openssl', '~> 4.4.0'
   cookbook 'yum-epel'

とりあえず必要そうなパッケージだけにしておきます。
Nginxは前回自分で作ったCookbookを今回もそのまま利用するので、 Berkshelfには記述しません。 編集したらberks vendor cookbookコマンドでCookbookを取得します。

次に、nodeのJSONファイルを編集していきます。Berkshelfで取得したパッケージとNginxのレシピをrun_listに列記します。

  {
    "run_list": [
      "recipe[rvm::system]",
      "recipe[sqlite]",
      "recipe[git]",
      "recipe[build-essential]",
      "recipe[openssl]",
      "recipe[yum-epel]",
      "recipe[nginx]"
    ],
    "automatic": {
      "ipaddress": "centos6-chef"
    },
    "rvm": {
      "user": "root",
      "default_ruby": "ruby-2.3.1",
      "user_default_ruby": "ruby-2.3.1",
      "rubies": ["ruby-2.3.1"]
    }
  }     

RubyはRVMで管理することにします。NodeオブジェクトのJSONファイルでRVMでインストールするRubyを指定できるようです。
せっかくなので、Rubyのバージョンは2016/6/9時点で最新の2.3.1をインストールしておきます。

あとはknife solo bootstrap centos6-chefコマンドでゲストOSに環境を適用します。
※ゲストOSは Chef soloでapacheをインストールしてみる - techium で使ったものをそのまま利用しています。あらかじめvagrant destroyしておきます。

ゲストOS上で必要なものを揃える

最終的にはChefで必要なパッケージ類は管理したいところですが、Gemパッケージのインストールがうまくいかず。。
個人的にNginx + Unicorn + Railsの環境を構築するのは今回が初めてなので、とりあえず今回は手作業で環境を構築しておきます。

ということで、ここからはゲストOS上での作業となります。
まずはRailsをインストールしてプロジェクトを作成します。
su -でスーパーユーザとなり、gem install railsであっさりインストールできました。

Railsがインストールできたので、rails newで新規プロジェクト(railsapp)を作ります。
前回Nginxをインストールした時、nginx.confのrootディレクトリは/usr/share/nginx/html/となっていたのでひとまずそこに新規プロジェクトを作ることにします。

# cd /usr/share/nginx/html/
# rails new railsapp

新規プロジェクトができたので、./railsapp/Gemファイルに必要なGemパッケージを記述します。
今回はアプリケーションサーバとしてUnicornを使いますので、以下を追記します。

# gem 'unicorn'
# gem 'therubyracer', platforms: :ruby

ついでにtherubyracerのコメントアウトも解除しておきます。
ここでbundle installを実行してGemパッケージをインストールします。

さて、あとはunicorn.rbとnginx.confの各設定ファイルを記述します。
Unicornは使ったことがないので、unicorn.rbには以下を参考にひとまず必要最低限の設定を記述します。

Rails unicorn · herokaijp/devcenter Wiki · GitHub

# -*- coding: utf-8 -*-
application = "railsapp"
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
working_directory "/usr/share/nginx/html/railsapp"

timeout 15
preload_app true  

listen "/tmp/unicorn.sock"
pid "/tmp/unicorn.pid"

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

最後にnginx.confにUnicornの設定を追記します。
一度編集したnginx.confはtemplateとしてChefリポジトリで管理するようにします。

・・・略・・・
    upstream unicorn-unix-domain-socket {
        server unix:/tmp/unicorn.sock;
    }

    server{
        listen 80;
        server_name 198.168.33.10;

        root /usr/share/nginx/html/railsapp/public;

        location / {
            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-unix-domain-socket;
        }
    }

rootは今回作成したrailsappプロジェクトとします。
ここまでやれば、ホストOS側のブラウザでRailsのWelcome aboardにアクセスできているはずです。 f:id:uentseit:20160610000124p:plain

あとはいつも通りRailsの開発を行っていきましょう。 Gemパッケージ類の環境設定もChefで管理できるようになることが今後の課題ですが、 今回Chefを利用しただけでもかなり楽に構築できたように思います。

502 Bad Gatewayエラーとなる場合

SE Linuxが効いている可能性が高いです。

# getenforce
Enforcing

Permissiveにしておきます。

# setenforce Permissive