NGINX Unit + Ruby + Sinatra

created: 2018/03/25 17:39 updated: 2018/03/25 17:43

これまでNGINX Unitは触ったことがなかったが、先日リリースされたunit-0.7でRubyがサポートされたようなので、NGINX Unit側のコードも読みつつ動かしてみることにした。

ビルド

普段使いのmacOSの上で動作確認したかったので、ソースコードからビルドする。
ビルド時の環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion:    10.13.3
BuildVersion:    17D102
$ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]

ドキュメント通りにconfiguremakeなどを実行すれば特に問題なくビルドは完了した。
(が、普段使いの環境なので要求されるライブラリ等が既に導入済みだっただけかもしれない)

$ curl -O https://unit.nginx.org/download/unit-0.7.tar.gz
$ tar -xf unit-0.7.tar.gz
$ cd unit-0.7
$ ./configure --prefix=/path/to/installed
$ ./configure ruby
configuring Ruby module
checking for Ruby ... found
 + Ruby version: 2.5.0
 + Ruby module: ruby.unit.so
$ make
$ make install
$ cd /path/to/installed

今回はprefixを指定して所定のinstalledというディレクトリに配置した。

実行

sbinディレクトリ以下にunitdというバイナリがあるのでそれを実行すればOK。
PIDファイルやログファイルをオプションで指定できるが、とりあえず指定しなくても動く。

$ sbin/unitd
2018/03/25 16:29:56 [info] 19685#7583311 unit started

実行後はPIDファイルやログファイル、設定ファイル読み書き用のUnixドメインソケットが生成される。

$ ls -g
total 16
drwxr-xr-x  3 staff   96  3 25 16:29 build
srw-------  1 staff    0  3 25 16:29 control.unit.sock
drwxr-xr-x  3 staff   96  3 25 16:29 sbin
-rw-------  1 staff  371  3 25 16:29 unit.log
-rw-r--r--  1 staff    6  3 25 16:29 unit.pid

設定ファイル

NGINX Unitでは専用のUnixドメインソケットからHTTPプロトコルを介して設定の反映と確認を行うらしい。
設定ファイル自体のフォーマットはJSON。

http://unit.nginx.org/configuration/#configuration

Rubyのみを動かす必要最低限の設定は次のような感じになるはず。
いわゆるRackに準拠したオブジェクトを実行してくれる様子。

http://unit.nginx.org/configuration/#ruby-application

{
  "listeners": {
    "*:8080": {
      "application": "ruby_sample"
    }
  },
  "applications": {
    "ruby_sample": {
      "type": "ruby",
      "processes": 2,
      "user": "murakmii",
      "group": "staff",
      "script": "/path/to/installed/config.ru"
    }
  }
}

ソースを読む

実行方法や設定方法は大体わかったので、次はNGINX Unit側でRubyのコードをどう実行しているかを読む。
"Added Ruby support"というコメントのコミットがあるので、その辺り。

https://github.com/nginx/unit/commit/37051b6c15cce7d6ab01c50e1086f8ef0b34e93d

nxt_ruby.cが主要な実装で、主に次のような関数が実装されている。

  • nxt_ruby_init: 主要な初期化処理。内部でさらに次の関数を呼び出す
    • nxt_ruby_rack_init: 設定ファイルに記載されたRubyコードを評価してRack準拠のオブジェクトを生成
    • nxt_ruby_rack_env_create: Rack準拠のオプジェクトの#callに引数として与えるHashの雛形生成
  • nxt_ruby_rack_app_run: リクエストを引数にRack準拠のオブジェクトの#callを呼び出す

nxt_ruby_rack_initを読めば設定ファイルのscriptで指定されたRubyコードがどう評価されるかわかりそうだったのでもうちょっと詳しく読む。

https://github.com/nginx/unit/commit/37051b6c15cce7d6ab01c50e1086f8ef0b34e93d#diff-9ae66b3680d539cbaa2671b1711c8d97R193

と言っても実際に行なっている処理はそんなに多くなく、いくつかのrequireを行なった後、
設定ファイルのscriptで指定されたファイルパスをRack::Builder.parse_fileに渡しているだけだった。
同じような処理をRubyで書くと大体こんな感じになると思う。

require "rubygems"
require "rack"

Rack::Builder.parse_file("/path/to/script")

rackgemを必ずrequireするので、これがgem installされていないと動作しない。
注意点はそれぐらいで、割と好き勝手できる印象。

Sinatraを動かす

理屈はわかったのでsinatraを使ってNGINX Unitを動作させてみたい。
bundlerも使えないと実用的ではないのでこれも併用する。(ここまで読んだ結果からすると特に問題なく動くはず

適当なディレクトリにbundlersinatraを入れて、

$ mkdir ~/Desktop/unit_sinatra
$ cd ~/Desktop/unit_sinatra
$ bundle init
$ echo 'gem "sinatra"' >> Gemfile
$ bundle install --path=vendor/bundle

こんな感じで実装する。

# config.ru

ENV["BUNDLE_GEMFILE"] = "/Users/murakmii/Desktop/unit_sinatra/Gemfile" # カレントディレクトリによっては不要かもしれない。未確認

require "bundler/setup"
require "sinatra/base"

class NginxUnit < Sinatra::Base
  get "/" do
    "Hello, NGINX Unit!"
  end
end

run NginxUnit.new

NGINX Unitの設定ファイル(config.jsonとしておく)では上記のコードを単にscriptで指定するだけ。

{
  "listeners": {
    "*:8080": {
      "application": "ruby_sample"
    }
  },
  "applications": {
    "ruby_sample": {
      "type": "ruby",
      "processes": 2,
      "user": "murakmii",
      "group": "staff",
      "script": "/Users/murakmii/Desktop/unit_sinatra/config.ru"
    }
  }
}

config.ruconfig.jsonを記述したらcurlでNGINX Unitに設定を流し込む。
(ここで設定や実装が間違っているとログ等でエラーが出力されるので要確認)

$ curl -X PUT -d @./config.json --unix-socket ./control.unit.sock localhost
{
    "success": "Reconfiguration done."
}

あとは localhost:8080 へアクセスしてレスポンスが表示されればOK

Hello, NGINX Unit!

所感

  • 個人的にはハマりどころが少なくていい感じ
  • Rubyから見た場合はただのRackサーバーなので、Railsも動くんじゃないですかね
  • コードの更新とかはGracefulにできるんだろうか
  • プロセス数は設定可能なのでUnicorn代替にはなり得そう
  • パフォーマンス見てません

な感じ感じ。