LoginSignup
105
99

More than 5 years have passed since last update.

NGINX 1.9が汎用TCPサーバとして使えるようになっていた件

Last updated at Posted at 2016-02-08

はじめに

一年程前にリリースされた Nginx v1.9.0で、Streamモジュールが追加されました。
Streamモジュールを使うと、任意のポートでNginxがTCPの接続を待ち受けるよう設定できます。

nginx.conf
stream {
    server {
        listen 12345;
        # ...
    }
}

この機能は、例えばNginxをTCPロードバランサとして構成する時に威力を発揮するようです。

強力そうな機能ですが、個人的にNginxをTCPロードバランサとして使っていなかったため、特に機能を活用する場もなくスルーしていました。

ところで、つい先日公開されたNginxのモジュール、stream-lua-nginx-module、及び、stream-echo-nginx-moduleを組み合わせれば、任意のポートで待ち受けるTCPサーバをLuaで記述できることに気付きました。

ngx_lua_stream_moduleについて

ngx_stream_lua_module - Embed the power of Lua into Nginx stream/TCP Servers.

OpenRestyの作者、 Yichun "agentzh" Zhang氏により開発されています。
現時点のステータスは「ほとんど機能するものの実験的」とのこと。

名前 ngx_lua_stream_module
作者 Yichun "agentzh" Zhang
必要なバージョン Nginx 1.9.7以降
名前 ngx_echo_stream_module
作者 Yichun "agentzh" Zhang
必要なバージョン Nginx 1.9.0以降

EchoサーバやDaytimeサーバをLuaで記述・登録すれば、Nginx上でそれらのサーバが動く構成に出来ます。
・・・先ほどの例だとこのような感じで、サーバをLuaで書けます。

nginx.conf
stream {
    server {
        listen 12345;
        content_by_lua_block {
          -- サーバのLua実装をここに記述!! --
        }
    }
}

更に、コアの部分ではnginxの高速性を引き継ぐため、自動的に(!)、かなりパフォーマンスが高くなっていました。

ビルド

cd nginx-1.9.10
./configure \
   --with-stream \
   --with-stream_ssl_module \
   --add-module="../stream-lua-nginx-module-master" \
   --add-module="../stream-echo-nginx-module-master" \
...後略...

Dockerイメージ (動作確認中・・・)

alpine-nginx-stream-lua-module

動作確認環境

  • ArchLinux (Kernel 4.1.16-1-lts on Vultr.com, 768MB RAM / 1x CPU / SSD)
  • Openresty 1.9.7.3 (with bundled LuaJIT 2.1 beta)
  • stream-lua-nginx-module (git verstion, from master branch)
  • stream-echo-nginx-module (git verstion, from master branch)

使ってみる

公式のドキュメント、及びテストケースが充実しているため、直接参照するのがてっとり早いと思います。

接続を受け付けて文字列を返すだけなら、stream_echo_nginx_moduleのAPIで足りるかもしれません。
ただし、制御構造が書けない、nginxの内部変数にアクセス出来ないなどかなり制限が多いので、大抵はstream_lua_nginx_moduleを使うことになると思います。

簡単なプロトコルを実装して動かしてみます。
(実装は厳密なものではありません、念のため)

Daytimeプロトコル

RFC 867
Port 13

実装

nginx.conf
stream {
  # Daytime Protocol
  server {
    listen 13;
    content_by_lua_block {
      ngx.say(ngx.localtime());
    }
  }
}

確認

$ nc 127.0.0.1 13
2016-02-08 19:52:49

Discardプロトコル

RFC 863
Port 9

実装

nginx.conf
# You need to build Nginx with the stream-echo-nginx add-on.
# https://github.com/openresty/stream-echo-nginx-module

stream {
  server {
    listen 9;
    echo_discard_request;
    echo_sleep 3600;      # in sec
  }
}

確認

$ nc 127.0.0.1 9
hogefuga ← ENTERキー

(何もecho backされない)

Chargenプロトコル

RFC 864
Port 19

実装

