ステータス:ドラフト(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つの方法
- 2. リフレクションとgrpcurl
- 3. channelz:チャネル内部を覗き見る
- 4. メトリクス:2つのファミリー
- 5. 実測:2つの罠
- 6. 制限事項
- 7. コントリビューション候補:ドキュメントとコードの食い違い
- 8. 小さなクラスターとのつながり
- 参考
1. イントロダクション:バイナリの手紙を読む4つの方法
JSON APIは curl 1行で人間が読むことができます。gRPCにはバイト列にフィールド名が含まれていないため(第2篇)、契約書がなければ読むことができません。デバッグツールは結局のところ、
契約書をどこから取得するかと 接続状態をどこで確認するかという問題を解決します。
| ウィンドウ | 通知内容 | 接続方法 |
|---|---|---|
| サーバーリフレクション | サービス・メソッド一覧、メッセージ定義 | 同一ポート上の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 를 직접 줌
- リクエストをJSON形式で記述すると、ツールが契約書を参照して第2篇のバイナリ形式に変換して送信します。レスポンスも同様に逆変換して表示します。
- リフレクションは サービスの構造をそのまま公開します。 外部に開放されたポートでは無効化し、内部・管理用のポートでのみ有効化するのが一般的です(第5篇第6節のhubble-relayがヘルス用の4222と本体サービスの4245を分離しているのと同様の発想)。
- ヘルスチェックはリフレクションなしでも動作します。ヘルス用のprotoはgrpcurlに内蔵されていないため、grpcリポジトリの
health.protoを-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つの組み合わせ:
started_total − handled_totalは 現在オープンなストリーム数です(第4篇第6.2節のWatch 242個)。handled_totalをgrpc_codeで割ると第3篇の表になります。ただし、WatchのCANCELLEDのように正常終了がエラーコードとして残るケースを除外する必要があります。- レイテンシヒストグラムは通常デフォルトで無効になっています。小さなクラスターで
grpc_server_handling_secondsを出力していたのはistiodのみであったため、本シリーズのレイテンシ表はapiserverのetcd_request_duration_secondsや、kubelet・Tempo独自のヒストグラムで代用しました。
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)。
- Kubernetesはこの依存関係を
go-grpc-middleware/providers/prometheusに変更し(PR #136227、2026-01-20マージ)、v1.36.0から含まれています(v1.35.0の同ファイルはまだ古いライブラリのままです)。新しいライブラリの単項インターセプターは、エラーの有無に関わらずレスポンスの受信を1回カウントします(interceptors/client.goのUnaryClientInterceptor,providers/prometheus/reporter.goのPostMsgReceive). - そのため、 1.35から1.36へのアップグレード時にこの指標の値が何桁も跳ね上がります(数百から数億へ)。この指標をもとにダッシュボードやアラートを作成している場合は驚くかもしれません。PRの本文には依存関係の更新のみが記載されており、指標の意味の変化については言及されていません。
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. 制限事項
- grpcurl・channelz・リフレクションは実行していません(未実測)。必要なダウンロード:grpcurlのリリースバイナリ、またはgrpc-goのサンプルサーバーをビルドするためのGoモジュール。
- 5.1節の判断は、指標の数値とライブラリのソースコードの突合に基づいています。apiserverが別の経路で
ReceivedMessageを追加で呼び出していないかについては、インターセプターチェーンを最後まで追跡していません。ただし、132=132、39=39が正確に一致しているため、他の経路は存在しないと考えています(推測)。 - 5.2節の事前初期化機能がArgo CDのどの設定で有効になるのかは確認していません(未確認)。
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 |
すでに知られている課題です。実測値を課題に付け加える程度です |
- 1番が最も小さく確実です。ドキュメントの proto ブロックに
Listと両方のメッセージを追加し、サーバー動作の段落(全体リスト、多くのサービスの場合は RESOURCE_EXHAUSTED)を A90 に合わせて要約すればよいです。 - grpc-go と grpc の組織には生成AIポリシーがあります。issue、PR、コメントは人間が直接書き、AIが作成した内容は提出者がすべて理解・検証する必要があります(第7部第7節)。 Kubernetes も PR 本文の告知など、別の規則があります。
8. 小規模クラスターとの接続
- 自作の gRPC サーバーであれば、ヘルスチェックとリフレクションを 管理用ポートに置き、channelz は
admin.Registerで同じ管理ポートにアタッチしておけば、第6部のような調査をssなしで行うことができます。 - ダッシュボードに
grpc_client_msg_received_totalを使用している場合は、Kubernetes 1.36 にアップグレードする前にセクション 5.1 を確認してください。 - 時系列の予算を確認する際は、
count by (job) ({__name__=~"grpc_.*"})から確認します。小規模クラスターでは、1つの job が 95% を占めていました。
ティティの一言。 「受け取ったボール 132個」という記録が、実は「落としたボール 132個」でした。スコアボードも時々逆に記載されていることがあるため、数値がおかしい場合は計算式から開いて確認してみてください!
参考
すべての資料は 2026-10-17 に閲覧しました。
- grpc/grpc v1.84.0:
doc/server-reflection.md,doc/health-checking.md,doc/statuscodes.md; grpc-protogrpc/health/v1/health.proto - gRFC: A14 channelz, A66 OpenTelemetry Metrics, A90 Health Service List Method
- grpc/grpc-go v1.83.2:
reflection/serverreflection.go,channelz/service/service.go,admin/admin.go,grpclog/loggerv2.go,stats/opentelemetry/client_metrics.go,health/server.go,internal/transport/http2_server.go; PR grpc-go#8155 - go-grpc-prometheus: v1.2.0
client_metrics.go, masterclient_metrics.go - kubernetes: v1.34.10
etcd3.go, v1.36.0 同ファイル, PR #136227, issue #111476 - 引用コード: go-grpc-prometheus v1.2.0, Apache License 2.0.