fluentdのnginxアクセスログ取得をdockerで試す

最近fluentdを触る機会があったので、調べたことを書きます。
またdockerでいくつか試してみたので、それも公開しておきます。試した内容は以下です。同じようなことを、入出力を変えて何度もやっているだけなので、http2fileだけ見ればだいたいわかると思います。

  • httpリクエストをファイルに書き出す
    http2file
  • nginxのアクセスログをファイルに書き出す
    nginx2file
  • nginxのアクセスログをMySQLに入れる
    nginx2mysql
  • nginxのアクセスログをBigQueryに入れる
    nginx2bq

この記事ではfluentdの設定の仕方に内容を絞り、fluentdの動作の詳細などは書きません(書けません)。

いろいろリンクを貼っていきますがfluentdの公式ドキュメントは英語だけらしいのでほとんど英語です。

fluentdのバージョンはv1.2を使います。v1系はv0.12とは設定のパラメータがけっこう違うので注意して下さい。またtd-agentは使いません。

fluentdとは

データの収集管理ツールです。ここでいう「データ」とは、主にログを念頭においています。

特徴として、入力と出力がプラグインで操作できるという点が挙げられます。
たとえばHTTPリクエストでPOSTされたデータを受け取り、それをファイルに書き出す。nginxのアクセスログをMySQLのテーブルに入れる。プラグインが入っていれば、そういったことが設定を変えることでできます。

td-agent…?

fluentdについて調べているとtd-agentなるものの存在がちらつきます。fluentdとtd-agentは何が違うのでしょうか?
公式のFAQに違いが説明されています。fluentdはgemだけどtd-agentはrpm/deb/dmgパッケージ。プラグインを追加する際に使うコマンドが違う。などなど。
また、たとえばAmazon S3に出力するときに使う out_s3 プラグインはtd-agentにはデフォルトで入っています。しかしfluentdには入っていないので fluent-plugin-s3 をインストールしなければなりません。
今回はfluentdがdockerイメージを用意してくれているので、td-agentではなくfluentdを使います。

dockerイメージについて

ここからはいくつかdockerでfluentdを試してみます。dockerイメージとしてはfluentdのdockerイメージを使用します。
このdockerイメージを使うにあたり、以下に注意します。

  • 設定ファイルのパス
    dockerコンテナの中の /fluentd/etc 以下のパスを環境変数 FLUENTD_CONF で指定するようになっています。今回はこの環境変数に my_fluentd.conf を指定する(docker-compose.ymlを参照)ので、設定ファイルのパスは /fluentd/etc/my_fluentd.conf となります。
    これ以降は主にこのファイルをいじっていきます。
  • オプション
    Fluentd command line optionにfluentdをコマンドラインで扱うときのオプションが書いてあります。今回のdockerイメージを起動する場合は環境変数 FLUENTD_OPT でこれを指定できます。とりあえず -vv を指定し(docker-compose.ymlを参照)、情報をいろいろ出すようにします。不要なら指定しなくてもいいです。
  • ログの出るディレクトリ
    /fluentd/log に出します。dockerホスト側にもログが残るよう、これをマウントしておきます(docker-compose.ymlを参照)。
  • 開くポート
    HTTPリクエストを入力とする場合にだけ関係しますが、ここによるとHTTPリクエストを受け付けるのはデフォルトで9880番ポートになっていますのでこれを開いておきます(これもdocker-compose.yml参照)。
  • プラグインのインストールについて
    普通はfluent-gemを使うと思うのですが、dockerイメージの説明では gem を使っています。とりあえずこれに従います。インストールはDockerfileで行っています。

設定ファイルの基礎知識

fluentd特有の用語がいろいろ出てくるのでまとめておきます。

タグ(tag)

Life of a Fluentd eventによるとfluentdのイベントは tagtimerecordという3要素で構成されています。 time はそのイベントがいつ起きたか。 record はイベントの内容(データの内容)です。では tag とは何かというと、これはそのイベントを分類するときに使うタグです。 foo とか foo.bar とか foo.bar.baz のような文字列です。

後述するhttp2fileでは、HTTPリクエストでデータをPOSTしますが、リクエストの中で http.test のようにタグを指定します。
http2fileの設定ファイルでは

<match http.**>
# 略
</match>

のように書いています。こうすると httphttp.foohttp.foo.barなどのタグにマッチするので、 http.test はこの中で処理されることになります。 foo.bar はマッチしないので、この中では処理されません。
** に似ていますが * は少し違います。 http.*http.foo にマッチしますが http にも http.foo.bar にもマッチしません。(ここ参照。)

別の例として、nginx2fileの設定ファイルでは

<source>
@type tail
# 略
tag nginx.access
# 略
</source>
<match nginx.access>
# 略
</match>

としています。つまり設定ファイルの中で入力のタグ nginx.access を指定しています。

バッファ(buffer)

ドキュメントはここ参照。
バッファは出力プラグインが使用します。たとえばMySQLのテーブルに出力するプラグイン fluent-plugin-mysql で、テーブルに出力する前に受け取ったデータをファイルに一時保存しておいたりします。バッファとしてはファイル(buf_file)の他にメモリ(buf_memory)も使えます。メモリの場合はfluentdが落ちればバッファも消えます。

