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のイベントは tag
、 time
、 record
という3要素で構成されています。 time
はそのイベントがいつ起きたか。 record
はイベントの内容(データの内容)です。では tag
とは何かというと、これはそのイベントを分類するときに使うタグです。 foo
とか foo.bar
とか foo.bar.baz
のような文字列です。
後述するhttp2fileでは、HTTPリクエストでデータをPOSTしますが、リクエストの中で http.test
のようにタグを指定します。
http2fileの設定ファイルでは
<match http.**>
# 略
</match>
のように書いています。こうすると http
、http.foo
、 http.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リクエストをファイルに書き出す
つまり
- 入力
httpリクエスト(POSTメソッド) - 出力
ファイル
の場合です。使用するプラグインとしては in_http と out_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のアクセスログをファイルに書き出す
- 入力
nginxのアクセスログ - 出力
ファイル
の場合です。使用するプラグインとしては in_tail と out_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に入れる
- 入力
nginxのアクセスログ - 出力
MySQLのテーブル
の場合です。使用するプラグインとしては in_tail と fluent-plugin-mysqlです。後者はインストールが必要です。
docker関係
MySQLは最新の8系ではなく5.7を使います(認証周りが面倒なので)。プラグインは mysql/Dockerfile
でインストールしています。
あとはMySQLのdockerイメージでの特殊事情をいろいろ考慮します。MySQLのrootパスワードとDB名をdocker-compose.ymlの MYSQL_ROOT_PASSWORD
、 MYSQL_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に入れる
- 入力
nginxのアクセスログ - 出力
BigQuery
の場合です。使用するプラグインとしては in_tail と fluent-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_NAME
、DATASET_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は認証がアレだから云々とか誰得)。入出力についても、もう少しバラエティを増やすべきだったかも…。
参考
公式ドキュメント
- Quickstart Guide | Fluentd
このページというか、このサイトのいろいろなページを読んで設定しました。 - Frequently Asked Questions | Fluentd
fluentdとtd-agentの違いが載っています。 - fluent/fluentd — Docker Hub
fluentdの公式dockerイメージ。(DockerHubの公式リポジトリという意味ではなく、fluentdプロジェクトの公式イメージという意味です。)
プラグイン
- List of Plugins By Category | Fluentd
プラグイン一覧。 - GitHub — tagomoris/fluent-plugin-mysql
MySQLのテーブルに出力するためのプラグイン。 - GitHub — kaizenplatform/fluent-plugin-bigquery
BigQueryのテーブルに出力するためのプラグイン。
その他(fluentd関連)
- docs.fluentd.orgからの日本語ドキュメントの削除 — Qiita
fluentdのドキュメントが英語だけになった話。 - BufferedOutput pluginの代表的なoptionについて — Qiita
古い記事ですが、データが保存されていく流れが説明されています。
その他
- library/mysql — Docker Hub
MySQLのdockerイメージ。(Docker Hubの公式イメージ。) - library/nginx — Docker Hub nginxのdockerイメージ。(Docker Hubの公式イメージ。)
- dockerのlog周りの対応 — Carpe Diem
nginxのdockerイメージでファイルにログを出す方法。