Edited at

nginxのアクセスログをfluentdでS3に送ってRedshiftにコピーする 2016年版

More than 1 year has passed since last update.

新旧色々な情報が入り乱れていて、なかなかうまく動かなかったので、動いたものをメモっておきます。ちなみにEC2のAmazon Linuxです。fluentdは0.12.26です。


fluentdのはなし


ログにホスト名を入れる方法

まず、ホスト名を入れる方法で悩みました。config expanderを使えとか、record transformerを使えとか、色々見つかったのですが、ことごとく動きませんでした。正確には動かせませんでした・・・そもそも{}で変数囲っても置換されません・・・

で、結局どうしたかっていうと、環境変数を読み込みました。

    time_slice_format "#{ENV['HOSTNAME']}/%Y/%Y%m/%Y%m%d/%H"

これでS3に上がるときにホスト名が入ります。EC2のインスタンスはELBにぶら下がっているので、ホスト名いれとかないとめんどくさいことになります。


追記

とやっていたのですが、これ、OSを再起動するとだめなんですよね。なのでインスタンス増やしたときやAuto Scalingでもダメです。起動時のserviceコマンドは環境変数をリセットするようになっているので、HOSTNAMEは引き継がれません。なので、こう変えて動くようになりました。

    time_slice_format "#{`hostname`.chop}/%Y/%Y%m/%Y%m%d/%H"


時間を消さない方法

nginxでLTSVを選んで、fluentdでJSONに変換しているのですが、これお馴染みの問題らしいのですが、JSONになったときにタイムスタンプが消されます。消されるというか、外に出されます。最終的にRedshiftに持っていく予定でしたので、JSONのデータの中にタイムスタンプを保持する方法を探しました。これはGoogle先生に聞けばすぐ見つかりました。これです。

    format_json true

include_time_key true


きれいなJSONにする方法

デフォルトの設定だと、S3に上げることはできても、そのままRedshiftで読み込みできません。なので、JSONをきれいにしておきます。それがこれ。

    output_tag false

output_time false

これで先頭にある余計なタイムスタンプとタグが消えてくれます。


タイムスタンプはJSTに

ネットでサンプル拾ってくると、だいたいUTCになっています。解析の時に面倒なので、JSTに統一する場合は、コンフィグファイルのutcと書いてある行を削除しましょう。


S3に上げる方法

去年くらいに仕様変わりました。

s3_endpoint⇒s3_regionになっています。


ログの分割ルール

time_slice_format、time_slice_wait、flush_intervalと良く分からなかったんですけど、試行錯誤でなんとなく分かってきました。

    time_slice_format "#{ENV['HOSTNAME']}/%Y/%Y%m/%Y%m%d/%H"

time_slice_wait 10m

この場合、S3には1時間に1回ファイルがアップされます。アップのタイミングはその時間の末に行われるので、最悪59分59秒待たないとS3に上がらないです。time_slice_waitの10分ってのは、10分間はログの到着を待つってことらしいです。例えば、nginxがめっちゃ重くて、12時59分59秒99のログの到着が30秒遅れた場合、これを指定しておかないと12時台のログファイルはCLOSEされてしまうので、ログが抜けてしまう、って感じ?? 試すの面倒なので正直良く分かりません・・・

    time_slice_format "#{ENV['HOSTNAME']}/%Y/%Y%m/%Y%m%d/%H"

time_slice_wait 10m
flush_interval 1m

こうするとS3へのアップは1分毎になり、1時間で最大60ファイルがアップされます。ファイル名の末尾に_1から_60まで通し番号が振られます。.gzの手前ですね。こうすることで、仮にインスタンスが落ちても、消失するログは1分だけで済みます。


まとめ

最終的なコンフィグがこちら。

これでnginx⇒fluentd⇒S3⇒Redshiftまでテスト済みです。

いまのところ、Redshiftへの読み込みはクエリで手動でやっています。


td-agent.conf

<source>

type tail
path /var/log/nginx/access.log
pos_file /var/log/td-agent/buffer/api.access.log.pos
format ltsv
time_format %d/%b/%Y:%H:%M:%S %z
tag nginx.access
</source>

<match nginx.access>
type s3
format_json true
output_tag false
output_time false
include_time_key true
include_tag_key true
time_format %Y-%m-%d %H:%M:%S
aws_key_id アクセスキー
aws_sec_key シークレットアクセスキー
s3_bucket バケット名
s3_region ap-northeast-1
path logs/
buffer_path /var/log/td-agent/s3
time_slice_format "#{ENV['HOSTNAME']}/%Y/%Y%m/%Y%m%d/%H"
time_slice_wait 10m
flush_interval 1m
# utc
</match>



Redshiftのはなし


Redshiftへのコピー

Redshiftへの読み込みについて、まずS3は上記設定ファイルにしたがって、

バケット名/logs/ホスト名/YYYY/YYYYMM/YYYYMMDD/

の中に、

HH_連番.gz

というファイルが大量に生成されます。

連番とgzipについてはRedshiftのCOPYコマンドが自動で処理してくれますので、こんな感じで読み込めます。


ある日のある時間のデータの読み込み

COPY テーブル名 FROM 's3://バケット名/logs/ホスト名/YYYY/YYYYMM/YYYYMMDD/HH'

CREDENTIALS 'aws_access_key_id=アクセスキー;aws_secret_access_key=シークレットアクセスキー'
JSON 'auto' GZIP;

このように、連番や拡張子は入力しなくても、Redshiftが勝手にやってくれます。

ちなみに、JSONPATHファイルは不要です。ログファイルのJSONは構造フラットですし、CREATE TABLEをちゃんとやっておけばautoでCOPYできます。一旦CHARとVARCHARで取り込んで、あとからintとかに変換しています。巨大になったらまずいかなあって思いつつです。

馬鹿みたいに冗長化してあるので、日単位や月単位の読み込みも簡単です。


ある日のデータの読み込み

COPY テーブル名 FROM 's3://バケット名/logs/ホスト名/YYYY/YYYYMM/YYYYMMDD/'

CREDENTIALS 'aws_access_key_id=アクセスキー;aws_secret_access_key=シークレットアクセスキー'
JSON 'auto' GZIP;


ある月のデータの読み込み

COPY テーブル名 FROM 's3://バケット名/logs/ホスト名/YYYY/YYYYMM/'

CREDENTIALS 'aws_access_key_id=アクセスキー;aws_secret_access_key=シークレットアクセスキー'
JSON 'auto' GZIP;