golang-grpc について
Go エンジニア向けの gRPC スキルです。サービスの実装とレビューを支援し、正確性・セキュリティ・運用性を扱います。
主な機能
- 01実装・レビューのモード
- 02Proto の構成とコード生成
- 03ステータスコードによるエラー処理
- 04期限設定と接続再利用
- 05インターセプター、ヘルスチェック、終了処理
- 06ストリーミング、セキュリティ、テストの指針
活用シーン
- 01Go gRPC サーバーやクライアントの実装
- 02RPC のエラー処理とセキュリティの監査
- 03bufconn を使ったサービスのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-grpcインストール後、Claude Code で/golang-grpcを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/protoc-reference.mdtesting.md詳細
ペルソナ: Go 分散システムエンジニアです。正確性と運用性を考慮して gRPC サービスを設計します。適切なステータスコード、デッドライン、インターセプター、グレースフルシャットダウンは、正常系と同じくらい重要です。
モード:
- 構築モード — gRPC サーバーまたはクライアントをゼロから実装します。
- レビュー モード — 既存の gRPC コードを監査し、正確性、セキュリティ、運用性の問題を確認します。
依存関係:
- protoc:
brew install protobuf - protoc-gen-go:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest - protoc-gen-go-grpc:
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Go gRPC のベストプラクティス
gRPC は純粋なトランスポート層として扱い、ビジネスロジックとは分離してください。公式の Go 実装は google.golang.org/grpc です。
このスキルは網羅的なものではありません。詳細については、ライブラリのドキュメントやコード例を参照してください。
- Go パッケージのドキュメント、シンボル、バージョン、インポーター、既知の脆弱性については、Context7 よりも
samber/cc-skills-golang@golang-pkg-go-devスキル(godig)を優先してください。 - 独自コード内でのこのライブラリの使用状況(定義、呼び出し箇所、診断情報)を調べるには、
samber/cc-skills-golang@golang-goplsスキル(gopls)を参照してください。 - Context7 は、pkg.go.dev にインデックスされていないドキュメントのフォールバックとして引き続き利用できます。
クイックリファレンス
| 懸念事項 | パッケージ/ツール |
|---|---|
| サービス定義 | .proto ファイルと protoc または buf |
| コード生成 | protoc-gen-go、protoc-gen-go-grpc |
| エラー処理 | codes と google.golang.org/grpc/status |
| 詳細なエラー情報 | google.golang.org/genproto/googleapis/rpc/errdetails |
| インターセプター | grpc.ChainUnaryInterceptor、grpc.ChainStreamInterceptor |
| ミドルウェアエコシステム | github.com/grpc-ecosystem/go-grpc-middleware |
| テスト | google.golang.org/grpc/test/bufconn |
| TLS/mTLS | google.golang.org/grpc/credentials |
| ヘルスチェック | google.golang.org/grpc/health |
Proto ファイルの構成
ドメインごとに、バージョン付きのディレクトリ(proto/user/v1/)で構成してください。必ず Request/Response ラッパーメッセージを使用してください。string のような基本型には、後からフィールドを追加できません。buf generate または protoc で生成します。
サーバーの実装
- ヘルスチェックサービス(
grpc_health_v1)を実装してください。Kubernetes のプローブがレディネスを判定するために必要です。 - 横断的関心事(ロギング、認証、リカバリー)にはインターセプターを使用してください。ビジネスロジックをクリーンに保てます。
- タイムアウト時に
Stop()にフォールバックするGracefulStop()を使用してください。これにより、処理中の RPC を完了させながらハングを防止できます。 - 本番環境ではリフレクションを無効にしてください。API の全体像が公開されます。
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor),
)
pb.RegisterUserServiceServer(srv, svc)
healthpb.RegisterHealthServer(srv, health.NewServer())
go srv.Serve(lis)
// On shutdown signal:
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped:
case <-time.After(15 * time.Second):
srv.Stop()
}
インターセプターパターン
func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("method=%s duration=%s code=%s", info.FullMethod, time.Since(start), status.Code(err))
return resp, err
}
クライアントの実装
- 接続を再利用してください。gRPC は単一の HTTP/2 接続で RPC を多重化するため、リクエストごとに接続すると TCP/TLS ハンドシェイクが無駄になります。
- すべての呼び出しにデッドライン(
context.WithTimeout)を設定してください。設定しないと、低速な上流によってゴルーチンが無期限に停止します。 dns:///スキーム経由で Kubernetes のヘッドレスサービスを使用する場合は、round_robinを使用してください。metadata.NewOutgoingContextを使ってメタデータ(認証トークン、トレース ID)を渡してください。
conn, err := grpc.NewClient("dns:///user-service:50051",
grpc.WithTransportCredentials(creds),
grpc.WithDefaultServiceConfig(`{
"loadBalancingPolicy": "round_robin",
"methodConfig": [{
"name": [{"service": ""}],
"timeout": "5s",
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}`),
)
client := pb.NewUserServiceClient(conn)
エラー処理
gRPC エラーは、必ず具体的なコードを指定して status.Error で返してください。生の error は codes.Unknown となり、クライアントが対処方法を判断できません。クライアントはコードに基づいて、再試行するか、即座に失敗させるか、機能を縮退させるかを判断します。
| コード | 使用する場面 |
|---|---|
InvalidArgument | 入力形式が不正(フィールドの不足、形式の不正) |
NotFound | エンティティが存在しない |
AlreadyExists | 作成に失敗したが、エンティティは存在する |
PermissionDenied | 呼び出し元に権限がない |
Unauthenticated | トークンがない、または無効 |
FailedPrecondition | システムが必要な状態ではない |
ResourceExhausted | レート制限またはクォータを超過 |
Unavailable | 一時的な問題で、安全に再試行できる |
Internal | 予期しないバグ |
DeadlineExceeded | タイムアウト |
// ✗ Bad — caller gets codes.Unknown, can't decide whether to retry
return nil, fmt.Errorf("user not found")
// ✓ Good — specific code lets clients act appropriately
if errors.Is(err, ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "user %q not found", req.UserId)
}
return nil, status.Errorf(codes.Internal, "lookup failed: %v", err)
フィールド単位の検証エラーには、status.WithDetails を使って errdetails.BadRequest を付加してください。
ストリーミング
| パターン | ユースケース |
|---|---|
| サーバーストリーミング | サーバーが一連のデータを送信する(ログの追跡、結果セット) |
| クライアントストリーミング | クライアントが一連のデータを送信し、サーバーが一度だけ応答する(ファイルのアップロード、バッチ処理) |
| 双方向 | 双方が独立して送信する(チャット、リアルタイム同期) |
大きな単一メッセージよりもストリーミングを優先してください。メッセージごとのサイズ制限を回避し、メモリ負荷を軽減できます。
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
for _, u := range users {
if err := stream.Send(u); err != nil {
return err
}
}
return nil
}
テスト
bufconn を使うと、ネットワークのオーバーヘッドなしに、gRPC スタック全体(シリアライズ、インターセプター、メタデータ)を通すインメモリー接続をテストできます。エラーシナリオで期待される gRPC ステータスコードが返ることを必ずテストしてください。
セキュリティ
- 本番環境では TLS を必ず有効にしてください。認証情報はメタデータ内を転送されます
- サービス間認証には mTLS を使用するか、サービスメッシュ(Istio、Linkerd)に委任してください
- ユーザー認証には
credentials.PerRPCCredentialsを実装し、認証インターセプターでトークンを検証してください - API の検出を防ぐため、本番環境ではリフレクションを無効にすることが望ましいです
パフォーマンス
| 設定 | 目的 | 一般的な値 |
|---|---|---|
keepalive.ServerParameters.Time | アイドル接続への ping 間隔 | 30s |
keepalive.ServerParameters.Timeout | ping の応答タイムアウト | 10s |
grpc.MaxRecvMsgSize | 大きなペイロード向けにデフォルトの 4 MB を上書き | 16 MB |
| 接続プール | 高負荷のストリーミング向けに複数の接続を使用 | 4 connections |
ほとんどのサービスでは接続プールは不要です。複雑さを増す前にプロファイリングしてください。
よくある間違い
| 間違い | 修正方法 |
|---|---|
生の error を返す | codes.Unknown となり、クライアントは再試行すべきか判断できません。具体的なコードを指定して status.Errorf を使用してください |
| クライアント呼び出しにデッドラインがない | 上流の処理が遅いと、いつまでも応答を待ち続けます。必ず context.WithTimeout を使用してください |
| リクエストごとに新しい接続を作成する | TCP/TLS ハンドシェイクが無駄になります。接続は一度作成して再利用してください。HTTP/2 は RPC を多重化します |
| 本番環境でリフレクションを有効にする | 攻撃者がすべてのメソッドを列挙できてしまいます。dev/staging でのみ有効にしてください |
すべてのエラーに codes.Internal を使う | コードが誤っていると、クライアントの再試行ロジックが機能しません。Unavailable は再試行を促しますが、InvalidArgument は促しません |
| RPC の引数にラッパーのない型を使う | string にフィールドを追加することはできません。ラッパーメッセージを使うと、後方互換性を保ちながら進化させられます |
| ヘルスチェックサービスがない | Kubernetes が準備状態を判断できず、デプロイ中に Pod を終了させます |
| コンテキストのキャンセルを無視する | 呼び出し元が処理を諦めた後も、長時間の処理が続きます。ctx.Err() を確認してください |
相互参照
- → デッドラインとキャンセルのパターンについては、
samber/cc-skills-golang@golang-contextスキルを参照してください - → gRPC エラーから Go エラーへのマッピングについては、
samber/cc-skills-golang@golang-error-handlingスキルを参照してください - → gRPC インターセプター(ロギング、トレース、メトリクス)については、
samber/cc-skills-golang@golang-observabilityスキルを参照してください - → bufconn を使った gRPC のテストについては、
samber/cc-skills-golang@golang-testingスキルを参照してください