自宅で DNSサーバに Unbound を使っているのだけど立ててるだけで特に監視していないので何か遊んでみようと考えた。
久しぶりに Fluentd を使おうと思ったらバージョンが変わっていて conf の書式にハマった。Elasticsearch は Raspberry Piで動かしているが Unbound のログの量が多いので調整はまだ続きそう…(´・ω・`)
- Unbound 1.9.0
- fluentd 1.4.0
- Python 3.5.3
- Elasticsearch 6.5.2
- Kibana 6.5.2
Fluentd tail Input Plugin で Unbound のログを解析する
まずは Unbound のログの解析。ログファイルから tail プラグインを使って読み込む。
https://docs.fluentd.org/v1.0/articles/in_tail
Unbound のログは log-queries
を有効にしておいて info
、query
、reply
の区別が付くようにしておく。
/etc/unbound/unboud.conf
log-queries: yes
Unbound のログは以下のような書式になる。ここからタイムスタンプ、クライアント、ホストを取り出していく。
/var/log/unbound/unbound.log
[1551614551] unbound[24609:0] query: 192.168.1.10 ssl.gstatic.com. A IN
fluent.conf
Unbound のログをすべて解析するわけではないので、パターンに一致しない行が pattern not match でダラダラと出てくるので @log_level error
で出力を抑制しておく。
<source> @type tail @log_level error tag unbound.log path /var/log/unbound/unbound.log pos_file /tmp/fluentd_unbount.log.pos <parse> @type regexp expression ^\[(?<time>[0-9]+)\] unbound\[.+\] query: (?<client>[^ ]+) (?<host>[^ ]+)\. A IN$ time_key time time_type unixtime </parse> </source> # 出力確認用 <filter unbound.log> @type stdout </filter>
🤔 @timestamp フィールドを作るか作らないか
Elasticsearch プラグインで logstash_format
を有効にする場合、プラグイン側で自動的に @timestamp
フィールドを作成してくれるのでここで用意しておく必要はない。外部フィルターで時間に対して何かしらの処理をしたい場合や logstash_format
を使わない場合はここで @timestamp
フィールドを作成しておくといいかもしれない。
🤔 時間フィールドが消えてしまう問題
time_key
で時間として指定したフィールドはデフォルトだと消えてしまうので残しておきたい場合は keep_time_key
を有効にする。
🤔 数値が数値型ではなく文字列型になってしまう問題
キーの型を指定したい場合は types
で キー名:型
と指定する必要があるようだ。ここでは数値型にしたいので @timestamp:integer
とする。
tail Input Plugin の出力結果
2019-03-03 21:02:31.000000000 +0900 unbound.log: {"client":"192.168.1.10","host":"ssl.gstatic.com"}
exec_filter でホスト名から国や緯度経度情報を取得するフィルターを作る
以前は GeoIP
モジュールを使っていたけど今回は GeoLite2-City.mmdb を使いたかったので maxminddb
モジュールを使うことにした。GeoIP
だと record_by_name()
でホスト名から情報を拾えるんだけど maxminddb
は get()
で IP アドレスを渡すくらいしかできないので socket.gethostbyname()
を通して IP アドレスを渡している。
#!/usr/bin/env python3import sys import json import socket import maxminddb reader = maxminddb.open_database('GeoLite2-City.mmdb') for line in sys.stdin: d = json.loads(line) g = reader.get(socket.gethostbyname(d['host'])) d.update({ 'country': { 'iso_code': g['country']['iso_code'], }, 'location': { 'lat': g['location']['latitude'], 'lon': g['location']['longitude'], }, }) sys.stdout.write(json.dumps(d))
fluent.conf
GeoIP フィルターとやりとりする部分を書いていく。バッファはメモリに配置。<format></format>
や <parse></parse>
が以前のバージョンになかったので困った。
- https://docs.fluentd.org/v1.0/articles/format-section
- https://docs.fluentd.org/v1.0/articles/parse-section
Elasticsearch プラグインで logstash_format
を使う場合、外部フィルターから返ってきた JSONから再度時間を抽出する必要がある。
<match unbound.log> @type exec_filter tag exec.unbound command /usr/bin/python3 geoip.py <format> @type json </format> <parse> @type json </parse> <inject> time_key @timestamp time_type unixtime </inject> <buffer> @type memory </buffer> </match>
Elasticsearch のマッピングをする
Fluentd から Logstash 形式でデータを投入する場合、日時でインデックスが作成されるが、マッピングの設定ができない。今回は緯度経度情報を扱うため、geo_point
の指定が必須になる。そこで、テンプレートを用意して自動的に適用されるようにしておく。データ量が多いので refresh_interval
をデフォルトの 1s
から 30s
に変更。string 型のデータは解析する必要もないので keyword
で入るようにしておく。
@timestamp
に epoch_second
を使う場合はここで設定しておけばよい。
PUT _template/unbound{"index_patterns": "unbound-*", "settings": {"number_of_shards": 1, "number_of_replicas": 0, "refresh_interval": "30s" }, "mappings": {"_doc": {"_all": {"enabled": false}, "dynamic_templates": [{"strings": {"match_mapping_type": "string", "mapping": {"type": "keyword" }}}, {"geo_point": {"match": "location", "mapping": {"type": "geo_point" }}}]}}}
Fluentd から Elasticsearch にデータを投入する部分を書く
データ量が多いせいか1時間ほどで Elasticsearch にデータが入らなくなってしまうので request_timeout
をデフォルトの 5s
から 30s
に増やしてある。(他は調整中)
今回は logstash_format
を有効にするのでレコードの @timestamp
フィールドは自動的に作成される。
<match exec.unbound> @type elasticsearch hosts localhost:9200 type_name _doc logstash_format true logstash_prefix unbound request_timeout 30s <buffer> flush_thread_count 4 chunk_limit_records 200 </buffer> </match>
Elasticsearch に投入されたデータ
{"_index": "unbound-2019.03.03", "_type": "_doc", "_id": "bbb0Q2kBVp0AiN9Y-bd0", "_score": 1, "_source": {"client": "127.0.0.1", "location": {"lon": -97.822, "lat": 37.751}, "host": "www.elastic.co", "country": {"iso_code": "US" }, "@timestamp": "2019-03-03T23:28:41.234223023+09:00" }, "fields": {"@timestamp": ["2019-03-03T14:28:41.234Z" ]}}
Kibana でダッシュボードを作る
Fluentd から送られてきたデータを Coordinate Map、Heat Map、Line で可視化する。
こうして見てみるとただブラウザで調べごとをしていたりするだけでも案外いろんな国にまで行っているのだなぁと感じる。
しばらく監視してみておかしなサイトに繋ぎに行ってないかとか発見出来れば面白いかな。
プロキシサーバのログも解析したいけど今日はもう疲れたのでまた今度にする…( ˘ω˘)スヤァ