mkrコマンドのjqオプションをマスターしよう! Mackerel APIからのデータ抽出とjqフィルタ活用術

こんにちは、Mackerel CREの id:kmuto です。いつもMackerelをご利用いただき、誠にありがとうございます。

早速ですが、皆さんはmkrコマンドを使ったことがありますか? 「mkr」は、MackerelのAPIサーバーと直接情報をやりとりできる、コマンドラインインターフェイス(Linux/Unixのターミナルや、WindowsのコマンドプロンプトおよびPowerShell)向けのツールです。Linuxディストリビューションではaptまたはyumなどのパッケージシステム経由でインストールでき、Windowsではmackerel-agentに同梱されています。

mackerel.io

mkrでできることは多岐にわたるのですが、本記事ではmkrを使ってMackerelから情報を取得し、さらにそれをmkr上で加工するテクニックを紹介します。

JSONとjq

mkrでMackerelのAPIサーバーから取得した情報は、JSON形式のデータになっています。

オーガニゼーションの情報を表示するサブコマンドmkr orgを実行してみましょう($はプロンプトです。Windows環境の場合、mkrコマンドの実行は"C:\Program Files\Mackerel\mackerel-agent\mkr.exe"とするか、C:\Program Files\Mackerel\mackerel-agentをユーザーの環境変数PATHに追加してください)。

$ mkr org
{
    "name": "mackerel-bizdemo",
    "displayName": "Mackerel🐟デモ環境"
}

JSONはJavaScriptのオブジェクト表記方法をもとにしたテキスト型のデータ交換用フォーマットで、キーと値のペア、配列、数値、文字列、真偽値、nullというシンプルなデータ型からなり、構造化されたデータを扱いやすいことから広く使われています。

mkrで取得するJSONは、Mackerel APIドキュメントに記載されているものに準拠しています。上記の例ではオーガニゼーション名(name)と管理名(displayName)それぞれのキー・値のペアが入っていますが、これはAPIドキュメントの「オーガニゼーションの情報を取得」相当です。

mackerel.io

小さなJSONデータならこのように目視で構造と情報を追えるものの、構造が複雑になっていくと読むのがたいへんですし、最終的な結果として出力したいのは生のJSONデータではなく、その中の何らかの文字列や数値であることがほとんどです。スプレッドシートに取り込んで見ることを目的としたCSV(カンマ区切り)やTSV(タブ区切り)にしたいこともあるでしょう。

JSONデータを抽出・加工するデファクトスタンダードのツールとして、「jq」があります。jqは、JSONを入力データとし、jqフィルタと呼ぶ一連の命令群で加工して、任意の出力データを生成します。

jqのGo言語向けの実装として「gojq」というライブラリがあります。gojqは完全なjqフィルタ互換ではありませんが、本記事で扱う範囲では問題ありません。mkrはgojqを取り込んでおり、情報取得のサブコマンドに備わった--jqというオプションでjqフィルタを指定して、取得したJSONデータを直接加工できます。

jqフィルタでできることは幅広いのですが、以降ではその一部を紹介しながらMackerelの情報を加工する例を挙げていきます。

例1:オーガニゼーション名や管理名を取得する

先ほどJSONの例を挙げましたが、そこからオーガニゼーション名や管理名を抽出してみましょう。

$ mkr org --jq ".name"
mackerel-bizdemo
$ mkr org --jq ".displayName"
Mackerel🐟デモ環境

--jqオプションの後に" "で囲んで1jqフィルタを記述します。

.はJSONの入力ストリーム(何も加工していないのでここでは入力データ自体)を表します。.nameはその入力ストリームにあるキーnameの値を抽出します。.displayNameも同様です。簡単ですね。

なお、mkr org--jqオプションがサポートされたのはmkrバージョン0.58.0以降(Windowsではmackerel-agentバージョン0.82.0以降)となります。もしflag provided but not defined: -jqというエラーになってしまったら、mkrを最新のものに更新してください。

mackerel.io

また、オーガニゼーション管理名は、英数字のみの制約があるオーガニゼーションに、日本語を含めた任意の別名を付けられるという便利な機能です。詳細については過去の告知記事をご覧ください。

例2:ホストをIDと名前で一覧する

次はオーガニゼーションに所属するホスト一覧を表示するサブコマンドmkr hostsを使い、結果を加工してみましょう。

mkr hostsをそのまま実行した結果の一部を示します。

