td-agent2からtd-agent3へバージョンアップしました

この記事は、[All About Group（株式会社オールアバウト）Advent Calendar 2018](https://qiita.com/advent-calendar/2018/allabout) 11日目の記事です。
All Aboutでデータエンジニアとして日々集計関連システムを作ったりして、データと仲良くしている[@tranbach](https://qiita.com/tranbach)です。
ベトナム人なので、日本語の間違いがあれば、是非教えてください。
今回の投稿は、私が担当した社内集計システムに使用しているtd-agentのバージョンアップの話を共有したいと思います。
参考にしていただいて、何かが役に立てれば、うれしいです。

# バージョンアップの狙い
私の所属しているチームは集計チームで、社内集計システムの開発・保守を担当しています。td-agentはその集計システムのアクセスログの収集・データ転送ツールとして使っています。
3年前に導入したtd-agentのバージョンはもう古くて、BigQueryにデータ転送する際に、古いtd-agentバージョンがBigQueryとの相性がよくなかったため、専用サーバー群を立てて、そのサーバー群のみに新td-agentを載せる暫定対応が必要でした。
今回のバージョンアップ前は、システムの構成は下記のようなものでした。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/113713/f8a11f3d-0372-c686-0117-9f9b881b08d1.png)

オールアバウトからの各アクセスログをLog Forwarderで一時的に受け取ってから、Log Aggregator&Processorへ転送します。そのAggregator&Processorで解析、分類、正規化等を行ったあとのログはBigQueryとTreasure DataといったDWHに転送されます。今回のバージョンアップ対象td-agentはLog Aggregator&Processorサーバー群にあるtd-agentたちです。

バージョンアップで改善したい点は下記の2点になります。
・Log Aggregator&Processorのtd-agentバージョンをアップし、右下の暫定対応用のサーバー群をなくす。システム構成がよりシンプルになるので、今後のスケールアウトはより簡単になる
・古いバージョンでは、CPUのシングルコアしか使えなく、CPUの複数コアをうまく活用できていないので、新td-agentバージョンでCPUのコア数をフル活用したい

# 変更バージョン
td-agent v2.2.1からtd-agent v3.1.1へ
(あるいはfluentd v0.12.12 → fluentd v1.0.2)
td-agentの変更履歴は下記のリンクから確認できます。
https://support.treasuredata.com/hc/en-us/articles/360001479187-The-td-agent-ChangeLog

# 作業内容と対応方法
これから、実際に行った作業を一つ一つ紹介しながら、対応のポイントを共有します。
主な作業の一覧はこちらになります。
・fluentdとプラグインの最新バージョン調査・選定
・既存td-agentの設定ファイルを新バージョン用に書き換える
・データ検証
・負荷テスト
※オールアバウトのfluentdはtd-agentパッケージに含まれるもの

## fluentdとプラグインの最新バージョン調査・選定
当時のfluentdとプラグインの最新バージョンを調査し、fluentdのバージョンとプラグインのバージョンの相性を確認したうえで、
その時点での最新バージョンに固定します。プラグインバージョンとfluentdバージョンが作業開始からリリースまで変わる可能性があるため、一度固定にして、その後の動作確認、データ検証等の作業はすべて同じバージョンで行うようにしました。
（当たり前のことですが、```td-agent-gem install {プラグインイン名}```でインストールすると、いつも最新版をインストールしてしまうので、バージョンが決まったら、バージョン指定でインストールするように関連チームに共有しましょう）

## td-agentの設定ファイル書き換え(td-agent.conf)
既存の設定ファイルを新バージョンで動かせるように、設定ファイルを書き直しました。
いくつかの書き換えパターンを紹介します。
### その１：文法変更
プラグインの最新バージョンで文法が変わってきたので、既存の書き方(rewriterule<num>とか、regexpNとか、excludeN等)を新しい文法に合わせる必要がありました。
下記は例の一つです。

```ruby
rewriterule<num> <key> <regex_pattern> <new_tag>
```
↓

```ruby
<rule>
    key     {key}
    pattern {regex_pattern}
    tag     {new_tag}
</rule>
```

### その２：変数の定義が必要になりました。
```ruby
url ${e_url.nil? ? url : e_url}
```
td-agentの内部のRubyバージョンが2.4になったので、上記のような書き方だと、下記のような警告がでました。

```ruby
[warn]: #0 record_reformer: failed to expand `%Q[#{e_url.nil? ? url : e_url}]` error_class=NameError error="undefined local variable or method `e_url'"
```
変数定義の必要と、{key}の書き方より、{record['key']}の書き方の方が良いと[指摘](https://docs.fluentd.org/v1.0/articles/filter_record_transformer#faq)されましたので、最終的に、下記の書き方で、警告がでなくなります。

```ruby
e_url ${record.has_key?("e_url") ? record["e_url"] : ""}
url ${record.has_key?("url") ? record["url"] : ""}

url ${record["e_url"] == "" ? record["url"]: record["e_url"]}
```
ちなみに、{record["key"]}という書き方ですが、

```ruby
fluentd 0.12.12までは、${x}では変数かtagとして読む（公式ドキュメント）
fluentd 1.0.2からは、${x}は関数もしくは変数かtagとして読む
変数だと指定して読むには：record["x"] と書く必要がある（公式ドキュメント）
```
実際pというものはこれまで`${x}`の書き方で変数として使ってきましたが、version 1.0.2では関数の扱いになってしまいました(Rubyにpという関数が存在するので、`${p}`は変な値になる）。なので、`{record["p"]}`に書き換えたら、問題を解決しました。


### その３：parserというfilterのデフォルト設定値が変わった
１行のデータにtimeフィールドがあり、それをデータの時間として維持したい場合は、明確に指定する必要になりました。
[デフォルト設定ではそのtimeフィールドの値が無視されます](https://docs.fluentd.org/v1.0/articles/filter_parser#reserve_time)
reserve_timeをtrueにすると、timeの値をparserフィルターを通ったあとも、維持することができる。

```ruby
<filter hogehoge>
   @type parser
   reserve_time true ← これを明確にする必要になった
   {他の設定省略}
</filter>
```

## データ検証
基本の検証方法は本番からのアクセスログのファイルを動作確認環境に持ってきて、複数の種類のデータ（１時間分のアクセスログと１日分のアクセスログ）をそれぞれをForwarderサーバーに流し込み、最終的に各DWHに届いたデータをデータベースのテーブル単位で本番のと比較し、検証を行いました。比較項目はテーブルの件数とテーブルの中身でした。
巨大なサイズのファイルを一気にForwarderサーバーに流し込むと、td-agentがうまく処理できなく、Aggregator&Processorサーバーへ転送できかかったことがありました。ファイルを一定の間隔に分けて流し込むようにすることで、上記の問題を解決しました。
このステップで発見できた本番データと検証データとの差分を一つずつ深堀し、原因特定と対応を行ったあと、再び検証する繰り返しをしました。
td-agent.confの設定ファイルには沢山の処理が入ってしまったので、このステップはかなり時間がかかりました。

## 負荷テスト
負荷テストは検証環境と本番環境で行いました。
検証環境での負荷テストはあまりコストがかからい方針でやりました。単に上記のステップのように、本番から検証環境に持ってきてきたデータをAggregator&Processorサーバーに直接流し込み、td-agentが耐えられるかのチェックとAggregator&Processorサーバーを通った後のデータ件数が本番のと一致するかどうかのチェックを行いました。
負荷テストのメインは本番リリース後の負荷監視でした。
![fluentd release.gif](https://qiita-image-store.s3.amazonaws.com/0/113713/865ba2ba-1199-8185-3b31-80655c99b6b7.gif)

上記のようなリリース方法で新バージョン搭載のサーバー(新規)を一台ずつ付け加えながら、負荷を監視しながら、リリースしました。
1. Log Aggregator&Processorの既存サーバーを残すまま、新バージョン搭載のサーバーを付け加え、Log Forwarderの負荷分散機能を使い、新サーバーに一部の負荷を与える。
2. 一部の負荷を受け取っている新サーバーのリソース(CPU, Memory)を測定しながら、想定の負荷を計算する
3. ニーズに応じての新規サーバー台数を計算し、増やしておく
4. 最終的に既存のサーバーを一台ずつ減らしていく

# まとめ
td-agentのバージョンアップの作業イメージを共有させていただきました。いかがでしょうか？参考になりそうなところがあったのでしょうか？
本投稿のタイトルにはtd-agentバージョンアップのみと書いていますが、td-agentバージョンと共に、設定ファイルに使っているプラグインのバージョンもアップする必要があったので、思ったより複雑な作業でした。最近のtd-agentでは、外部が作っていたプラグインがfluentdの内部プラグインに吸収されるので、多くの外部プラグインのバージョン管理をしなくても済むようになると思います。
といっても、うちの設定ファイルにはまだ沢山のプラグインを使っているので、今後、どんどん設定をシンプル化して、外部プラグインの依存を脱却したい。