TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

gRPCのデバッグと観測:grpcurl、リフレクション、channelz、そしてメトリクスの罠

ステータス:ドラフト(2026-10-17)、一部実測。 基準バージョン:grpc/grpc-go v1.83.2、grpc/grpc v1.84.0 ドキュメント、kubernetes v1.34.10、go-grpc-prometheus v1.2.0。メトリクスは、2026-10-17 03時台(KST)にPrometheusから読み取ることで取得しました。 grpcurlとchannelzは実行していません(未実測)。必要なのはgrpcurlバイナリ(またはGoでビルドするためのモジュールのダウンロード)と、リフレクション・channelzを有効にしたテストサーバーです。そのため、第3節および第4節のコマンドはドキュメントとコードで確認した使用方法であり、出力結果は掲載していません。

目次

1. イントロダクション:バイナリの手紙を読む4つの方法

JSON APIは curl 1行で人間が読むことができます。gRPCにはバイト列にフィールド名が含まれていないため(第2篇)、契約書がなければ読むことができません。デバッグツールは結局のところ、 契約書をどこから取得するかと 接続状態をどこで確認するかという問題を解決します。

서버 하나를 들여다보는 네 가지 창 gRPC 서버 애플리케이션 서비스 서버 리플렉션 reflection.v1 grpcurl 로 조회 헬스 체크 health.v1 kubelet 프로브 channelz channelz.v1 채널·소켓 통계 메트릭 grpc_server_* Prometheus·OTel 위 셋은 같은 포트의 gRPC 서비스로 붙고, 메트릭은 별도 HTTP 로 긁는다.
図:リフレクション・ヘルス・channelz・メトリクスの位置づけ
ウィンドウ 通知内容 接続方法
サーバーリフレクション サービス・メソッド一覧、メッセージ定義 同一ポート上のgRPCサービス grpc.reflection.v1.ServerReflection
ヘルスチェック SERVINGの状態 grpc.health.v1.Health(第5篇)
channelz チャネル・サブチャネル・ソケットごとの呼び出し数、失敗数、最後の呼び出し時刻、ストリーム・メッセージ数 grpc.channelz.v1.Channelz
メトリクス メソッド・ステータスコードごとの回数、レイテンシ 別個のHTTP(/metrics)またはOTelエクスポート

2. リフレクションとgrpcurl

リフレクションは、「クライアントにスタブがなくても実行時にリクエストを作成できるように」サーバーが自身のproto定義を公開するオプショナルな機能です(server-reflection.md)。代表的なユースケースはコマンドラインのデバッグツールであり、grpcurlはそのようなツールの1つです。

grpc-goでは1行で有効化できます。 reflection.Register はv1と旧v1alphaを 両方とも 登録します。まだv1alphaしか認識できないクライアントが多いためであると、同ファイルの RegisterV1 のコメントで説明されています。

grpcurlで行う作業は概ね以下の通りです(実行せず、未実測)。

grpcurl -plaintext 서버:포트 list                                   # 서비스 목록(리플렉션 필요)
grpcurl -plaintext 서버:포트 describe tt.v1.Scoreboard              # 메서드와 메시지 정의
grpcurl -plaintext -d '{"rallyId":150}' 서버:포트 tt.v1.Scoreboard/Hit   # JSON 으로 요청, JSON 으로 응답
grpcurl -plaintext -import-path . -proto rally.proto 서버:포트 list   # 리플렉션 없이 .proto 를 직접 줌

3. channelz:チャネル内部を覗き見る

channelz(gRFC A14)は、第7篇の階層構造をそのまま統計情報として表示します。最上位のチャネル → サブチャネル → ソケットのツリー構造から、各ノードの状態(READYなど)、開始・成功・失敗した呼び出し数、最後の呼び出し時刻、ソケットのストリーム・メッセージ・キープアライブ数を提供します。

grpc-goでは channelz/service.RegisterChannelzServiceToServer を使用して直接有効化するか、あるいは admin.Register を使用してchannelzと(xDSを使用している場合は)CSDSを同時に有効化します。第6篇の「apiserverのetcd接続247個」のような疑問は、本来このウィンドウを使って解決するのが定石です。kube-apiserverはchannelzを公開していないため、今回は ss で代用しました(kube-apiserverのソースコードからchannelzの登録が見つからないため、未確認)。

ログもウィンドウとして機能します。grpc-goでは、環境変数の GRPC_GO_LOG_SEVERITY_LEVEL(info・warning・error)と GRPC_GO_LOG_VERBOSITY_LEVEL(数値)によって内部ログを有効化できますgrpclog/loggerv2.go:65)。リゾルバーの更新、ピッカーの切り替え、接続失敗などがここに記録されます。

4. メトリクス:2つのファミリー

ファミリー 名前の例 使用者
インターセプター方式(go-grpc-prometheus系) grpc_server_started_total, grpc_server_handled_total{grpc_code}, grpc_client_msg_received_total、オプションの grpc_server_handling_seconds etcd、kube-apiserver(etcdクライアント)、Argo CD、istiod
gRPC独自のOpenTelemetry指標(gRFC A66) grpc.client.attempt.duration, grpc.client.call.duration, grpc.server.call.duration grpc-go stats/opentelemetry(client_metrics.go:291)
個別の名称 kubelet_runtime_operations_total, tempo_request_duration_seconds{route}, otelcol_* kubelet、Tempo、Collector

有用な3つの組み合わせ:

5. 実測:2つの罠

