SensuのhandlerとしてAnsibleを使う

うどん大学 情報系 院1年の堀内です．
勢いで [Sensu Advent Calendar](http://qiita.com/advent-calendar/2014/sensu) を立てましたが，無事埋まっていてホッとしています．

SensuはAnsibleと同時期に使い始めました．
現在は，研究室のサーバ監視に使っています．

## もくじ

  1. Sensuとの出会い
  2. 研究室のサーバ監視で
  3. handlerとしてのAnsible

## Sensuとの出会い

「[研究室でAnsible使ってる話](http://qiita.com/hiconyan/items/ddf5d3e34a18a2028566)」でも書きましたが，8月末にヤフーのインターンシップに参加しました．
そこでSensuが使われているのを見せて頂き，興味を持ったのがキッカケです．
ホテルに戻ってから，試してみようと作業していたのを覚えています．
(Ansibleを使ってsensu-clientを流し込もうと四苦八苦．)

ヤフーでの取組みはAdvent Calendarに書かれています．

  - [SensuとGraphiteによる大規模インフラの監視 - Yahoo! JAPAN Tech Blog](http://techblog.yahoo.co.jp/operation/2014-sensu-and-graphite/)

## 研究室のサーバ監視で

研究室にはVMを含めて20台以上のサーバがあります．
そこにAnsibleを使ってsensu-cluentを流し込み，監視しています．

監視項目はCPUやメモリ，HTTPなど，基本的なものだけです．
[community-plugins](https://github.com/sensu/sensu-community-plugins) もしくは，その機能を削って使っています．

![sensu_metrics.png](https://qiita-image-store.s3.amazonaws.com/0/58742/4103b4c5-a2c1-32c8-6b93-c58ec46ef5aa.png "sensu_metrics.png")

metricsはElasticsearchに投げて，Kibanaで可視化しています．
(fluentdなどを介さず，handlerから直接投げています．)
また，研究室で [Typetalk](https://typetalk.in/) を使っているので，Hubotを使ってアラートを投げたりもしています．

  - [hico-horiuchi/sensu-elasticsearch](https://github.com/hico-horiuchi/sensu-elasticsearch)
  - [hico-horiuchi/sensu-hubot](https://github.com/hico-horiuchi/sensu-hubot)
  - [curator + cron + logrotate - 12FF5B8](http://hico-horiuchi.hateblo.jp/entry/20141128/1417164227)

## handlerとしてのAnsible

ここからが本題です．

例えば，SensuのHTTPチェックでCRITICALが発生したとします．
そこで，handlerからWebサーバを再起動するためにAnsibleを使ってみます．
(ついでに，Typetalkに再起動の通知も出します．)

発生したアラートはこんな感じ．

```json
{
  "client":{
    "name": "web-server-01",
    "address": "192.168.11.10",
    "subscriptions": ["webservers"],
    "timestamp": 1326390159
  },
  "check":{
    "name": "check-http",
    "issued": 1326390169,
    "output": "HTTP CRITICAL: HTTP/1.1 503 Service Temporarily Unavailable",
    "status": 2,
    "command": "etc/sensu/community-plugins/plugins/http/check-http.rb -r -u http://localhost/",
    "subscribers":["webservers"],
    "interval": 60,
    "handler": "ansible",
    "history": [],
    "flapping": false
  },
  "occurrences": 1,
  "action": "create"
}
```

まず，このアラートを受ける`/etc/sensu/plugins/ansible_handler.rb`を作ります．

```ruby
#!/usr/bin/env ruby
# Sensu Ansible Handler

require 'sensu-handler'

class AnsibleHandler < Sensu::Handler
  def playbooks
    settings['ansible']['playbooks'] || '/etc/sensu/playbooks'
  end

  def typetalk_vars
    {
      client_id: settings['ansible']['typetalk_client_id'],
      client_secret: settings['ansible']['typetalk_client_secret'],
      topic: settings['ansible']['typetalk_topic']
    }
  end

  def generate_hostfile(event)
    client = event['client']['name']
    address = event['client']['address']
    check = event['check']['name']

    hostfile = "/tmp/#{Time.now.strftime '%y%m%d%H%M%S'}.hosts"
    typetalk = typetalk_vars

    File.open(hostfile, 'w') do |f|
      f.puts "[#{check}]"
      f.puts "#{client} ansible_ssh_host=#{address}"
      f.puts "[#{check}:vars]"
      f.puts "typetalk_client_id=#{typetalk[:client_id]}"
      f.puts "typetalk_client_secret=#{typetalk[:client_secret]}"
      f.puts "typetalk_topic=#{typetalk[:topic]}"
    end

    hostfile
  end

  def handle
    status = @event['check']['status']
    occurrences = @event['occurrences']
    return if status < 2 || occurrences > 1

    hostfile = generate_hostfile @event
    check = @event['check']['name']
    result = `ansible-playbook -i #{hostfile} #{playbooks}/#{check}.yml`
    unless $?.to_i == 0
      puts result
    end

    File.delete hostfile
  end
end
```

handlerの設定ファイル`/etc/sensu/conf.d/handler_ansible.json`は下記の通り．

```json
{
  "ansible": {
    "playbooks": "/etc/sensu/playbooks",
    "typetalk_client_id": "abcdefghijklmnopqrstuvwxyz123456",
    "typetalk_client_secret": "abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrstuvwxyz123",
    "typetalk_topic": 1234
  },
  "handlers": {
    "ansible": {
      "type": "pipe",
      "command": "/etc/sensu/handlers/ansible_handler.rb"
    }
  }
}
```

ansibleで実行する`/etc/sensu/playbooks/check-http.yml`．
Nginxの再起動と，Typetalkの通知のみでシンプルにします．

```yaml
---
- hosts: check-http
  sudo: yes
  tasks:
    - name: restart nginx
      service:
        name=nginx
        enabled=yes
        state=restarted

    - name: notify to typetalk
      typetalk:
        client_id={{ typetalk_client_id }}
        client_secret={{ typetalk_client_secret }}
        topic={{ typetalk_topic }}
        msg='[sensu] restart nginx on {{ ansible_hostname }}'
```

後は，実際に対象サーバのNginxを止めると，Ansibleが実行されるはずです．
こんな感じで [Huboco](https://github.com/hico-horiuchi/huboco) ちゃんが通知してくれます．

![huboco.png](https://qiita-image-store.s3.amazonaws.com/0/58742/fb45bf4f-59cd-5852-7482-2d5dd2f524bc.png "huboco.png")


## まとめ

今ハマっているSensuとAnsibleを組合せる方法を紹介しました．
今回はNginxでしたが，[AnsibleでRailsをデプロイ](http://hico-horiuchi.hateblo.jp/entry/20141007/1412672371) しているので，こちらも対応させたいなと思います．

また，この記事のhandlerはGitHubで公開しています．
コメント・プルリクお願いします!

  - [hico-horiuchi/sensu-ansible](https://github.com/hico-horiuchi/sensu-ansible)