$ mkr hosts
[
    {
        "id": "5ch1Pxwn14b",
        "name": "2acd3c8ef2f846b4863ba386eb96f407",
        "status": "working",
        "roleFullnames": [
            "blog-ecs:benchmarker"
        ],
        "isRetired": false,
        "createdAt": "2024-06-29T17:44:05+09:00",
        "ipAddresses": {
           ...
        }
    },
    {
        "id": "4adupJLoZ4w",
        "name": "ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal",
        "status": "working",
        "roleFullnames": [
            "Blog:worker"
        ],
        "isRetired": false,
        "createdAt": "2021-02-14T16:58:48+09:00",
        "ipAddresses": {
          ...
        }
     },
     ...
]

入力データは、個々のホストを配列の各要素とする[]で囲まれたJSON配列です。ここからホストID(id)を抽出してみます。

$ mkr hosts --jq ".[]|.id"
5ch1Pxwn14b
4adupJLoZ4w
...

.[]は入力ストリームの配列の各要素を受け取ります。|は左側の出力ストリームを右側の入力ストリームとするパイプラインで、ここでは各要素のidの値を取り出しています。

名前(name)も取得してみることにします。「,」を使ってキーの名前を列挙することで、それぞれの対応する値を取得できます。

$ mkr hosts --jq ".[]|.id, .name"
5ch1Pxwn14b
2acd3c8ef2f846b4863ba386eb96f407
4adupJLoZ4w
ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal
...

取得はできましたが、すべて改行されてしまってわかりにくい結果になってしまいました。このようなときには、結果を配列にして、それをタブ区切りのTSVにするのがよいでしょう。

# まず配列にして…
$ mkr hosts --jq ".[]|[.id, .name]"
["5ch1Pxwn14b","2acd3c8ef2f846b4863ba386eb96f407"]
["4adupJLoZ4w","ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal"]
...

# TSV形式で書き出す
$ mkr hosts --jq ".[]|[.id, .name]|@tsv"
5ch1Pxwn14b     2acd3c8ef2f846b4863ba386eb96f407
4adupJLoZ4w     ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal
...

IDと名前の値を取得している箇所を[ ]で囲んで配列を作成します。さらにこの配列を@tsvに渡すことで、タブ区切りで固有の文字エスケープも済ませたテキストが出力されます。

例3:マイクロホストのクラウドサービス情報を表示する

Mackerelはクラウドインテグレーションという機能でAWSAzureGoogle Cloudのマネージドサービスの監視ができますが、各サービスはマイクロホストとしてホスト一覧に掲載されます(VMなどのエージェントインストール可能なホストはスタンダードホストとなります)。

サブコマンドmkr hosts-vオプションを付けると、ホストがスタンダードホストかマイクロホストか、マイクロホストの場合はどのクラウドサービスかも表示されます。

$ mkr hosts -v
[
    {
        "id": "5ch1Pxwn14b",
        "name": "aa42c6a6736f4cdaa336f66abe160841",
        "size": "micro",
        ...
        "meta": {
            ...
            "cloud": {
                "provider": "fargate",
                "metadata": {
                    ...
                }
                ...
            },
            ...
        }
    },
    {
        "id": "4adupJLoZ4w",
        "name": "ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal",
        "size": "standard",
        ...
        "meta": {
            ...
            "cloud": {
                "provider": "ec2",
                "metadata": {
                    ...
                }
                ...
            },
            ...
        }
    },
    {
        "id": "4UWnwfGs4pf",
        "name": "502f1673983a4946830027d057570a1c",
        "displayName": "bizdemo_ecs_cluster",
        "size": "micro",
        ...
        "meta": {
            ...
            "cloud": {
                "provider": "ecscluster",
                "metadata": {
                    ...
                }
                ...
            },
            ...
        }
     },
     ...
]

マイクロホストに絞ってホストID、名前、クラウドサービスをTSV形式で出力してみましょう。

$ mkr hosts -v --jq ".[]|select(.size == \"micro\")|[.id, .displayName // .name, .meta.cloud.provider]|@tsv"
5ch1Pxwn14b     2acd3c8ef2f846b4863ba386eb96f407        fargate
4UWnwfGs4pf     bizdemo_ecs_cluster     ecscluster
3zcUAyh3Ztf     bizdemo-rds     rds
3zcUAxdWtLL     bizdemo-web-alb alb
...

入力のJSON配列に対して、条件に合った要素だけを抽出した配列を作るselectを通します。条件はsize"micro"(マイクロホスト)である、としました。

また、クラウドインテグレーションで登録されたホストには任意の管理名を付けることができますが(displayName)、管理名が存在したらそれを、なければもともとのホスト名(name)を使うために演算子//を使っています。

クラウドサービスの名前は配列要素のmetacloudproviderと構造を掘り下げた値なので、入力ストリームの「.」をつなぐことで取得しています。