5.1 kube-apiserver 1.34の受信メッセージ指標は「失敗」をカウントしている

apiserverのetcdクライアント指標を確認していたところ、おかしな数値に出くわしました。起動以降、Rangeが1億6千万回以上呼び出されたにもかかわらず、受信メッセージ数が132個しかなかったのです。

sum by (grpc_service, grpc_method, grpc_type) (grpc_client_msg_received_total{job="apiserver"})
sum by (grpc_method, grpc_code) (grpc_client_handled_total{job="apiserver", grpc_type="unary"})
メソッド 受信メッセージ数(msg_received) 送信メッセージ数(msg_sent) OKではない結果の合計
Range 132 162,817,697 CANCELLED 15 + DEADLINE_EXCEEDED 2 + OUT_OF_RANGE 94 + UNAVAILABLE 21 = 132
Txn 39 58,967,709 26 + 4 + 9 = 39
LeaseGrant・Status・Compact 時系列なし(一度もカウントされていない) それぞれ1.7万〜155万 0

受信メッセージ数が エラー数と完全に一致しています。 apiserver 1.34はetcdクライアントに github.com/grpc-ecosystem/go-grpc-prometheus を組み込んでおりetcd3.go:316()、go.modのバージョンはv1.2.0です。同バージョンの単項クライアントインターセプターは以下のようになっています。

func (m *ClientMetrics) UnaryClientInterceptor() func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		monitor := newClientReporter(m, Unary, method)
		monitor.SentMessage()
		err := invoker(ctx, method, req, reply, cc, opts...)
		if err != nil {
			monitor.ReceivedMessage()
		}
		st, _ := status.FromError(err)
		monitor.Handled(st.Code())
		return err
	}
}

出所:grpc-ecosystem/go-grpc-prometheus v1.2.0 client_metrics.go 108〜120行目、Apache-2.0

err != nil のとき「受信した」とカウントしています。条件が反転しているのです。同一リポジトリのmasterでは err == nil に修正されていますが、それ以降リリースは行われておらず、リポジトリはアーカイブ(archived)状態です。ストリーム(Watch)は別の経路(RecvMsg のラップ)でカウントされるため正常です。同じクラスターのetcdサーバー側の grpc_server_msg_received_total は別のライブラリを使用しているため正常でした(Compact 10,268 = 開始 10,268)。

5.2 Argo CDサーバー:1,938個の時系列データのうち13個のみ

count(grpc_server_handled_total{job="argocd-server-metrics"})          → 1,938
count(grpc_server_handled_total{job="argocd-server-metrics"} > 0)      → 13
count(count by (grpc_service,grpc_method) (grpc_server_started_total{job="argocd-server-metrics"}))  → 114

114メソッド×17ステータスコード=1,938となります。インターセプター方式のライブラリには、サーバーに登録されているすべてのメソッドとすべてのコードの組み合わせを 事前に0で初期化する 機能があり(「まだ一度も呼び出されていないメソッド」も比率の計算に含まれるようにするため)、Argo CDサーバーでこれが有効になっていると推測されます(Argo CDのソースコードは未確認)。その結果、このジョブ単体の時系列データが2,280個に達し、 grpc_* の時系列データが2,280個に達し、 小さなクラスター全体 grpc_* の時系列データ2,402個の95% を占めていました。値が存在するのは13個のみです。時系列データの予算が逼迫している場合は、 metric_relabel_configs を使用して値が0の組み合わせを破棄することを検討に値します。

6. 制限事項

7. コントリビューション候補:ドキュメントとコードの食い違い

本シリーズの執筆中に発見したものです。まだどこにも公開しておらず、公開前に最新のmainブランチで再度確認する必要があります。

# リポジトリ 食い違い 根拠 規模
1 grpc/grpc doc/health-checking.md のサービス定義に List RPCが存在しない gRFC A90およびgrpc-protoの health.proto には List·HealthListRequest·HealthListResponse が存在し、grpc-goではv1.83.2に実装されている(health/server.go:73、grpc-go#8155)。ドキュメントはmasterでもCheck・Watchの2つのみ(2026-10-17) ドキュメントPR 1件
2 grpc/grpc doc/statuscodes.md はINVALID_ARGUMENTをライブラリが生成しないコードとして記述している grpc-goサーバーが不正なcontent-typeのリクエストに対してHTTP 415と grpc-status: 3 を送信する(internal/transport/http2_server.go:504)。他の言語の実装も同様であるか要確認 まずIssueで質問する
3 kubernetes 1.36での依存関係の置き換えにより、 grpc_client_msg_received_total(apiserver→etcd、単項)の意味が「失敗数」から「応答数」に変更された 5.1節の実測(132=132)、go-grpc-prometheus v1.2.0 client_metrics.go:113、PR #136227 リリースノートの補足または既知の問題への記録(有用性低、すでに過去のリリース)
4 kubernetes apiserverがリソース(CRD)ごとにetcdクライアントを作成するため、接続数がリソース数に比例する(ノードAでTCP 247) etcd3.go:74 のTODO、Issue #111476 すでに知られている課題です。実測値を課題に付け加える程度です

8. 小規模クラスターとの接続

ティティの一言。 「受け取ったボール 132個」という記録が、実は「落としたボール 132個」でした。スコアボードも時々逆に記載されていることがあるため、数値がおかしい場合は計算式から開いて確認してみてください!

参考

すべての資料は 2026-10-17 に閲覧しました。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます