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

  • 9
    いいね
  • 0
    コメント

こんにちは、@hico_horiuchiです。
この記事はDocker2 Advent Calendar 2016の3日目です。

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

Sensuとは?

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

sensu-diagram.png

SensuによるDockerの監視

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

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

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

Dockerのイベントについて

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

event_state.png

サンプルとして、rabbitmqコンテナを run , kill , rm したときのイベントを取ってみました。
ネットワークの接続や、ボリュームのマウントが実施されていることが分かりますね。
(RubyからDocker Remote APIを呼ぶのに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文字で良さそうな名前…ということで、砂滑にしました。)

sensu-server, sensu-api, uchiwa(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

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

events.png

まとめ・これから

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

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