fluentdで本番環境を再現する


[toyama0919/fluent-plugin-http_shadow](https://github.com/toyama0919/fluent-plugin-http_shadow)というShadow Proxyっぽいことを簡単にやるプラグインを作りました。
production環境で半年くらい動かしてたのでメモしときます。

### 「Fluentd Meetup 2015 夏」で[実際のユースケース](http://www.slideshare.net/toyama0919/fluentd-49628783)を発表しました。

## Shadow Proxyサーバとは

Shadow Proxyサーバについては以下がわかりやすいです。

* [気軽なMySQLバージョンアップ - まめ畑](http://d.conma.me/entry/2013/12/10/000046)
* [Go言語を含む複数種類の言語により実装されたソフトウェアのベンチマーク - Qiita](http://qiita.com/kentaro/items/17e2c334a902677af995)

実装としては以下のようなものが公開されています。

* [cookpad/kage](https://github.com/cookpad/kage)
* [lestrrat/p5-Geest](https://github.com/lestrrat/p5-Geest)
* [kentaro/delta](https://github.com/kentaro/delta)

本番のリクエストをそのままバックエンドにあるサーバーに複製して送信するのですが、アプリケーションの規模が大きくなればなるほど効果を発揮します。
規模が大きくなればなるほどテスト方法も複雑になってきます。

**現在本番で発生しているrequestをそのまま持ってくることで、限りなく本番に近い状態を再現できます。**
Shadow Proxy代表的なユースケースとしては負荷試験や結合試験になります。

これをもう少し簡単かつ安全に出来ないかと考え、fluentdで実現してみました。

## 概要

fluentdに送信されるログからhttp requestを復元しています。

![fluent-plugin-http_shadow](http://i.gyazo.com/59d5fe9c4430325f6ad59c638971cc25.png "fluent-plugin-http_shadow")

Proxyは使わないので、本番のWEBサーバーには影響なく導入出来ます。

## 設定例(Apache等)

```apache
<source>
  type tail
  format ltsv
  time_key log_time
  time_format %d/%b/%Y:%H:%M:%S %z
  path /var/log/httpd/access_log
  pos_file /var/log/td-agent/access.pos
  tag http_shadow.example
</source>
```

ログフォーマット例

```
  {
    "host": "exsample.com",
    "ip_address": "127.0.0.1",
    "server": "10.0.0.11",
    "remote": "-",
    "time": "22/Dec/2014:03:20:26 +0900",
    "method": "GET",
    "path": "/hoge/?id=1",
    "code": "200",
    "size": "1578",
    "referer": "http://exsample.com/other/",
    "user_agent": "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko"
  }
```

設定はこう

```apache
<match http_shadow.example>
  type http_shadow
  host_hash { 
    "www.example.com": "staging.example.com", 
    "api.example.com": "api-staging.example.com", 
    "blog.ipros.jp": "blog-staging.ipros.jp"
  }
  host_key host
  path_format ${path}
  method_key method
  header_hash { "Referer": "${referer}", "User-Agent": "${user_agent}" }
  max_concurrency 10
  flush_interval 10
  timeout 10
  rate 10
</match>
```


* host_hashでVirtual Hostに対応できます。
  * Host名に対応したrequest先を定義します。

* rateが10の場合、requestが10%に希釈されます。(残りの90%は破棄される)
  * ステージング環境が本番環境よりも低いスペックの場合などはrateを調節します

* header_hashでhttp headerを指定できます。
  * ${referer}のようにplaceholderでkeyを指定。
  * 固定文字列も可能です。

* apacheのログの場合、postのパラメータはないので欠損します。

## 設定例2(アプリ内で専用のログを作れる場合、Railsとか)

アプリケーションから以下のようなログを出す場合、

```json
  {
    "host": "example.com",
    "ip_address": "127.0.0.1",
    "log_time":"2015-05-18 16:38:14",
    "method": "GET",
    "path": "/hoge/",
    "uuid": "e78407cf040f67d17361281906895ca1283929e33592f81cdbda54a4a24e8cf7",
    "session_id": "42d0c906bfde8a30d323865d82a234abb4d13db80471d3b0bf4b7adc515d06da903f514289340ca17ba5cedde118868deebe53ef3986bfae8b591b9a614f7242",
    "user_id": 1
    "params": {"utf8"=>"✓", "client_id"=>"384809"} ,
    "referer": "http://exsample.com/other/",
    "user_agent": "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko"
  }
```

設定例

```apache
<match http_shadow.exsample>
  type http_shadow
  host_hash { 
    "example.com": "staging.example.com"
  }
  host_key host
  path_format ${path}
  method_key method
  header_hash { "Referer": "${referer}", "User-Agent": "${user_agent}" }
  cookie_hash {"rails-app_session": "${session_id}"}
  params_key params
  max_concurrency 10
  flush_interval 10
  timeout 10
  rate 10
</match>
```

* cookie_hashでcookieを送信も送信可能です。
  * 設定方法はheader_hashと同じ

* getやpostのパラメータはparams_keyの中にhashで格納しておけばパラメータをmergeします。


## パラメータ

### 設定可能な値とデフォルト値

```ruby
  config_param :host, :string, :default => nil
  config_param :host_key, :string, :default => nil
  config_param :host_hash, :hash, :default => nil
  config_param :path_format, :string
  config_param :method_key, :string, :default => nil
  config_param :header_hash, :hash, :default => nil
  config_param :cookie_hash, :hash, :default => nil
  config_param :params_key, :string, :default => nil
  config_param :max_concurrency, :integer, :default => 10
  config_param :timeout, :integer, :default => 5
  config_param :username, :string, :default => nil
  config_param :password, :string, :default => nil
  config_param :rate, :integer, :default => 100
```


### 詳細

|設定値|内容|
|:--|:--|
|           host|                                                                                       httpリクエストを送信するhost名|
|       host_key|                                                                                     Virtual Hostの際の接続Hostのキー|
|      host_hash|                                                                                     Virtual Hostの際の接続Hostのペア|
|    path_format|                                                                 urlのpathのフォーマット。${path}などでテンプレート化|
|     method_key|                                                                         http methodが定義されたキー。デフォルトはGET|
|    header_hash|                                                                                                    http headerのペア|
|    cookie_hash|                                                                                                         cookieのペア|
|     params_key|                                                                               parameterが格納されたhashのkey|
|max_concurrency|                                                                                               httpリクエストの並列数|
|        timeout|                                                                                                     タイムアウト時間(秒)|
|       username|                                                                                                Basic認証のユーザー名|
|       password|                                                                                                Basic認証のパスワード|
|           rate|HTTPリクエストの希釈率。デフォルトが100で全てのリクエストがそのまま送信される。50ならば半分のリクエストが送信される。|