nginx-lua から fluentd へログを送信する

  • 31
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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 を使用するなどの工夫が必要そうです。

この投稿は nginx Advent Calendar 20157日目の記事です。