Mackerel、Treasure Data、Slackを組み合わせて障害検知の初動をサポートする


## 背景

GMOペパボでは、社内チャットとして [Slack](https://slack.com/) を、サーバリソースの監視やアラートに[Mackerel](https://mackerel.io) を利用しています。

また、最近はログ集約、解析の基盤として [Treasure Data](https://www.treasuredata.com/) の利用も増えてきました。

Slack 導入以前から、IRC をヘビーに利用していたこともあり、chat-ops 文化はあったのですが、通知や bot があたかも自律的に連携しているかのような、ピタゴラスイッチ的な仕組みはあまりありませんでした。(そして、私はそういう仕組みを作るのがすごく楽しい!)

ですので、今回、試験的にそういった仕組みを作って導入してみました。(今日から!)

## 障害検知からの初動

今携わっているサービスでの障害検知は、おおざっぱに書くと次のようになっています。

- Mackerel からアラートが Slack にくる
- bot に実装されてる外形監視コマンドを発行する
- 問題がありそうだったらログの確認やサーバに入って調査する

外部にオープンになっているサービスなので SPAM ATTACK もそれなりに多く、ちょっと待つと落ち着くようなケースもかなりあるので、「障害検知即トッププライオリティで対応」というわけではないという背景があります。

## Mackerel からの通知に Hubot を反応させる

まずは Mackerel からのアラートに反応して、外形監視コマンドを実行させるところを自動化してみましょう。と思ったのですが、 Mackerel からの通知は、Hubotの `robot.hear` などでは反応できませんでした…

[slackhq/hubot-slack](https://github.com/slackhq/hubot-slack) を読むと、メッセージ内部表現は以下の3種類があることがわかります。

- SlackTextMessage
- SlackRawMessage
- SlackBotMessage

Mackerel からの通知は Slack 上では attachment つきの SlackBotMessage として扱われるようです。そしてこのタイプのメッセージには `hubot.hear` では反応できないようです。

じゃあどうやって反応するのかというと [hubot-slack/listener.coffee at master · slackhq/hubot-slack](https://github.com/slackhq/hubot-slack/blob/master/src/listener.coffee#L25) にあるように、listnerを追加するといけるということがわかります。例えば、CLITICAL のアラートに対して反応できるようにしたければ以下のように書くとよいようです。

```coffeescript
HubotSlack = require 'hubot-slack'

module.exports = (robot) ->
  robot.listeners.push new HubotSlack.SlackBotListener robot, /^CRITICAL: ([^ ]*)/i, (res) ->
    res.send "#{res.match[1]} で障害みたいだよ!"
```

ためしに自分用の bot に実装してみるとこういう感じになりました。よさそうですね。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/38/7538f72b-5387-cd0b-6975-80e9b8ca3f92.png "1.png")

## Treasure Data のクエリの結果を Slack に通知する

現在は、大半のアクセスログを Treasure Data に送信しています。その情報を使うことで、さらに障害検知の初動をサポートできるのではないかと考えました。

Treasure Data にはクエリの実行結果を HTTP PUT で任意URLに送信してくれる機能があります。これと [treasure-data/td2slack](https://github.com/treasure-data/td2slack) を組み合わせると簡単に実現できそうです。しかも、Heroku Button も付いているので、簡単に試すことができます。

最初は、1時間毎のアクセスランキングを出すクエリの受け口にして動作のイメージをつかみました。(クエリはイメージです)

```sql
SELECT
  CONCAT('http://', vhost, path) AS url,
  vhost,
  path,
  COUNT(1) AS access_count
FROM
  access_logs
WHERE
  TD_TIME_RANGE(time, TD_TIME_ADD(TD_SCHEDULED_TIME(), "-1h"))
GROUP BY
  vhost,
SORT BY access_count DESC
```

```erb
1時間以内のアクセストップ5だよ~
<% 5.times do |i| %>
  <%= i + 1 %>位 <%= @td['url'][i] %> | <%= @td['access_count'][i] %>
<% end %>
```

通知結果は以下のようになります。

![2.png](https://qiita-image-store.s3.amazonaws.com/0/38/f3cd24fe-cadf-df13-e695-06e04b2b5415.png "2.png")

## Hubot から Treasure Data のクエリを実行する

Treasure Data の REST API を nodejs から操作するには [treasure-data/td-client-node](https://github.com/treasure-data/td-client-node) を使えばよさそうです。と思ったのですが、幾つかバグがあったり、足りない機能があったので、今は一時的に fork した [kenchan/td-client-node](https://github.com/kenchan/td-client-node)  を使っています。(すぐフィードバックします :bow: )

Treasure Data のクエリの実行方法はいくつかあるのですが、時刻以外のパラメータを固定できるのであれば、あらかじめクエリを保存しておいて、API 経由で実行をキックするのが簡単です。

たとえば、 `my_saved_query` というような名前でクエリが保存されていた場合に、nodejs経由で実行する場合は以下のように書けばよいようです。

```javascript
var TD = require('td')
var c = new TD('your-td-token');

c.runSchedule('my_saved_query', new Date().getTime() / 1000, function(e, r) { console.log(r) })
```

これを、最初に書いた Mackerel の通知を受ける部分と組み合わせるとこうなります。

```coffeescript
TreasureData = require 'td'
HubotSlack = require 'hubot-slack'

module.exports = (robot) ->
  tdClient = new TreasureData(process.env.HUBOT_TD_TOKEN)

  robot.listeners.push new HubotSlack.SlackBotListener robot, /^CRITICAL: ([^ ]*)/i, (res) ->
    res.send "#{res.match[1]} で障害みたいだよ!"
    tdClient.runSchedule 'my_saved_query', new Date().getTime() / 1000, (error, result) ->
      if error?
        res.send 'TDへのJOB登録に失敗しました'
      else
        res.send "TDへのJOB登録に成功しました https://console.treasuredata.com/jobs/#{result['jobs'][0]['job_id']}"
```

これが実際に実行されると以下のようになります。

![3.png](https://qiita-image-store.s3.amazonaws.com/0/38/36bd75a1-5893-a575-8365-8b88e46132b9.png "3.png")


## まとめ

Mackerel の通知をトリガーとして、そこから障害検知後の初動を Slack 上で完結できるような仕組みを導入しました。これにより、今まで手作業で行なっていた確認作業が不要になるだけではなく、エンジニアがなんらかの理由で対応できない場合でも、サービスの責任者が最低限の状況を把握できるようなったのではないかと思います。

最初に書いたように、まさに今日(というかついさっき)から動きはじめた仕組みなのでまだまだ改善の余地があるとは思いますが、まずはこういった取り組みが無事動き出した記念として、Advent Calendarのネタとさせてもらいました。

## おまけ

Q. こういうのってストリーム処理でやるとカッコイイのでは? 傾向を見てアラートを未然に防げたりできそうなんだけど。
A. おっしゃる通りでございます!とはいえ、ある程度まとまったデータを見ることでわかることもあるだろうとは思います。あとは、単純に、 norikra や fluentd プラグインの検証に手を付けられていないという問題もあるのです :innocent: