Mackerelは「エンジニアをワクワクさせる」ツールであることをサービスの大事な考え方の一つとして捉えています。 一体どういう場面でエンジニアはワクワクするのでしょうか。 簡単にインストールできるmackerel-agentや、直感的で触りやすい画面、チャットツールとの連携は大事な機能です。 しかし、監視ツールとしてもっと重要なのは、ミドルウェアのメトリックをどのように可視化し、何を監視するかということです。
Mackerelは公式のプラグインリポジトリに各種プラグインを揃えています (contributorの皆様ありがとうございます)。 これらはすべてGo言語で書かれています。 しかし、MackerelのプラグインはGo言語で書かなければいけない、ということはありません。 例えばカスタムメトリックのヘルプページのサンプルプラグインはRubyで書かれていますし、メタデータのヘルプのサンプルスクリプトはPerlで書かれています。
Mackerelのプラグインはどんな言語で書いていただいても構いません。 Rubyで書いてもシェルスクリプトで書いてもHaskellで書いても構いません。 世の中にはいろいろなミドルウェアがありますから、公式プラグインのなかに要件を満たせない場合もあるかもしれません。 自分の好きな言語でミドルウェアのメトリックを可視化する、そして監視をかけて、異常をいち早く察知する、つまり自らコードを書いて監視を作ること、これはすごくワクワクすることなのです。
こういうMackerelのhackableな部分はすごく大事だと思っていますし、これからも大事にしていきたいと思います。 初期のデザインドキュメントを見てみても、APIで様々な操作を行えることを最初から重要視していたことが伺える記述が見つかります。 そのまま実行したらグラフを確認できるcurlコマンドサンプルが置かれているのも、このあたりの考えが反映さています。
ところで、私はRustという言語がとても気に入っています。 家ではだいたいRustのことを考えていて、Rustで書かれたプロダクトのコードを読んだり、いろいろ作ったりして楽しんでいます。 好きな言語があるとMackerelのAPIを叩いてみたくなるわけで、mackerel-client-rsやmackerel-client-hsを作ってきました。 ただ、APIクライアント作りってやることが淡々としていて、正直飽きてくるんですよね。 まだすべてのAPIに対応できていないのでがんばらないといけないわけですが、いかんせんモチベーションが上がりにくいわけです。
一方で、Mackerelのメトリックプラグインを作るのは、APIクライアントを作るよりも格段におもしろいわけです。 そもそもメトリックのとり方はいろいろありますし、サーバーの変化を時系列データとして可視化できると、これがすごく楽しい。 ひょっとして、MackerelプラグインをRustで書いたらもっと楽しいんじゃない?
本題です。 RustでMackerelのプラグインを書くためのヘルパーライブラリ、mackerel-plugin-rsを作りました。 公式のgo-mackerel-pluginのRust版です。
まだ作ったばかりで、差分計算など一部の機能をまだ足りていませんが、今はモチベーションがすごく高いので、すぐに実装されると思います。 上記ライブラリを使って、手始めにloadavgプラグインとuptimeプラグインを書いてみました。 topやuptimeコマンドで3つの数字が並んだ状態のloadavgはよく目にしますが、実際にグラフにしてみると激しく変化するloadavg1、それに上下するloadavg5、よりなだらかなloadavg15というメトリックの特徴がよく出ていて、すごく美しいわけです。 プラグインのコードはこういう感じです。
extern crate libc; #[macro_use] extern crate mackerel_plugin; use std::collections::HashMap; use mackerel_plugin::*; pub struct LoadavgPlugin {} #[inline] fn get_loadavgs() -> Result<[f64; 3], String> { let mut loadavgs: [f64; 3] = [0.0, 0.0, 0.0]; let ret = unsafe { libc::getloadavg(loadavgs.as_mut_ptr(), 3) }; if ret == 3 { Ok(loadavgs) } else { Err("failed to get load averages".to_string()) } } impl Plugin for LoadavgPlugin { fn fetch_metrics(&self) -> Result<HashMap<String, f64>, String> { let mut metrics = HashMap::new(); let loadavgs = get_loadavgs()?; metrics.insert("loadavg.loadavg1".to_string(), loadavgs[0]); metrics.insert("loadavg.loadavg5".to_string(), loadavgs[1]); metrics.insert("loadavg.loadavg15".to_string(), loadavgs[2]); Ok(metrics) } fn graph_definition(&self) -> Vec<Graph> { vec![ graph! { name: "loadavg", label: "Load averages", unit: "float", metrics: [ { name: "loadavg15", label: "loadavg15" }, { name: "loadavg5", label: "loadavg5" }, { name: "loadavg1", label: "loadavg1" }, ] }, ] } }
プラグインを作るとき、まずどのようにしてメトリックをとるかを調べなければいけません。
これが普段ウェブアプリケーションを書くのとは違っていて実に楽しいわけです。
loadavgの取得方法について調べていくと、getloadavg(3)
がLinuxとBSDの両方で同じように使えることが分かりました。
mackerel-plugin-loadavgはlibc::getloadavg
を使うことで、macOSとUbuntuの両方で同じようにコンパイルして動作することを確認しています。
一方でuptimeのportableな取得する方法は難しく、Linuxではsysinfo
、BSDではsysctl
でboottime
を取得してから計算する必要がありました。
今回プラグインを書くためのライブラリを書く上で、公式のgo-mackerel-pluginをかなり読んで挙動を確認しました。 歴史的な経緯でinconsistentな挙動となってしまっていたり、プラグイン側でやったほうがよさそうな余計な機能があったり、エラーハンドリングを丁寧にやったほうがよさそうなところなど、よろしくないところがいくつかあることに気が付きました。 mackerel-plugin-rsを作る時間にはお給料は出ていませんが、go-mackerel-pluginへの理解が深まり、問題点が把握できたのはよかったなと思います。 いくつか良くない処理は直そうと思っています。 また、ヘルプの文言も不正確な部分は既にいくつか修正しています。
Mackerelのプラグインを知ることは、その監視対象を知ることです。 Redisのプラグインを作ろうと思ったら、redis-cli infoの各項目が何を表しているか、正確に把握する必要があります。 Linuxのメトリックを取得するということは、Linuxに対する理解を深めるということです。 Rustはそういう領域が得意な言語ですので、loadavgをとるには→man 3 getloadavgを見る→libcライブラリのドキュメントでgetloadavgを探すというようにすぐに目的の関数にたどり着くことができます。
Linuxや各種ミドルウェアへの理解を深めることは、エンジニアとしてすごくワクワクすることです。 監視対象に詳しくなると同時に、プログラムを書く楽しさを改めて教えてくれるのです。
Mackerelはただのサーバー監視ツールではありません。 プラグインを作るワクワクを、みなさんも是非実感してみてください。
本日10/5はMackerel Dayを開催します。 私も会場にいるので、是非お声がけください。
Mackerel開発チームのメンバーが書いた公式の入門書、出ました。
はてなでは、ワクワクしたいエンジニアに限らず、ディレクターやCRE (Customer Reliability Engineer) も募集しております。 hatenacorp.jp