fluentdでログ監視して特定の文字列をgrepで検知したらSlackに通知する（バースト抑止付き）

# はじめに
fluentdでログ監視して特定の文字列を検知したらSlackに通知するのを試してみたメモです。

背景として、以下でauditdでファイル改竄検知したらシスログに出すのを検証してました。
[auditdでLinuxのファイル改竄検知を行う](http://qiita.com/minamijoyo/items/0d77959a869e458d850a)

これをfluentd経由で特定の文字列をgrepで見つけてSlackに通知しようという算段です。

Slackの通知には[sowawa/fluent-plugin-slack](https://github.com/sowawa/fluent-plugin-slack)を使いました。
またSlackに通知するのに、大量のエラーメッセージがバーストすると実運用上困るので、
[fujiwara/fluent-plugin-suppress](https://github.com/fujiwara/fluent-plugin-suppress)を利用して、一定時間あたりの連続投稿に制限をかけてみました。

稼働確認したのは以下の環境です。

* Amazon Linux 2015.09
* td-agent-2.3.1-0.el2015.x86_64
* fluentd: 0.12.20
* fluent-plugin-slack: 0.6.4
* fluent-plugin-suppress: 0.0.6

# 設定
fluentd自体のインストールなどは割愛します。適宜ググって下さい。

## シスログを投げる
シスログ(/var/log/messages)の送信側は本題とあまり関係ありませんが、一応前提となる構成として書いておきます。
実際には他の設定も入ってますが、関連するところを抜粋。各自の環境で適宜読み替えて下さい。

```/etc/td-agent/td-agent.conf
<source>
  type config_expander

  <config>
    type tail
    format syslog
    path /var/log/messages
    pos_file /var/log/td-agent/messages.pos
    tag hoge.messages
  </config>
</source>

<filter *.**>
  @type record_transformer
  enable_ruby true
  <record>
    @hostname ${hostname}
    @timestamp ${time.strftime('%Y-%m-%dT%H:%M:%S%z')}
  </record>
</filter>

<match *.**>
  type forward
  retry_limit 5
  flush_interval 5s
  <server>
    host fluentd.local
    port 24224
  </server>
</match>
```

以降の説明はすべてログの受信側の設定です。

## シスログのタグを取り出す

処理の単位をわかりやすくするために `**.messages` にマッチしたものを `@messages` ラベルにルーティングしてます。特に要件ではないのでこの辺はご自由にどうぞ。

```/etc/td-agent/td-agent.conf
<source>
  type forward
  port 24224
</source>

<match **.messages>
  type copy

  # シスログの監視
  <store>
    type   relabel
    @label @messages
  </store>

  # コピーしてデフォルト処理にも流しておく
  #
  # (略)
  #

</match>
```

## ログを特定の文字列でgrepする
ここから本題。特定の文字列を抽出するにはfluent組み込みのgrepのfilterプラグインを使います。

この時点で入力のデータはこんなかんじになってる想定です。

```json
{
  "host": "hoge-i-XXXXXXXX",
  "ident": "audispd",
  "message": "node=hoge-i-XXXXXXXX type=SYSCALL msg=audit(1479196400.701:2663): arch=c000003e syscall=2 success=no exit=-13 a0=1e18ee0 a1=401 a2=1b6 a3=0 items=1 ppid=14033 pid=14034 auid=513 uid=513 gid=514 euid=513 suid=513 fsuid=513 egid=514 sgid=514 fsgid=514 tty=pts1 ses=403 comm=\"bash\" exe=\"/bin/bash\" key=\"passwd_changes\"",
  "@hostname": "hoge-i-XXXXXXXX.example.com",
  "@timestamp": "2016-11-15T16:53:20+0900"
}
```

grepのfilterプラグインはキーごとに指定した正規表現のANDを満たすレコードのみを抽出します。
`ident` キーが `audispd` で `message`　に `passwd_changes` を含むレコードを抽出するにはこんな感じです。


```/etc/td-agent/td-agent.conf
<label @messages>
  <filter **.*>
    type grep
    regexp1 ident ^audispd$
    regexp2 message passwd\_changes
  </filter>

  ...

</label>
```

また、あとでslackに渡すためにfluent組み込みのrecord_transformerを使って、`title` と `message` キーを作っておきます。
この場合は、たまたま `message` キーが存在しますが、明示するために再設定しています。
titleはident(=この場合デーモン名)とhostnameを@で連結した文字列にしています。この辺はお好みでどうぞ。

```/etc/td-agent/td-agent.conf
<label @messages>
  <filter **.*>
    type grep
    regexp1 ident ^audispd$
    regexp2 message passwd\_changes
  </filter>

  <filter **.*>
    type record_transformer
    enable_ruby
    <record>
      title ${ident}@${record["@hostname"]}
      message ${message}
    </record>
  </filter>

  <match **.*>
    type relabel
    @label @slack
  </match>
</label>

```

## バースト抑止をかける
大量に同じエラーメッセージがバーストしてSlackを荒らすと実運用上困るので、
fujiwara/fluent-plugin-suppressを使って一定時間内の同じtitleのメッセージ投稿数を制限しておきます。

https://github.com/fujiwara/fluent-plugin-suppress

プラグインをインストールします。fluent-gemのパスは適宜読み替えて下さい。

```
# /opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-suppress
Fetching: fluent-plugin-suppress-0.0.6.gem (100%)
Successfully installed fluent-plugin-suppress-0.0.6
Parsing documentation for fluent-plugin-suppress-0.0.6
Installing ri documentation for fluent-plugin-suppress-0.0.6
Done installing documentation for fluent-plugin-suppress after 0 seconds
1 gem installed
```

```/etc/td-agent/td-agent.conf
<label @slack>
  # バースト抑止のため一定時間内の同じtitleのメッセージ投稿数を制限する
  <filter **.*>
    type      suppress
    interval  60
    num       5
    attr_keys title
  </filter>

  ...

</label>
```

attr_keysのところが、グループ化するキーです。カンマ区切りで複数指定するとAND条件になるっぽいです。
ここでは同じ `title` キーで抑止制限をかけます。 

## Slackに通知する
やっとSlack通知まできた。

まずSlackのWebフックURLを発行しておきます。
Slackの「Apps&Integrations」から「Incoming WebHooks」を新規に作成してWebフックのURLを発行して下さい。

アイコン用にfluentdの画像素材が欲しい場合は公式で配布されてるこの辺を使うとよいんじゃないかと。

http://docs.fluentd.org/articles/logo

次にsowawa/fluent-plugin-slack プラグインをインストールします。
https://github.com/sowawa/fluent-plugin-slack

fluent-gemのパスは適宜読み替えて下さい。

```
# /opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-slack
Fetching: fluent-plugin-slack-0.6.4.gem (100%)
Successfully installed fluent-plugin-slack-0.6.4
Parsing documentation for fluent-plugin-slack-0.6.4
Installing ri documentation for fluent-plugin-slack-0.6.4
Done installing documentation for fluent-plugin-slack after 0 seconds
1 gem installed
```

設定は以下のようなかんじにしてみました。他に使える設定項目はREADMEに書いてあるのでご一読下さい。
`message` と `message_keys` はデフォルト値のままなので省略可能ですが、設定ファイルの読みやすさのために明示してます。

```/etc/td-agent/td-agent.conf
<label @slack>
  # バースト抑止のため一定時間内の同じtitleのメッセージ投稿数を制限する
  <filter **.*>
    type      suppress
    interval  60
    num       5
    attr_keys title
  </filter>

  <match **.*>
    type slack
    webhook_url https://hooks.slack.com/services/XXXXXXX
    channel playground
    username fluentd
    icon_emoji :fluentd:
    color warning
    title %s
    title_keys title
    message %s
    message_keys message
    flush_interval 10s
  </match>
</label>
```

設定を反映するために一旦td-agentを再起動。

```
# service td-agent restart
Restarting td-agent:                                       [  OK  ]
```
## fluent-catで試してみる

fluent-catのパスは適宜読み替えて下さい。

```
# echo '{"host": "hoge-i-XXXXXXXX", "ident": "audispd", "message": "node=hoge-i-XXXXXXXX type=SYSCALL
 msg=audit(1479196400.701:2663): arch=c000003e syscall=2 success=no exit=-13 a0=1e18ee0 a1=401 a2=1b6 a3=0 items=1 ppid=14033 pid=14034
auid=513 uid=513 gid=514 euid=513 suid=513 fsuid=513 egid=514 sgid=514 fsgid=514 tty=pts1 ses=403 comm=\"bash\" exe=\"/bin/bash\" key=\"
passwd_changes\"", "@hostname": "hoge-i-XXXXXXXX.example.com", "@timestamp": "2016-11-15T16:53:20+0900"}' | /opt/td-agent/embedded/bin/fluent-cat hoge.messages
```

![image](https://qiita-image-store.s3.amazonaws.com/0/61152/4d03bcb5-c7b7-9eb1-f621-df11f5beb3cf.png)

飛んできた。よさげ。

# まとめ
fluentdでログ監視して特定の文字列をgrepで検知したらSlackに通知できるようにしてみました。
ログはなんでもfluentdに投げておけば、あとはよしなにgrepしてSlackに投げるパターンは汎用的でいろいろ使えそうです。