こんにちは、Mackerel CREの id:kmuto です。
9月25日に、Mackerelのエージェント用チェックプラグイン「check-mackerel-metric」をリリースしました。
このプラグインは、クラウドインテグレーションを利用した場合など一部標準の機能ではできない、ホストメトリックの途切れ監視を実現するものです。たとえばcheck-mackerel-metric -H <hostId> -n "custom.ecs.running_task.batch-cluster.count -w 30 -c 60
という指定をしておくと、ホストID <hostId>
のメトリックcustom.ecs.running_task.batch-cluster.count
について、現在から30分前までの間に何も投稿されていなければWARNING、60分前までの間に何も投稿されていなければCRITICALのアラートを発行します。
本記事では、Go言語ビギナーのCREが、お客さまのご要望を機にこのチェックプラグインを設計・開発し、開発チームのアドバイスを受けながら、サポート対象のプロダクトとしてリリースするまでの道のりをご紹介します。
メトリック途切れ監視について考える
私たちCRE(Customer Reliability Engineer)は、はてな社が提供するSaaS型監視サービスMackerelについて、お客さまのさまざまなご質問に対してエンジニアの知見からお答えするほか、検証やご要望対応のためのちょっとしたプログラムを書くこともしばしばあります。
複数のお客さまからいただいていたご要望の1つに、「メトリックが投稿されなくなったことを監視したい(途切れ監視したい)」というものがありました。
少々細かい話になりますが、Mackerelがテレメトリーデータとして投稿を受け付けるメトリックには、ホストメトリックとサービスメトリックがあります。前者のホストメトリックは、エージェント(mackerel-agentあるいはmackerel-container-agent)をインストールしたサーバーや、クラウドインテグレーションを使ってクラウドのテレメトリーデータを取得したマネージドサービス(たとえばAWSのALBやRDS、ECSなど)から収集されるメトリックです。後者のサービスメトリックは、Mackerelの管理単位である「サービス」に対してユーザーが独自に投稿できるメトリックです。
このうち、Mackerelが途切れ監視できるのはサービスメトリックのみです。
ホストメトリックについては、mackerel-agentをインストールしているホストであれば、一定期間何もメトリック投稿がなかったときに「connectivityアラート」というアラートをMackerelが発行するので異常に気付くことができます。しかし、マネージドサービスのメトリックの場合にはconnectivityアラート相当のものが存在しません。
「実行タスク数が0になったらアラートを上げたい」など、閾値を使った監視がしたくても、監視対象であるタスク数が0のときはメトリック投稿が行われず、Null値になるような仕様の場合は閾値が機能しません。
そのため、スクリプトを作成して、クラウドが提供する監視の仕組み(Amazon CloudWatchなど)をMackerelと併用してください、というのが歯痒さを感じながらのお客さまへのご回答の常でした。
概念実証を書く
何かもっと良い方法がないかを考えているうちに、時間をさかぼったメトリックをMackerelのAPIサーバーから取得し、投稿が1つも存在しなければアラートを出すというチェックプラグインを作るという発想が生まれました。
mackerel-agentのようなエージェントには、メトリック取得あるいは定期チェックを行うプラグインを追加できます。後者に相当するチェックプラグインは、エージェントから1分ごとに呼び出され、正常、あるいはWARNING(注意)、CRITICAL(警告)、UNKNOWN(未知)のいずれかの状態を終了コードで返します。これがMackerelのAPIサーバーに送られ、Mackerelの監視アラートとなります。
MackerelのプラグインのほとんどはGo言語で書かれており、Windowsを含めた複数OSに対応するにはGo言語で書くのが妥当な選択です。とはいえ、私自身はGo言語ビギナーでハードルが高かったので、まずはそもそもこのアイデアがうまくいくかの概念実証として、シェルスクリプトで実装してみました。
MackerelのAPIサーバーとやり取りするCLIとしてmkrコマンドがあります。mkr metrics
サブコマンドを使って指定した期間のメトリックの取得を試行し、その結果のJSONが空の配列であれば、期間内にメトリックが投稿されていない(途切れている)と判断できます。
実装したシェルスクリプトの中核部分はgistに掲載しています。
実際のホストを対象にいろいろ試してみて、実用上問題ないことを確認できました。
Go言語で実装する
シェルスクリプトでの概念実証は成功しましたが、プロダクト化にあたってはGo言語で実装するのが必須要件です。
Go言語のコード自体はMackerelのエージェントやプラグインなどで見てはいるものの、実際に1から書いてみたことはなく、「Tour of Go」をやってみたことがある、という程度の知識でした。
今回は『初めてのGo言語』(オライリー・ジャパン)を参考書としつつ、チェックプラグインの実装例として公式Mackerelチェックプラグイン集go-check-pluginsの中で最新のcheck-dnsプラグインのコードを真似ることにしました(なお、メトリックプラグインの場合はgo-mackerel-pluginというリポジトリがあり、こちらはドキュメントも揃っています)。
実際のところ、概念実証を済ませていたので処理としてすべきことは明確で、仕組みとしても並行処理などの複雑な技術は不要、check-dnsのコードも見やすいという事情で、実装自体は予想外に苦労しませんでした。
初期の開発のものではなく、後述の開発レビューを受けた後のものとはなりますが、以下のリンク先に実装本体のコードがあります。
- メトリックの取得:mackerel-client-goパッケージの
FetchHostMetricValues
関数を使えば簡単でした(当初ホストメトリックのみの対応でしたが、サービスメトリックへの対応も容易だったので実装しました)。また、さかのぼれる上限として、1日ごとに投稿されるメトリックをたまに見かけることを勘案し、「1,441分(1日前+実行タイミングを吸収するための1分)」としました。 - チェック状態の返却:checkersパッケージを利用し、状態を示す関数(
Ok
、Warning
、Critical
、Unknown
)に引き渡したいメッセージを指定するだけです。 - APIキーの参照:MackerelのAPIサーバーに問い合わせや投稿をするにはAPIキーが必要です。エージェントは自身の設定として保有していますが、プラグインまでは伝播されません。mkrコマンドは「環境変数
MACKEREL_APIKEY
があればその値を使う」「値がなければエージェントの設定ファイルからAPIキーを拾う」という挙動になっているので、mkrのリポジトリを眺めて、該当の処理部分を流用しました。 - プラグインの引数の処理:check-dnsプラグインではgo-flagsパッケージを使って引数の処理をしていたのですが、値の任意の検証がしづらく、またGitHubを見てもリリースが止まっていてIssueも店晒しになっていたため、そのIssueの中で代替として挙げられていたgo-argパッケージを使いました。
- テストの用意:公式チェックプラグインのテストのやり方を参考に、引数のテスト(指定不足や異常値、相反する指定など)と、メトリック取得のテスト(モックのAPIサーバーを用意し、境界でのメトリック取得の有無や、指定のホストやメトリック名がないときの挙動を照合する)を作成しました。
まったくトラブルにぶつからずに書けたというわけではなく、go run
で実行しては直し、の繰り返しはしていましたが、Visual Studio CodeでのGo言語サポート——特に型チェック、パッケージの関数の候補表示、コード整形は大いにそのありがたみを感じました。格好良くデバッガを使う域には至っていないので、fmt.Printf
で頻繁に結果を見ていましたが、書式指定子%v
(型によらずデフォルトフォーマットで出力)の存在にも助けられました。
開発チームのアドバイスを受ける
英語・日本語で書いたドキュメントも用意し、個人OSSであればすぐにでも公開できる状態にはなりましたが、「はてな公式ではない個人OSSプロダクトは社内的に導入しづらい」というご意見もいただいており、せっかく広く便利にユーザーの皆さまに使っていただけそうなものなのに正式にご案内しづらいという状況は避けたいと思いました。
Mackerelが公開しているリポジトリとして、エージェントやライブラリなど正式サポート対象であるmackerelioと、実験的でサポートもベストエフォートのmackerelio-labsがあります。
そもそもの途切れ監視のアプローチが妥当かも含めて開発チームに相談し、mackerelio-labsでまずは公開することに決まりました。
Mackerelの名前でリリースするからには、開発チームのレビューは必須です。Mackerel開発チームのid:ne-sachirouさんが今回のコードレビューをしてくださいました。詳細に見ていただきましたが、主なアドバイスを次に挙げます。
- フォルダ構成:Organizing a Go module - The Go Programming Languageに従って、checkmackerelmetricパッケージのサブフォルダは
lib
でなくcheckmackerelmetric
のほうがよいでしょう。 - テストすべき対象: 引数テストにおいて、ある引数で引数処理の関数が成功することをテストするのではなくて、ある引数で引数処理の関数の結果がどうなるかをテストしましょう。
- 依存パッケージチェックの導入:RenovateかDependabotを設定すると保守しやすいでしょう。
- CIの実施:GitHub Actionsを使ってテストを回すなどのCIを用意しましょう。
- バイナリのビルドとリリース:mkrコマンドでプラグインをインストールできるよう、リリースタグを打ってバイナリを提供しましょう。GoReleaserを使うとよいでしょう。
いただいたアドバイスをもとに、情報を調べたり、ほかのMackerelのリリースリポジトリの設定を参照したりして、改善を施していきました。
リリースする
アドバイスにもあったとおり、ビルドとリリースについてはGoReleaserを初めて使ってみました(GoReleaser設定の.goreleaser.yaml、GitHub Actions設定のrelease.yml)。
GitHub Actionsと組み合わせて、タグを打つだけでクロスプラットフォームビルドとクロスアーキテクチャビルドが行われ、リリースに必要な作業がすべて行われることには感動です(今さら気付いたのかという声も聞こえてきそうですが……)。
プラグインをインストールするためのmkr plugin install mackerelio-labs/check-mackerel-metric
コマンドで正常にインストールでき、開発環境のmacOSだけでなく実機のLinuxとWindowsでもコマンドが正しく動作することを確かめます。
ユーザーの利便性を上げるために、さらに短縮化した形のmkr plugin install check-mackerel-metric
コマンドでインストールできるよう、「mkr plugin installに対応したプラグインを作成する」を参考にプラグインレジストリにPRを送り、マージしていただきました。
これで準備万端です!
まとめ
ご要望をいたいだいていたお客さま方には、リリース後に早速ご連絡し、良い反応をいただきました。
シンプルで小さなプラグインではありますが、お客さまのご要望をもとに設計・概念実証、Go言語での開発、開発チームの知見あるレビューと進み、(現時点ではlabsに置いてはいますが)お客さまへ自信をもってお届けできるプロダクトリリースまで実現できたのは、とても良い体験でした。
SaaS型監視サービス「Mackerel」はユーザーの皆さまの声をもとに日々改良を続けています。ぜひ機能についてのご意見・ご要望をお寄せください。