自律分散監視システムとそれを利用したネットワークグラフ可視化への挑戦

はじめに

はてなサマーインターン2017の大規模システムコースの成果報告をします。

今年の大規模システムコースではメンターのid:masayoshiさんとid:y_uukiさんの下、自律分散監視システムとそれを利用したネットワークグラフの可視化に取り組みました。自律分散監視システムでは単純なクラスタリングによる死活状況の確認だけではなくアプリケーションレベルの疎通確認を行えるものを実現しました。またどのようにしてクラスタを形成するかという問題に取り組む内に、サービス間のネットワーク上のつながりを取得できるようになり、その情報でサーバー間の関係性の可視化を行いました。この記事では、それらの詳細を説明します。

自律監視システムの実現

中央サーバー型の監視システム

従来の監視システムは、中央に各サーバーのメトリクスなどを保管する監視サーバーを持ったものが多いです。こうした監視システムを、今回は中央サーバー型の監視システムと呼ぶことにします。

f:id:y_uuki:20170912124305p:plain
中央サーバー型の監視システムのイメージ(正常)

中央サーバー型の監視システムの実現方法はいくつか存在あると思いますが、今回はその詳細は省きます。1

こうした中央サーバー型の監視システムの問題点は、例えば監視サーバーとサービスを提供しているサーバーが別のネットワークにあるときに、その間が切断されてしまうと、正常に監視をすることが出来ないことがあります。また、中央サーバーがダウンしたときにも当然ながら監視は行えません。

f:id:y_uuki:20170912124302p:plain
中央サーバー型の監視システムのイメージ(異常)

こうした状況でも監視をするための仕組みとして自律分散監視システムを考え、実装に取り組みました。

自律分散監視システム

自律分散監視システムとは、各サーバーにインストールされたエージェントが通信し合い、相互に監視するようなシステムです。

f:id:y_uuki:20170912124239p:plain
自律分散監視システムのイメージ

このシステムには中央サーバーがおらず、各サーバーがダウンしたときに他のサーバーがそれを検知することができるのが特徴です。

また、今回はこの検知をする部分フォーカスすることにして、障害の通知は検知した情報をメトリクスとしてMackerelに送信することで、Mackerelに通知を任せました。

さて、このような自律分散監視システムとして、Serfを思い浮かべる人も多いのではないでしょうか。

SerfはVagrantやConsulなどを開発しているHashicorpの開発したソフトウェアです。Serfをインストールしたサーバー(ノード)間で通信し合ってクラスタを形成し、クラスタ内でメッセージの伝達をしたり、クラスタ内のノード数の増減を検知することなどができます。このクラスタにはリーダーなどはおらず、全てのノードが対等に通信し合います。特にクラスタ内のノード数の増減が検知できることが今回は重要で、単純にノードを減らしたときだけではなく、何か異常があってノードがクラスタから抜けてしまった際にも検知することができます。

Serfはクラスタを構成するために内部でGossipプロトコルというものを利用しています。これは通信量をなるべく抑えつつクラスタ内で情報をやり取りするためのプロトコルで、詳細についてはこちらなどを参照ください。

そして幸運なことに、SerfはこのGossipプロトコルによってクラスタを形成する部分をmemberlistというライブラリに切り出して提供しています。

これでサーバーが相互に通信し合ってクラスタを形成する部分を素早く開発することができました。しかしmemberlistを使うだけではただクラスタを組むだけなので、検知できるのもノードの増減だけであり、監視と呼ぶには少し貧弱です。また、他にもいくつかの問題が考えられました。

  1. そのサーバーで動いているアプリケーションが生きているかどうかを知りたいのに、このままではクラスタを組むためのプログラム(以下エージェントと呼びます)が死んでいることしか検知できない。
  2. エージェントがクラスタに参加するためには最初にエージェントの動いている他のサーバーのIPアドレスは必要だけど、どうやってそれを手に入れるか。