バッファの設定についてはここを見て下さい。

チャンク(chunk)

バッファの概要バッファの設定方法を見て下さい。
バッファはチャンクで構成されています。とりあえずfluentdの設定をするときに必要な知識はチャンクキーです。fluentd設定ファイルの中に <buffer time> のような部分がありますが、この time がチャンクキーです。たとえばこの部分の中で timekey 60 とすると、60秒単位でのチャンクが作られます。このチャンクの中には、その60秒間のイベントが書き込まれています。そしてすべてのチャンクを合わせたのがバッファ、のようなイメージです。

httpリクエストをファイルに書き出す

http2file

つまり

  • 入力
    httpリクエスト(POSTメソッド)
  • 出力
    ファイル

の場合です。使用するプラグインとしては in_httpout_file で、どちらもデフォルトで使えるようになっています。

設定ファイル

http2file/fluentd/my_fluentd.confです。

  • 入力
    入力のタイプが in_http であることを指定するだけです。
    ポート番号なども指定できますが、すべてデフォルトにしておきます。
  • 出力
    出力のタイプ、出力先ファイルのパスの他、バッファに関する内容を書いておきました。
    出力先ファイルに書きだす前に、バッファ(これもファイルにします)に一時保存します(これもプラグインで制御しています)。
    出力先ファイルのパスに /fluentd/log/file.%Y%m%d-%H%M を指定していることに注意して下さい。ファイル名に日時のプレースホルダを使っています。そして <buffer time> の中でバッファに関する設定を行います。 timekey は60秒とし、 timekey_wait は0秒とします。こうするとそれぞれのログファイルには60秒間のログが格納され、それはその期間が終わると0秒後にファイルに書き込まれます。たとえば12時0分0秒から12時0分59秒までのPOST内容は、この期間内はバッファにためこまれ、12時1分0秒に /fluentd/log/file.20180819-1200_0.log というようなファイルに出力されます(_0 はいまはどうでもいいです。ここのappendオプション参照)。
    はじめ、出力先ファイルパスを /fluentd/log/file.%Y%m%d としていましたが、 insufficient timestamp placeholders in path というエラーが出て起動できませんでした。 <buffer time> では1分おきに別々のファイルに書き出す設定にしているのに、ファイル名は日単位で作るようになっていたためにエラーになったと思われます。
    このあたりはここを見て下さい。

試してみる

ターミナルで http2file ディレクトリに入り

docker-compose build
docker-compose up

して下さい。そのあと別のターミナルを開き、以下のコマンドを打ちます。

curl http://localhost:9880/http.test -X POST -d 'json={"foo": "bar"}'

するとまずデータがバッファに入ります。今回は(dockerホストでの)ディレクトリ ./fluentd/log/buf 以下のファイルに出力されます。
そして1分ほど経つとバッファファイルが消え、 ./fluentd/log/file.20180819-1851_0.log のような名前のファイルに以下の内容が書き込まれます。

2018-08-19T18:51:26+09:00       http.test       {"foo":"bar"}

日時、タグ(http.test)、POST内容が記録されていることがわかります。

nginxのアクセスログをファイルに書き出す

nginx2file

  • 入力
    nginxのアクセスログ
  • 出力
    ファイル

の場合です。使用するプラグインとしては in_tailout_file で、どちらもデフォルトで使えるようになっています。
つまりさっきの場合とは入力だけが違います。 in_tail は名前の通り、 tail -f の内容を入力とするようなプラグインです。

docker関係

nginxのコンテナを追加します。
nginxのアクセスログファイルをfluentdのコンテナでも読めるように、nginxコンテナのログディレクトリをfluentdにマウントします(docker-compose.ymlでやっています)。
また、nginxのコンテナはデフォルトでは /var/log/nginx/access.log は標準出力に出てしまっていたので、こちらのサイトを参考にしてファイルに出すようにしました(nginx/Dockerfile参照)。

設定ファイル

nginx2file/fluentd/my_fluentd.confです。

  • 入力
    入力のタイプ、tail対象のファイルパス(fluentdコンテナ内でのパス)、 pos_file(どこまで読んだかを記録するファイルのパス)、そしてアクセスログファイルのパーサを書きます。
    このパーサですが、fluentdに用意されているパーサをそのまま使っています。ここなどによると、このパーサでは問題があるらしいです…が、問題なくパースできているように見えるので、とりあえずこれを使っておきます。(パーサが改善された?)
  • 出力
    先ほどの例とほとんど同じです。変えたのはタグ名、出力先ファイル名、バッファ名だけです。ここからも、fluentdでは入力と出力が分離されていることがわかります。

試してみる

先ほどと同様にターミナルで nginx2file ディレクトリに入り

docker-compose build
docker-compose up

して下さい。

http://localhost にブラウザでアクセスするとaccess.logには

172.27.0.1 - - [19/Aug/2018:23:11:43 +0900] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" "-"
172.27.0.1 - - [19/Aug/2018:23:11:48 +0900] "GET /favicon.ico HTTP/1.1" 404 571 "http://localhost/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" "-"