nginx.conf
# Chargen server
stream {
  server {
    lua_code_cache on;
    lua_check_client_abort on;
    listen 19 so_keepalive=2m:30s:4 reuseport;
    tcp_nodelay off;
    content_by_lua_file /home/dseg/devel/openresty_service/chargen.lua;
  }
}
chargen.lua
-- chargen.lua
-- RFC 864 (https://tools.ietf.org/html/rfc864)

-- Init character table
local chars = {}
for j = 0x21, 0x7E do table.insert(chars, string.char(j)) end

-- Main loop
local size, i, p = #chars, 0, ngx.print
while true do
    local ok, err = p(chars[i % size + 1])
    -- If client force abort, error will occure
    if not ok then
        ngx.log(ngx.ERR, "chargen.lua: Socket Write Error. ", err)
        ngx.exit(200)
        break
    end
    i = i + 1
    -- wrap line
    if (i % 72) == 0 then p("\r\n") end
end

確認

% nc 127.0.0.1 19
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
ijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQR
STUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<
=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&
'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn
opqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWX
YZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@AB
CDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,

← CTRL+C

Echoプロトコル

RFC 862
Port 7

実装

nginx.conf
stream {
  server {
    lua_code_cache on;
    lua_check_client_abort off;
    listen 7 so_keepalive=3m:30s:4 reuseport backlog=128;
    tcp_nodelay off;
    content_by_lua_file /home/dseg/devel/openresty_service/echo.lua;
  }
}
echo.lua
local p = ngx.say
local sock = assert(ngx.req.socket(true))
if not sock then
    ngx.log(ngx.ERR, "Failed to get the socket. Exiting...")
    return
end

-- Main loop
while (true) do
    local data, err = sock:receive() -- Read a data from downstream
    if not err then
        p(data) -- output data
    else
        ngx.log(ngx.ERR, err)
        ngx.exit(200)
        break
    end
end

確認

$ telnet 127.0.0.1 7
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello ←ENTERキー
hello
^]
telnet> q
Connection closed.

ベンチマーク

Echoサーバのベンチマークを取ってみます。
計測には@methaneさん作のechoserverと、そのclientを利用させて頂きました。

# Nginx + Lua (先述のecho.lua)
$ echoserver/client -c50 -o2 -h10000 -p7 127.0.0.1
Throughput: 64447.98 [#/sec]

# C++ epoll echoサーバ起動
$ echoserver/server_epoll

$ echoserver/client -c50 -o2 -h10000 -p5000 127.0.0.1
Throughput: 78058.97 [#/sec]

64448 ÷ 78059 * 100 = 82.5 (%)

Nginx+Lua は C++ Epoll版の 80%程の速度でした。これはなかなか速いのでは?

開発時のTips

lua_code_cache off と luaファイル外出し

通常nginx.conf内にLuaのコードを記述する形になりますが、コードを変更する度にNginxのリロードが必要となります。これはさすがに面倒です。
そこで、Luaのキャッシュ機能をオフにし、更に外部ファイルからLuaのコードを読み込むようにすることで、開発効率が改善されます。

nginx.conf
location /something {
  lua_code_cache off;
  content_by_lua_file /path/to/lua/file.lua;
}

スクリプトの確認にrestyコマンドを使う

Openrestyに付属のrestyコマンドが開発時にとても便利です。

restyコマンドは、裏で最小構成のnginxデーモンを立ち上げ、指定したLuaのコードを init_worker_by_lua 命令内で実行してくれます。
ngx_luaのAPIに馴染みがない使い始めの頃、コマンドラインでAPIを使ってみるのに大変重宝しました。
内部でタイマーハンドラを使っているので、一部はrestyコマンドから実行できない制限もありますが(ngx.on_abortハンドラ等)、通常は問題ないと思います。

echo ngx.say("hello") > test.lua

$ resty -e 'ngx.say ("hello")'
hello

$ resty test.lua
hello

おわりに

確かにNginx + Luaで高パフォーマンスのTCPサーバが作れそうですが、問題は「何に活用すれば良いか」、ですかね・・・。

105
99
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
105
99