例4:特定のURL宛ての外形監視設定を探す

外形監視を含めた監視ルールの設定は、サブコマンドmkr monitorsで取得できます。

$ mkr monitors
[
    {
        "id": "45VuBx47jxS",
        "name": "custom.alb.host_count.bizdemo-web-tg.healthy",
        "memo": "ALB配下の有効なWebサーバーが減少している",
        "type": "host",
        "metric": "custom.alb.host_count.bizdemo-web-tg.healthy",
        "operator": "<",
        "warning": 4,
        "critical": 2,
        "duration": 3,
        "maxCheckAttempts": 2,
        "scopes": [
            "Blog: web-lb"
        ]
    },
    {
        "id": "4qePHRTdsUy",
        "name": "Mackerelブランドサイト監視",
        "type": "external",
        "method": "GET",
        "url": "https://ja.mackerel.io",
        "maxCheckAttempts": 3,
        "followRedirect": true,
        "headers": [
            {
                "name": "Cache-Control",
                "value": "no-cache"
            }
        ]
    },
    ...
]

「mackerel」という文字列を監視対象のURLに含んでいる外形監視の情報を抽出してみましょう。

$ mkr monitors --jq ".[]|select(.type == \"external\" and (.url|contains(\"mackerel\")))|[.id, .name, .url]|@tsv"
4qePHRTdsUy     Mackerelブランドサイト監視      https://ja.mackerel.io
...

selectの中では、種類(type)が外形監視("external")であること、関数containsを使ってURL内に文字列「mackerel」を含むこと、の両方が満たされるAND条件としています。なお、文字列マッチを正規表現で記述したいときには、関数testmatchを利用できます。

例5:発生・解決の時刻も添えてアラートを一覧する

アラートの情報はサブコマンドmkr alertsで取得できます。解決済みのアラートも含めるには-wオプションも付けます。

$ mkr alerts -w
[
    {
        "id": "5ch7ndiFrJh",
        "status": "WARNING",
        "monitorId": "4G6yi55Yo1G",
        "type": "connectivity",
        "hostId": "4RXBJ9SQw67",
        "openedAt": 1719656875
    },
    {
        "id": "5chdsug4evS",
        "status": "OK",
        "monitorId": "45VugCVZyZw",
        "type": "host",
        "hostId": "4adupJLoZ4w",
        "value": 46.666666666666664,
        "openedAt": 1719656771,
        "closedAt": 1719657073
    },
    {
        "id": "5chcDEbKP3q",
        "status": "OK",
        "monitorId": "4UWnLYZ69x3",
        "type": "check",
        "hostId": "4adupJLoZ4w",
        "message": "CloudWatch Logs OK: 0 messages for pattern /ERROR/",
        "openedAt": 1719656346,
        "closedAt": 1719656406
    },
    ...
]

アラートの発生時刻や解決時刻は「epoch秒」で返されます。これは協定世界時(UTC)1970年1月1日午前0時0分0秒からの経過秒数のことです。

関数strflocaltimeを使うと、epoch秒からローカル時間表記の文字列に変換できます(ローカル時間が日本時間になるかは、OSやユーザーの環境設定に依存します。環境変数TZで一時的に切り替えることもできます)。

MackerelのWebコンソールのURL、現在の状態、種類、発生時刻と(解決していれば)解決時刻をTSV形式で並べてみましょう。

$ mkr alerts -w --jq ".[]|[@uri \"https://mackerel.io/my/alerts/\(.id)\", .status, (.openedAt|strflocaltime(\"%Y-%m-%d %H:%M\")), if .closedAt then (.closedAt|strflocaltime(\"%Y-%m-%d %H:%M\")) else \"-\" end]|@tsv"
https://mackerel.io/my/alerts/5ch7ndiFrJh       WARNING connectivity    2024-06-29 19:27     -
https://mackerel.io/my/alerts/5chdsug4evS       OK      host            2024-06-29 19:26     2024-06-29 19:31
https://mackerel.io/my/alerts/5chcDEbKP3q       OK      check           2024-06-29 19:19     2024-06-29 19:20
...

URLの生成にはエスケープ等も良きにはからってくれる@uriを使いました。構成するURL文字列内には\(.id)という記法でアラートIDの値を挿入しています。

また、解決時刻(closedAt)はまだクローズされていないアラートには存在しないため、if〜then〜else〜endの条件構文を使ってclosedAtが存在しないときには「-」を表示するようにしました。

例6:メトリックをCSV形式でダウンロードする

Mackerelに投稿されたメトリック値をCSVでダウンロードしたいというご要望はよくいただきます。sabadashiといったOSSのツールもありますが、ここではmkrを使ってCSVファイルに書き出してみましょう。