のように出力され、fluentdの出力では

2018-08-19T23:11:43+09:00       nginx.access    {"remote":"172.27.0.1","host":"-","user":"-","method":"GET","path":"/","code":"200","size":"612","referer":"-","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36","http_x_forwarded_for":"\"-\""}
2018-08-19T23:11:48+09:00 nginx.access {"remote":"172.27.0.1","host":"-","user":"-","method":"GET","path":"/favicon.ico","code":"404","size":"571","referer":"http://localhost/","agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36","http_x_forwarded_for":"\"-\""}

のようになります。うまくパースされているようです。

nginxのアクセスログをMySQLに入れる

nginx2mysql

  • 入力
    nginxのアクセスログ
  • 出力
    MySQLのテーブル

の場合です。使用するプラグインとしては in_tailfluent-plugin-mysqlです。後者はインストールが必要です。

docker関係

MySQLは最新の8系ではなく5.7を使います(認証周りが面倒なので)。プラグインは mysql/Dockerfile でインストールしています。

あとはMySQLのdockerイメージでの特殊事情をいろいろ考慮します。MySQLのrootパスワードとDB名をdocker-compose.ymlの MYSQL_ROOT_PASSWORDMYSQL_DATABASE で指定します。ここで指定したrootユーザ、DBが作成されます。また、ログを入れるMySQLのテーブルは mysql/docker-entrypoint-initdb.d/create_table.sql で作成します。こうするとコンテナ起動時にテーブルが作成されます。カラムサイズはかなり適当です。

設定ファイル

nginx2mysql/fluentd/my_fluentd.confです。

  • 入力
    先ほどと同じです。
  • 出力
    ここを見ながら設定します。 host はホストアドレスを記入するのですが、今回はdocker-composeを使っているので、docker-compose.ymlの中のサービス名 mysql で指定します。
    また、バッファの設定はこれまでと同様にしていますが、プラグインの公式の説明では flush_interval で指定しています。どちらでも動作するようなので、これまでと同じにしておきます。

試してみる

先ほどと同様にターミナルで nginx2mysql ディレクトリに入り

docker-compose build
docker-compose up

し、 http://localhost にブラウザでアクセスして下さい。ターミナルで

docker exec -it docker-fluentd-examples-nginx2mysql-mysql-container bash

でMySQLコンテナに入り

mysql -u root -p

でrootパスワード(mypassword)を入力します。そして

select * from mydb.logs;

とすれば、データが入っているのがわかります。

nginxのアクセスログをBigQueryに入れる

nginx2bq

  • 入力
    nginxのアクセスログ
  • 出力
    BigQuery

の場合です。使用するプラグインとしては in_tailfluent-plugin-bigqueryです。後者はインストールが必要です。

docker-compose.ymlのfluentdのオプション -vv だとログが多すぎるので -v にしておきます。

BigQuery

サービスアカウントキーを作成し、JSONファイルで認証します。(GCPコンソールに入り、「APIとサービス」 -> 「認証情報」から作成できます。)
また、BigQueryのデータセットを作っておきます。テーブルは不要です。

設定ファイル

nginx2bq/fluentd/my_fluentd.confです。

  • 入力
    先ほどと同じです。
  • 出力
    ここを見ながら設定します。nginx2bqディレクトリ直下に secret_keyディレクトリを作成し、その中にGCPのサービスアカウントキーのJSONを入れて下さい。それをfluentdの設定ファイルの json_key で指定します(JSON_KEY_FILE_NAME.json としてあるので置き換えて下さい)。あとはGCPプロジェクト名、BigQueryのデータセット名などです(それぞれ PROJECT_NAMEDATASET_NAME としているので置き換えて下さい)。
    また、テーブル名に日時を入れたいので auto_create_table をtrueにします。 table nginx_access_%Y%m%d_%H%M とし、 schema にテーブル構造を書いておけば、テーブル名に日時の入ったテーブルが自動生成されます。

試してみる

先ほどと同じです。ブラウザでアクセスするとfluentdのログにエラーが出ると思いますが、正しい動作のようです。(エラーが出てから auto_create_table でテーブルを生成しているようです。)BigQueryを見ると、指定したデータセットの下にテーブルができているはずです。中身をプレビューできるまでは時間がかかるようですが、SQLでSELECTすれば中身も確認できます。

感想

fluentdはかなり前から当たり前のように使われていますが、実際に設定したことがないので、このように試すことができてよかったです。
ただ、docker-composeを使えば簡単にfluentdのデモができる!と思っていろいろなパターンで試しましたが、だんだんdockerの設定自体が面倒になり、fluentdの勉強をしているのか何なのかわからなくなってきた感が拭えません。fluentdの設定自体は簡単なのに、dockerの設定のせいで複雑に見えてしまっている気がして残念です(nginxのアクセスログが標準出力に出るから云々とか、MySQL8は認証がアレだから云々とか誰得)。入出力についても、もう少しバラエティを増やすべきだったかも…。

参考

公式ドキュメント

プラグイン

その他(fluentd関連)

その他