これらの問題を解決することが今回のインターンでの主要な活動でした。

アプリケーションレベルの相互監視

まずは一つ目の問題、

そのサーバーで動いているアプリケーションが生きているかどうかを知りたいのに、このままではクラスタを組むためのプログラム(以下エージェントと呼びます)が死んでいることしか検知できない。

についてどのように対処したかを説明します。

memberlist(Gossipプロトコル)は定期的に、他ノードの生存を確認するためにクラスタ内のいくつかのノードに対して生存を確認するメッセージ(pingと呼びます)を飛ばします。また、memberlistは各ノードにクラスタ内で共有されるメタ情報を付加することができます。そこでメタ情報にそのサーバーで何が動いているかという情報を乗せて、pingを飛ばすタイミングに合わせて相手に合わせた疎通確認を行うようにしました。相手に合わせた疎通確認というのは、例えば相手がHTTPサーバーならHTTPのHEADリクエストを飛ばす、RedisならRedisのPINGコマンドを飛ばす、などです。

この相手に合わせた疎通確認を行う部分はpng (Ping Next Generation)というGo言語のライブラリとして公開しました。

pngは現在、HTTP/WebSocket/Redis/MySQL/Postgres/AMQP/TCPに対応しています。ぜひ使ってみてください(これが宣伝です)。

どうやってクラスタを形成するか?

次に二つ目の問題、

エージェントがクラスタに参加するためには最初にエージェントの動いている他のサーバーのIPアドレスは必要だけど、どうやってそれを手に入れるか。

についてです。

これは非常に難しい問題で、また多くの解決策があるのではないかと思います。例えばSerfでは、Serf自体の通信以外にmDNSとしても機能していて、別のサーバーでSerfが起動した際にmDNSのクエリを投げることでIPを知りクラスタを形成することができます。しかしmDNSを使った場合の問題点として、AWSのネットワークで動作させることが難しいということが挙げられます(このことはSerfにもIssueとして報告されています。参考)。

そこで今回はサーバーの実際の通信を見て(/proc/net/tcpを読んで)、そこから他のサーバーらしきIPアドレスを見つけたらクラスタの参加を試みる、という実装をしました。エージェントの起動時に自身のサーバーで動いているミドルウェアのlistenしているポートを指定して、そのポートにつなぎにきているIPアドレスへの参加を試みます。

この方法の欠点は実際に通信が無いとクラスタが形成されないことですが、実際に稼動しているWebサービスのサーバーでは通信が途絶えることはほとんど無いので問題ではないと考えています。

実験

こうして実装したエージェントを、Webアプリケーションを模した構成のDockerで建てたコンテナ100個にインストールして動作を確認しました。始めはエラーが出て動作しなかったのですが、これはARPテーブルが溢れていることが問題だったので、カーネルのパラメーターをチューニングしたら動作するようになりました。コンテナを200個に増やすとまた別のエラーが出て上手く動かなかったのですがこれも同様に、Dockerを動かしているホストのチューニングが不十分で、大量のコンテナを動かしきれていないのだと考えています。

100個のコンテナで動かした際も1個のコンテナ辺りの通信量は1分間に数KBもなく、この場合では通信の負荷は許容できるものでした。しかし、これ数千台の規模になった際の負荷はまだ確認していないので、検証の余地があると考えています。

ネットワークグラフの可視化

サーバー間の関係を可視化する

ここまでで、今回開発したGossipプロトコルを用いた相互監視について説明をしました。

サーバー監視はサービスを安定して提供するために重要なものであり、大規模なシステムではその重要性が高まります。

その大規模なシステムは複雑になり、サーバー同士の関係を把握することが難しくなるので、システムの維持コストが大きくなります。

ここで私たちは、こうしたサーバ同士の関係性を図にすることで、より大きなシステムを容易に理解できるようにしたいという動機から、分散監視システムで形成したクラスタから収集したデータを元に可視化を行うツールの開発に取り組みました。

今回は、エージェントが対象サーバーの通信状況をMackerelに送信するように設計し、それらを集計して関係図を生成するという方法を取りました。

Graphvizでの可視化

まず初めに、Graphvizでの可視化を試みました。Graphvizはグラフ(グラフ理論のほう)の描画ソフトで、この分野では著名なのではないかと思います。

http://www.graphviz.org/

Graphvizでサーバーの関係性を表示すると、こんな風になりました。

f:id:y_uuki:20170912124254p:plain
Graphvizでの可視化の例

これはリクエストをNginx(web)が受けアプリケーションサーバー(app)にプロキシして、そのアプリケーションサーバーがRedisに読み書きするという構成の簡単なWebアプリケーションの通信を可視化した例です。なんとなく、この構成が読みとれるのではないかと思います。また、Redisにmasterが存在していてmaster-slaveの構成になっていることも分かります。

このようにしてGraphvizで可視化することはできたのですが、サーバーの台数が多くなるとGraphvizでの表示が難しくなってきます。アプリケーションサーバーとRedisサーバーを50台ずつにしたものでは、横に長くなりすぎて見るのが難しくなってしまいました。

もちろんGraphvizでの表示の調整をすればもう少し見れるものになりますし、表示するサーバーの数をある程度絞るなどの工夫もできます。しかし、表示を調整するのはグラフの生成と確認を繰り返さなければいけなくて大変ですし、どのように情報を絞りたいかは状況によって違うと思うので、それらを適切に表現するのは難しいです。

そこでグラフDBに目を付けました。

Neo4jでの可視化

Neo4jはグラフDBの一つです。グラフDBはグラフ構造の格納に適したデータベースです。グラフDBに関してはこのスライドが分かりやすいのではないかと思います。

ここでははてなの実際に動作しているサーバーの通信の情報を入れて、Neo4jのWeb UIで表示してみました。次の画像はその様子です。それなりに分かりやすくなったのではないかと思います。

f:id:y_uuki:20170912124321g:plain
Neo4jでの可視化の画像

可視化UIの改善

Neo4jのWeb UIでは、グルーピングして配置をするというのがやりにくいと感じました。

そこで、Neo4jをグラフDBとして利用しつつ、Neo4jから取得したデータをを利用してより柔軟に配置できるようなWebUIを開発しました。

グラフ描画にはcytoscape.jsとcola.jsを利用しました。

実際に先ほどと同じグラフを操作すると以下のようになりました。

f:id:y_uuki:20170912124252g:plain
Neo4jの自作フロントエンドの画像

サービスの種類でグループ化されていて、グループ単位での操作ができてより便利になったと感じています。

また、2つのロールを指定して、その間のロールのみを表示するような操作も実現しています。

まとめと今後の展望

今回作ったものは、最終的にこのような構成になりました。

f:id:y_uuki:20170912124243p:plain
構成図

サーバーにインストールされたエージェントが互いに監視し合い、検知結果や通信の情報をMackerelに送信します。そして通信の情報をMackerelからNeo4jへと収集クライアントで移して、最終的にNeo4jのデータを可視化UIで表示しています。

また、こうして実装することで見えてきた課題がいくつかあります。

  • 分断された複数のネットワークにまたがってmemberlistでクラスタを形成すると不安定になる。
  • ALBのようにIPアドレスの分からないものが通信の途中にあると、可視化が上手くいかない。

また、今後の展望としては、Mackerelのエージェントに今回実装したような機能が取り込まれればいいと思っています。

この記事は昔のid:motemenさんに似ているらしいid:makenowjustと、京都から帰りたくないという気持ちのid:t-k3ntarooの二人でお送りしました。ありがとうございました。


  1. 例えばMackerelのようにエージェントが監視サーバーにメトリクスを送る方法や、反対に監視サーバーが監視しているサーバーのメトリクスを取りに行く方法などがあります。