nginx Advent Calendar 2015 7日目 兼 fluentd Advent Calendar 2015 6日目のエントリです。
nginx-lua (https://github.com/openresty/lua-nginx-module) から fluentd にログを送信する方法を紹介します。
Lua から fluentd へログを送信するライブラリとして fluent-logger-lua というものがありますが、これは LuaSocket ライブラリを使用しているため nginx-lua では使いづらいので、ngx.socket.tcp を使用して自前で送信してみましょう。
fluentd の forward protocol は3パターンの入力フォーマットがあるのですが、一番単純なフォーマットは [tag, time, event]
の形式で、この形に MessagePack のバイナリを組み立てて TCP でそのまま流すだけでログを送信することができます。
例として「リクエストされた URL引数、User-Agent、アクセス元IPアドレスを fluentd へ送信する」というアプリケーションを書いてみます。
msgpack = require("msgpack")
local sock = ngx.socket.tcp()
sock:settimeout(5000)
-- URL引数を取得
local args, err = ngx.req.get_uri_args()
if not args then
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 127.0.0.1:24224 の fluentd に接続
local ok, err = sock:connect("127.0.0.1", 24224)
if not ok then
return ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end
args["user_agent"] = ngx.var.http_user_agent
args["remote_addr"] = ngx.var.remote_addr
-- forward 形式のメッセージを組み立てて投げる
local bytes, err = sock:send( msgpack.pack({"access", ngx.time(), args}) )
if err then
return ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end
sock:setkeepalive(10000) -- keepalive 10 sec.
-- レスポンスを返す
ngx.header.content_type = "text/plain"
ngx.say("ok")
MessagePack のライブラリとして lua-MessagePack を使用しますので、あらかじめ lua_package_path
の場所に配置してください。
これで、適当に URL 引数を付けてリクエストすると fluentd に送信するアプリケーションができました。Webビーコン的なものですね。
ポイントは以下です。
- 時刻取得に
os.time()
ではなくngx.time()
を使用する- システムコールによるオーバーヘッドを避けることができます
-
sock:setkeepalive()
で TCP 接続を使い回す- 指定した時間、socket を close せず別のリクエストに使い回すことができます
ベンチマーク
- Macbook Pro 13 (Early 2015)
- fluentd-0.12.16
- ruby 2.2.2p95
- openresty/1.9.3.1
- wrk 4.0.0
上記環境で、ローカルマシンで簡易的なベンチマークを取ってみました。
fluentd は flowcounter-simple を使用して、流れてくるログの流量を集計します。
# fluentd.conf
<source>
type forward
port 24224
</source>
<match access.**>
type flowcounter_simple
unit second
</match>
結果
$ wrk -c 50 -d 10 -t 4 "http://localhost:8000/?foo=bar&bar=baz"
Running 10s test @ http://localhost:8000/?foo=bar&bar=baz
4 threads and 50 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 5.20ms 13.79ms 247.88ms 92.33%
Req/Sec 7.97k 2.16k 14.03k 67.94%
270322 requests in 10.08s, 44.84MB read
Requests/sec: 26809.95
Transfer/sec: 4.45MB
2015-12-07 15:09:36 +0900 [info]: plugin:out_flowcounter_simple count:8571 indicator:num unit:second
2015-12-07 15:09:37 +0900 [info]: plugin:out_flowcounter_simple count:30995 indicator:num unit:second
2015-12-07 15:09:38 +0900 [info]: plugin:out_flowcounter_simple count:33546 indicator:num unit:second
2015-12-07 15:09:39 +0900 [info]: plugin:out_flowcounter_simple count:72508 indicator:num unit:second
2015-12-07 15:09:40 +0900 [info]: plugin:out_flowcounter_simple count:24412 indicator:num unit:second
2015-12-07 15:09:41 +0900 [info]: plugin:out_flowcounter_simple count:30495 indicator:num unit:second
2015-12-07 15:09:42 +0900 [info]: plugin:out_flowcounter_simple count:32792 indicator:num unit:second
2015-12-07 15:09:43 +0900 [info]: plugin:out_flowcounter_simple count:35525 indicator:num unit:second
2015-12-07 15:09:44 +0900 [info]: plugin:out_flowcounter_simple count:32710 indicator:num unit:second
2015-12-07 15:09:45 +0900 [info]: plugin:out_flowcounter_simple count:2314 indicator:num unit:second
2コア4スレッドのマシンで nginx worker_processes 4
で実行したところ、26000req/sec 程度の性能でした。
wrk, nginx, fluentd がそれぞれ CPU を食い合っている状態なのであくまで参考程度ですが、たいていのアプリケーションには十分すぎる性能が出ていると思います。
注意点など
上記サンプルアプリケーションでは、接続先の fluentd が落ちていたりしてログが送信できない場合、503 を返すだけになっています。可用性を高めるためには、送信できない場合に別の fluentd に送るなどの対処が必要でしょう。
また、各種 fluentd の logger には送信できなかったログはメモリ上にバッファリングして次回接続時に再送するなどの機能があるものがありますが、nginx上でリクエストをまたいでメモリ上に確保するのは ngx.shared.DICT を使用するなどの工夫が必要そうです。