はじめに
こんにちは。エンジニアリングチームの「いかろちゃん」こと清水です。
近年、マイクロサービスアーキテクチャの普及に伴い、サービス間通信にGoogleが開発したオープンソースのRPC(Remote Procedure Call)フレームワークであるgRPCを採用するシステムが増えています。
HTTPの監視であれば、成熟した監視方法が存在します。たとえばハートビーツで採用しているOSSの監視ツールであるNagiosでは標準プラグイン集に含まれるcheck_httpを利用することができます。しかし、gRPCはHTTP/2上でバイナリエンコーディングされたメッセージをやり取りするため、従来のHTTP監視プラグインではそのまま対応できません。
こうした課題を解決するために、ハートビーツではcheck_httpのgRPC版ともいえる監視プラグイン「check_grpc」を開発し、このたびOSSとして公開しました。本記事ではその概要と使い方をご紹介します。
check_grpcの概要
check_grpcは、gRPCサービスに対してUnary RPCメソッドを動的に呼び出し、その結果をNagios互換の終了コードで返す監視プラグインです。主な特徴は次のとおりです。
- gRPCメソッドを動的に呼び出せる。Protocol Buffersのdescriptor setファイル(.pb)を利用するため、Goのスタブコードを事前に生成する必要がない
- レスポンスタイムを監視できる。WARNINGとCRITICALのしきい値を設定し、レスポンスタイムに基づいたアラートを発報できる
- レスポンス内容を検証できる。レスポンスに期待する文字列が含まれているかを検査する
- gRPCステータスコードを検証できる。期待するコードと実際のコードを比較する
- TLSを使用したセキュアな接続に対応している
- 任意のgRPCメタデータを送信できる。認証トークンやリクエストIDなどを付与してリクエストできる
- Nagios互換の終了コードを返す。OK(0)、WARNING(1)、CRITICAL(2)、UNKNOWN(3)を返すため、NagiosのほかMackerelやZabbix、Sensuなど主要な監視システムと連携できる
- マルチプラットフォームに対応している。Linux、Windows、macOSのamd64・arm64向けにビルド済みバイナリを提供している
gRPCとは
まず、check_grpcの使い方を理解するうえで前提となる、gRPCの基本的な仕組みを整理します。gRPCは.proto ファイルでサービスのインターフェースとメッセージ構造を定義し、各言語向けのコードを自動生成して利用します。言語はGo、Java、Python、C++などに対応しています。また、通信パターンは、Unary(単一リクエスト・単一レスポンス)、Server Streaming、Client Streaming、Bidirectional Streamingの4種類です。
gRPCを構成する技術要素
gRPCは主に3つの技術要素から成り立っています。
1つ目は、Protocol Buffers(protobuf)によるスキーマ定義とバイナリエンコーディングです。.proto ファイルで定義したスキーマに基づき、メッセージはコンパクトなバイナリ形式でシリアライズされます。テキストベースのHTTP/JSONと比べてサイズが小さく、パース処理も高速ですが、テキストベースのリクエスト・レスポンスを前提とした監視プラグインでは内容を正確に扱えません。
2つ目は、HTTP/2ベースの通信です。gRPCはHTTP/2上で動作し、多重化やヘッダ圧縮、双方向ストリーミングなどの機能を活用した高効率な通信を実現します。裏を返せば、HTTP/1.1のみを前提とした一般的なHTTP監視プラグインでは、gRPCのRPCを正しく呼び出すことができません。
3つ目は、独自のステータスコード体系です。gRPCはHTTPの200/4xx/5xxとは別に、OK、CANCELLED、UNKNOWN、INVALID_ARGUMENT など17種類の独自ステータスコードを持ちます。呼び出しの成否は、このgRPCステータスコードで判断するのが基本です。
このような特性から、gRPCサービスの監視には専用のクライアント実装が必要になります。check_grpcは、gRPC公式のGo実装(grpc-go)をクライアントとして利用し、3つの特性にそれぞれ次のように対応しています。
- バイナリエンコーディング:descriptor setファイルからスキーマを読み込み、リクエストとレスポンスを動的にエンコード・デコードする
- HTTP/2ベースの通信:grpc-goによりHTTP/2で接続する
- 独自のステータスコード体系:呼び出し結果からgRPCステータスコードを取り出し、–expect_status_code(-e)で指定した値(デフォルトは 0、つまり OK)と比較する。一致しなければCRITICALと判定する
このうち、利用者が事前に準備する必要があるのがdescriptor setファイルです。以下で詳しく説明します。
descriptor setファイル
ここでは、descriptor setファイルの仕組みと生成方法を説明します。gRPCのスキーマは、通常は .proto ファイルとして人が読めるテキスト形式で記述されます。これをツールから機械的に扱えるようバイナリへ変換したものが descriptor setファイル(.pb) です。protoc コマンドで次のように生成します。
# 基本的な生成(-I にはprotoファイルやimportの解決の基準となるディレクトリを指定)
protoc -I <proto_path> --descriptor_set_out=service.pb service.proto
# 他のprotoファイルをimportしている場合は --include_imports を付与
protoc -I <proto_path> --include_imports --descriptor_set_out=service.pb service.proto
# gRPC標準のヘルスチェックサービスの場合
# (health.protoはgrpc/grpc-protoリポジトリで公開されています)
git clone https://github.com/grpc/grpc-proto.git
protoc -I grpc-proto --descriptor_set_out=health.pb grpc/health/v1/health.proto
check_grpcは、起動時にこのdescriptor setファイルを読み込み、サービスとメソッドの定義を動的に解決します。これにより、Goのスタブコードを事前に生成することなく、任意のgRPCサービスのメソッドを呼び出せます。
check_grpcを開発した背景
ここでは、check_grpcを開発するに至った経緯を説明します。
ハートビーツでは、運用監視にNagiosを利用しています。あるとき、gRPCサービスの死活と応答性能、応答内容を監視したいというニーズが生まれました。しかし、前述の3つの特性(バイナリエンコーディング、HTTP/2必須、独自ステータスコード体系)が壁となり、従来のHTTP監視プラグインは利用できませんでした。check_http のような汎用的に使えるgRPC監視プラグインも見当たりませんでした。
こうした背景から、gRPCネイティブな監視を実現するプラグインとしてcheck_grpcを開発しました。
使い方
ここからは、check_grpcのインストール方法から具体的な実行例、制限事項までを順に説明します。
インストール
check_grpcのインストール方法は3通り用意しています。
もっとも手軽なのは、ビルド済みバイナリを利用する方法です。Releases ページからお使いのプラットフォーム向けのバイナリをダウンロードし、次のように展開・配置します。
# 例: Linux amd64
curl -L -o check_grpc.zip https://github.com/heartbeatsjp/check_grpc/releases/latest/download/check_grpc_linux_amd64.zip
unzip check_grpc.zip
chmod +x check_grpc
sudo mv check_grpc /usr/local/bin/
Goの開発環境が整っている場合は、go install で直接インストールできます。
go install github.com/heartbeatsjp/check_grpc@latest
ソースコードから自分でビルドしたい場合は、リポジトリをクローンしてビルドします。
git clone https://github.com/heartbeatsjp/check_grpc.git cd check_grpc go build -o check_grpc .
基本的な使い方と使用例
次にcheck_grpcの基本的な実行方法と代表的な使用例を紹介します。check_grpcは次の形式で実行します。
check_grpc -H <host:port> -m <package.Service/Method> -D <descriptor.pb> [options]
必須フラグは次の3つです。
| フラグ | 短縮形 | 説明 |
|---|---|---|
--host | -H | 監視対象のgRPCエンドポイント(host:port) |
--method | -m | 呼び出すgRPCメソッド(package.Service/Method 形式) |
--descriptor_set_file | -D | descriptor setファイルのパス(*.pb) |
以下、具体的な使用例を見ていきます。
もっとも基本的な例は、gRPC標準のヘルスチェックサービスを呼び出す例です。
check_grpc -H localhost:50051 -m grpc.health.v1.Health/Check -D health.pb
# OK: gRPC 0 OK - invoke grpc.health.v1.Health/Check, 15.234ms response time
監視対象がTLSに対応している場合は、-S オプションを付与して接続します。
check_grpc -H grpc.example.com:443 -m grpc.health.v1.Health/Check -D health.pb -
レスポンスタイムを基準にアラートを発報したい場合は、-w と -c でしきい値を秒単位で指定します。次の例では、2秒を超えるとWARNING、5秒を超えるとCRITICALと判定されます。
check_grpc -H localhost:50051 -m grpc.health.v1.Health/Check -D health.pb -w 2 -c 5
# WARNING: Response Time: 2.5s > 2s # CRITICAL: Response Time: 5.234s > 5s<
リクエストにデータを渡したい場合は -a でJSON形式の値を指定し、レスポンス内容を検証したい場合は -s で期待する文字列を指定します。
check_grpc -H localhost:50051 -m com.example.MyService/MyMethod -D fservice.pb \ -a '{"name":"test"}' -s "expected_value"
なお、-s で指定した文字列は、レスポンスをprotobufのtext-format形式で表現した文字列に対する部分一致で検索されます(JSONではありません)。たとえば grpc.health.v1.Health/Check の正常応答は status:SERVING のような形式になるため、-s SERVING のように指定します。レスポンスがどのような文字列になるかわからない場合は、-v を付けて実行すると確認できます。
check_grpc -H localhost:50051 -m grpc.health.v1.Health/Check -D health.pb \ -M "authorization:Bearer token123" -M "x-request-id:abc"
終了コードはNagiosプラグインの規約に従います。
| コード | ステータス | 条件 |
|---|---|---|
| 0 | OK | gRPC呼び出しが成功し、すべてのチェックに合格 |
| 1 | WARNING | レスポンスタイムがWARNINGしきい値を超過 |
| 2 | CRITICAL | レスポンスタイムがCRITICALしきい値を超過、期待する文字列が未検出、gRPCステータスコードの不一致、サービス/メソッドが見つからない、接続失敗 |
| 3 | UNKNOWN | 引数の不正、descriptor setファイルの読み込みエラー、メタデータ形式の不正、TLS証明書プールの取得失敗 |
オプションフラグ
check_grpcが受け付ける主なオプションフラグは次のとおりです。
| フラグ | 短縮形 | デフォルト | 説明 |
|---|---|---|---|
--secure | -S | false | TLS/SSLを使用して接続する |
--timeout | -t | 10 | gRPC呼び出しのタイムアウト(秒) |
--warning | -w | -1(無効) | レスポンスタイムのWARNINGしきい値(秒) |
--critical | -c | -1(無効) | レスポンスタイムのCRITICALしきい値(秒) |
--string | -s | "" | レスポンスに含まれることを期待する文字列 |
--argument | -a | "" | リクエストデータ(JSON形式) |
--expect_status_code | -e | 0(OK) | 期待するgRPCステータスコード |
--metadata | -M | [] | メタデータ(key:value 形式、複数指定可) |
--verbose | -v | false | デバッグ情報を表示する |
制限事項
現バージョンのcheck_grpcには次の制限があります。最初の2つは意図的な設計判断によるもの、残りの2つは現時点で対応していない機能です。
gRPC Server Reflectionに非対応(設計判断)
gRPCには、サーバーが自身のサービス定義を公開する Server Reflection という仕組みがあります。grpcurl などのツールはこれを利用することで、descriptor setファイルなしでメソッドを呼び出せます。
しかし、Server Reflectionを外部からアクセス可能な状態で有効にすると、提供サービスの一覧やメソッドのシグネチャ、メッセージ構造といった情報をネットワーク経由で取得できるようになります。そのため、環境によっては攻撃者にAPIの構造を把握する手掛かりを与える可能性があります。
check_grpcはあえてServer Reflectionに依存せず、事前に protoc で生成した .pb ファイルを利用する設計としました。descriptor setファイルの生成・配置という手間は発生しますが、監視対象のgRPCサーバーには設定変更を加えず、既存のセキュリティポリシーを維持したまま監視を導入できます。
Unary RPCのみサポート(設計判断)
単一リクエスト・単一レスポンスのUnary RPCのみをサポートし、各種ストリーミングRPCには対応していません。接続を維持し続けるストリーミングRPCの性質が、一定間隔のポーリングで状態を確認する監視システムと相性が悪いためです。
プライベートCA・自己署名証明書への対応(未対応)
--secure を指定した場合、OSのシステム証明書プールに登録されたCA証明書のみで検証します。自己署名証明書やプライベートCAを使う環境向けの --cacert 相当のオプションはまだ提供していません。
レスポンスのJSON要素マッチング(未対応)
--string で指定した文字列は、レスポンス全体に対する部分一致で検索されます。レスポンスをJSONとして解析し、特定の要素に対してマッチングする機能は提供していません。
まとめ
check_grpcは、gRPCサービスの死活監視・パフォーマンス監視をNagios監視プラグイン互換の仕組みで実現する監視プラグインです。descriptor setファイルを利用した動的呼び出しにより、監視対象サーバーへの変更なしに導入できます。gRPCを採用しているシステムの運用監視にぜひお役立てください。IssueやPull Requestも歓迎しています。
- GitHub: https://github.com/heartbeatsjp/check_grpc
- ライセンス:BSD 3-Clause
また、ハートビーツでは今回のgRPC監視に限らず、公式やサードパーティーから監視プラグインが提供されていない対象についても独自に監視プラグインを開発して監視を実装した事例が多数あります。「システム固有の監視をしたいが、対象に対応する監視プラグインが見つからないため監視ができない」といったお困りごとがありましたら、お気軽にお問い合わせください。