Docker Events APIとSensu Proxy Clientsでコンテナ監視


こんにちは、[@hico_horiuchi](https://twitter.com/hico_horiuchi)です。
この記事は[Docker2 Advent Calendar 2016](http://qiita.com/advent-calendar/2016/docker2)の3日目です。

先日、社内でコンテナやクラウドネイティブの勉強会があり、そこでLTした内容をブラッシュアップしました。
Dockerは仕事で使っている訳ではなく、紹介するツールに新規性も実用性も無いですが、参考までに。

- [DockerEventsAPIとSensuProxyClientsでコンテナ監視 - esa-pages.io](https://esa-pages.io/p/sharing/4975/posts/21/10ed68af0ac6f680c5d2-slides.html#/)

## Sensuとは?

[Sensu](https://sensuapp.org/)は、2011年から開発されているRuby製のOSS監視ツールです。
監視ホストの自動登録など、Nagiosの問題点を解消し、クラウド環境に適した監視ツールとして作られました。
下の図のように、sensu-serverとsensu-client間の通信にRabbitMQ、設定や監視結果の保存にRedisを使っています。
(sensu-serverやミドルウェアは簡単にスケールアウトすることができます。)
また、JSON形式の簡素な設定ファイルなど、ChefやAnsibleなどの構成管理ツールとの相性も良いです。

- [Sensu, a monitoring framework | PorterTech](http://portertech.ca/2011/11/01/sensu-a-monitoring-framework/)
- [【DevOpsDays Tokyo 2013】クラウド時代のモニタリングツール、UNIXの思想が息づくsensuチュートリアル](https://codezine.jp/article/detail/7442)

![sensu-diagram.png](http://portertech.ca/images/2011-11-01/sensu-diagram.png "sensu-diagram.png")

## SensuによるDockerの監視

Sensuでは、Docker監視用の公式プラグインが提供されています。
ただ、少し触ってみて、機能不足を感じる部分がありました。

- [sensu-plugins/sensu-plugins-docker](https://github.com/sensu-plugins/sensu-plugins-docker)
  - [check-container-logs.rb](https://github.com/sensu-plugins/sensu-plugins-docker/blob/master/bin/check-container-logs.rb): 監視するコンテナ毎に設定が必要。
  - [check-container.rb](https://github.com/sensu-plugins/sensu-plugins-docker/blob/master/bin/check-container.rb): 監視するコンテナ毎に設定が必要。
  - [check-docker-container.rb](https://github.com/sensu-plugins/sensu-plugins-docker/blob/master/bin/check-docker-container.rb): 実行中のコンテナの数を基準にしている。

上のプラグインでは、sensu-clientを導入したDockerホストが1つの監視対象として登録されます。
しかし、今回は各コンテナを、それぞれsensu-clientとして監視したいと考えました。
ただ、一々Dockerfileを編集してsensu-clientを導入するのは面倒です。

そこで、Sensuの[Proxy Clients](https://sensuapp.org/docs/0.26/reference/clients.html#proxy-clients)という機能を使うことにしました。
Proxy Clientsは、sensu-apiに対して監視ホストや監視結果のJSONを投げると、登録してくれる物です。
(sensu-clientを導入していないホストも、sensu-clientとして登録できる、など。)
これで、コンテナ単位で監視でき、監視結果のハンドリング(通知など)もsensu-serverで行えるようになります。

## Dockerのイベントについて

Dockerでは、イメージをビルドする、コンテナを起動するなどのイベントが発火・記録されます。
今回は `create` で監視ホスト登録、 `kill` で監視結果登録するように実装しています。
(本当は終了ステータス等も考慮する必要があるのですが、まだその段階まで行っていないので…。)

- [docker events - Docker](https://docs.docker.com/engine/reference/commandline/events/)
- [Monitor Docker’s events - Docker Remote API v1.24 - Docker](https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/monitor-dockers-events)

![event_state.png](https://docs.docker.com/engine/reference/api/images/event_state.png "event_state.png")

サンプルとして、[rabbitmq](https://hub.docker.com/_/rabbitmq/)コンテナを `run` , `kill` , `rm` したときのイベントを取ってみました。
ネットワークの接続や、ボリュームのマウントが実施されていることが分かりますね。
(RubyからDocker Remote APIを呼ぶのに[swipely/docker-api](https://github.com/swipely/docker-api)を使いました、便利ですね。)

```
[1] pry(main)> Docker::Event.stream { |event| p event }                                                                                                                             
#<Docker::Event:0x007fed16026c40 @Type="container", @Action="create", @Actor=#<Docker::Event::Actor:0x007fed16026a38 @ID="a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", @Attributes={"image"=>"rabbitmq", "name"=>"rabbitmq"}>, @time=1480730174, @timeNano=1480730174259097295, @status="create", @from="rabbitmq">
#<Docker::Event:0x007fed16025138 @Type="network", @Action="connect", @Actor=#<Docker::Event::Actor:0x007fed16024f80 @ID="4d01638e5b1c4b44257d45dddab0958ef01b0cd9ffb9fda7bfa6af2eb1d77aed", @Attributes={"container"=>"a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", "name"=>"bridge", "type"=>"bridge"}>, @time=1480730174, @timeNano=1480730174418394592, @status=nil, @from=nil>
#<Docker::Event:0x007fed1601f9e0 @Type="volume", @Action="mount", @Actor=#<Docker::Event::Actor:0x007fed1601f760 @ID="e663057569416c62641ea9e9ded1c449f9a69b84a4858903636103e610e2d833", @Attributes={"container"=>"a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", "destination"=>"/var/lib/rabbitmq", "driver"=>"local", "propagation"=>"", "read/write"=>"true"}>, @time=1480730174, @timeNano=1480730174447237658, @status=nil, @from=nil>
#<Docker::Event:0x007fed16017128 @Type="container", @Action="start", @Actor=#<Docker::Event::Actor:0x007fed16016d68 @ID="a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", @Attributes={"image"=>"rabbitmq", "name"=>"rabbitmq"}>, @time=1480730174, @timeNano=1480730174677730843, @status="start", @from="rabbitmq">
#<Docker::Event:0x007fed1600c2f0 @Type="container", @Action="kill", @Actor=#<Docker::Event::Actor:0x007fed16003ba0 @ID="a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", @Attributes={"image"=>"rabbitmq", "name"=>"rabbitmq", "signal"=>"9"}>, @time=1480730187, @timeNano=1480730187279469309, @status="kill", @from="rabbitmq">
#<Docker::Event:0x007fed1523c6c0 @Type="container", @Action="die", @Actor=#<Docker::Event::Actor:0x007fed1523c2d8 @ID="a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", @Attributes={"exitCode"=>"137", "image"=>"rabbitmq", "name"=>"rabbitmq"}>, @time=1480730187, @timeNano=1480730187282653393, @status="die", @from="rabbitmq">
#<Docker::Event:0x007fed15235820 @Type="network", @Action="disconnect", @Actor=#<Docker::Event::Actor:0x007fed15235320 @ID="4d01638e5b1c4b44257d45dddab0958ef01b0cd9ffb9fda7bfa6af2eb1d77aed", @Attributes={"container"=>"a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", "name"=>"bridge", "type"=>"bridge"}>, @time=1480730187, @timeNano=1480730187497574672, @status=nil, @from=nil>
#<Docker::Event:0x007fed1522e368 @Type="volume", @Action="unmount", @Actor=#<Docker::Event::Actor:0x007fed1522dd78 @ID="e663057569416c62641ea9e9ded1c449f9a69b84a4858903636103e610e2d833", @Attributes={"container"=>"a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", "driver"=>"local"}>, @time=1480730187, @timeNano=1480730187530913425, @status=nil, @from=nil>
#<Docker::Event:0x007fed152274a0 @Type="container", @Action="destroy", @Actor=#<Docker::Event::Actor:0x007fed152272c0 @ID="a34b0b00cfe6201a9d597e01cf3bff34743b5165c73b8a3ba560258cb12cea11", @Attributes={"image"=>"rabbitmq", "name"=>"rabbitmq"}>, @time=1480730195, @timeNano=1480730195313928081, @status="destroy", @from="rabbitmq">
```

## Sunameriの紹介

では、Docker Events APIを監視してSensu Proxy Clientsを登録するツールを作ったので紹介します。
(クジラの仲間で、漢字2文字で良さそうな名前…ということで、砂滑にしました。)

- [hico-horiuchi/sunameri: 砂滑: Docker container monitor for Sensu proxy client](https://github.com/hico-horiuchi/sunameri)

sensu-server, sensu-api, [uchiwa](https://uchiwa.io/)(Sensuのダッシュボード)などの一式が入ったDockerfileを同梱しています。
Git、Ruby、Dockerが使える環境であれば、コマンドを幾つか打つだけで使い始められる筈です。

```
$ git clone git://github.com/hico-horiuchi/sunameri.git
$ cd sunameri
$ docker build -t hico/sunameri .
$ docker run -d -p 3000:3000 -p 4567:4567 --name sunameri hico/sunameri
$ bundle install --jobs=4 --path=vendor/bundler --binstubs=vendor/bin --without doc production
$ SENSU_API_HOST=localhost SENSU_API_PORT=4567 bundle exec ruby bin/sunameri
```

http://localhost:3000/ でuchiwaにアクセスできます。
試しにrabbitmqコンテナを起動すると、クライアント一覧に登録されていることが分かりますね。

![clients.png](https://qiita-image-store.s3.amazonaws.com/0/58742/88036e63-1f7c-6b46-f6b9-276742ec03ae.png "clients.png")

次に、rabbitmqコンテナを `kill` すると、アラートが上がってきました。
勉強会で「死亡時のログも見れると嬉しい」と言われたので、一緒に見られるようにしてみました。
(行頭に何か文字が入っているようです…除去方法知ってる方居たら教えて下さい…。)

![events.png](https://qiita-image-store.s3.amazonaws.com/0/58742/83263b9f-ea6e-ad98-13b5-317ee76ea52a.png "events.png")

## まとめ・これから

今回、とりあえずコンテナの登録とアラートの発火まではできるようになりました。
Docker Remote APIを触るのはほぼ始めてでしたし、イベントサイクルの勉強にもなって良かったです。
今はVPS上のDockerホストの監視に[Mackerel](https://mackerel.io)を使っていますが、いつかは大好きなSensuを使えるようにしたいですね。

- 監視するDockerホスト上に、Sunameriをコンテナとして載せられるようにしたい。
  - MackerelやDatadogなどと同じように、DockerのUnixソケットをマウントさせれば良さそうです。
  - [Dockerをモニタリングする - Mackerel ヘルプ](https://mackerel.io/ja/docs/entry/advanced/docker)
  - [コンテナ化したDatadog agentを使ってDockerのモニターリング&管理 - Etizolam](http://jhotta.github.io/blog/2014/06/11/docker-ize-datadog-with-agent-containers/)
  - [モニターリングサービスのDatadogを使ってDockerをモニターリング&管理 - Etizolam](http://jhotta.github.io/blog/2014/06/10/monitoring-docker-with-the-datadog/)
- メトリクスを取得して時系列データベースに投げたり、閾値を設定してアラートを上げられるようにしたい。
  - Docker Stats APIを使えば、コンテナ毎のメトリクスを取得できそう。
  - [Get container stats based on resource usage - Docker Remote API v1.24 - Docker](https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/get-container-stats-based-on-resource-usage)

まだまだ不足している機能や課題がありますが、引き続き細々と開発を続けて行こうと思っています。
以上、ありがとうございました。これからAdvent Calendarを書かれる方、楽しみにしてます&応援してます!