サブコマンドmkr metricsで、指定期間に投稿されたホストメトリックまたはサービスメトリックの値をまとめて取得できます(メトリック名はサブコマンドmkr metric-namesで調べられます)。

取り出し開始・終了の時刻は、前出のepoch秒で指定する必要があります。

現在時刻のepoch秒はLinuxではdate +"%s"、PowerShellではGet-Date -UFormat "%s"で調べられますが、やろうと思えば、mkrでも可能です2。以下は日付関数を使ってepoch秒を求める例です。

# 現在時刻のepoch秒を返す(日本時間2024年6月28日12:05:10に実行)
$ mkr org -jq "now|floor"
1719543910

# 現在時刻から6時間前のepoch秒を返す
$ mkr org -jq "now - 60 * 60 * 6|floor"
1719522310

# 時刻文字列からepoch秒に変換する
$ mkr org -jq "\"2024-06-28T12:05:10+0900\"|fromdate"
1719543910

nowは現在時刻のepoch秒を返します。小数点付きの精度で得られますが、MackerelのAPIサーバーが許容するのは整数のみなので、小数点以下を関数floorで切り捨てています(切り上げならceil、四捨五入ならroundを使います)。

6時間前のepoch秒を求める例では、簡単な数値演算をjqフィルタ内で実行しています。

また、関数fromdateを使うと、文字列表記した時刻からそのepoch秒を求められます。

上記で求めたepoch秒を使い、ホストID 4adupJLoZ4wのloadavg5ホストメトリックを取得して、CSV形式で書き出し(@csv)をしてみましょう。

# もともと出力されるJSONデータ
$ mkr metrics --host 4adupJLoZ4w --name loadavg5 --from 1719522310 --to 1719543910
[
    {
        "time": 1719522360,
        "value": 0.04
    },
    {
        "time": 1719522420,
        "value": 0.03
    },
    {
        "time": 1719522480,
        "value": 0.04
    },
    ...
    {
        "time": 1719543900,
        "value": 0.07
    }
]    

# CSV形式での書き出し
$ mkr metrics --host 4adupJLoZ4w --name loadavg5 --from 1719522310 --to 1719543910 --jq ".[]|[(.time|strflocaltime(\"%Y-%m-%d %H:%M\")), .value]|@csv"
"2024-06-28 06:06",0.04
"2024-06-28 06:07",0.03
"2024-06-28 06:08",0.04
"2024-06-28 06:09",0.05
"2024-06-28 06:10",0.08
...
"2024-06-28 12:05",0.07

# スプレッドシートなどで扱うためにファイルに書き出す
$ mkr metrics --host 4adupJLoZ4w --name loadavg5 --from 1719522310 --to 1719543910 --jq ".[]|[(.time|strflocaltime(\"%Y-%m-%d %H:%M\")), .value]|@csv" > 2024-06-28-loadavg5.csv

>はリダイレクト記号で、その右側に指定した名前のファイル(ここでは2024-06-28-loadavg5.csv)に書き出します。

なお、Mackerelの制約上、範囲を長くすると取得メトリックの粒度が1分よりも粗くなっていきます。1分粒度で取得したいときには、範囲を20時間以内に収める必要があります。

おまけとして、範囲で取得したメトリックのうち、最大の値を求めてみます。これはmax_byという関数で簡単に実現できます(最小はご想像のとおりmin_byです)。

$ mkr metrics --host 4adupJLoZ4w --name loadavg5 --from 1719522310 --to 1719543910 --jq "max_by(.value)|[(.time|strflocaltime(\"%Y-%m-%d %H:%M\")), .value]|@csv"
"2024-06-28 09:10",0.58

おわりに

駆け足でしたが、mkrのjqオプションを使ったMackerelの情報加工テクニックを紹介してきました。特にWindows Server環境では、Linux環境に比してOSの標準機能として提供されているフィルタツールが乏しく、PowerShellを使うのも複雑になりがちなので、mkrだけで加工を完結できるのは便利でしょう。

ぜひ皆さんの日々の運用の利便性向上にご活用ください!


  1. Linuxのターミナルでは' 'で囲むこともできます。この場合、jqフィルタ内で"を使いたいときに\"とエスケープする必要はありません。本記事ではWindowsのコマンドプロンプトでも動作するよう" "を採用しています。
  2. APIサーバーへ問い合わせをした上でデータを捨てるという無駄な通信が発生するので、スクリプトプログラムを作るときにはOSやプログラミング言語が提供するより適切な方法を選ぶのがよいでしょう。