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

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

# fluentdのはなし
## ログにホスト名を入れる方法
まず、ホスト名を入れる方法で悩みました。config expanderを使えとか、record transformerを使えとか、色々見つかったのですが、ことごとく動きませんでした。正確には動かせませんでした・・・そもそも｛｝で変数囲っても置換されません・・・
で、結局どうしたかっていうと、環境変数を読み込みました。

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

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

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

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

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

```ruby
    format_json true
    include_time_key true
```

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

```ruby
    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;
```