Overview
Go is gRPC's home turf: grpc-go is a reference implementation maintained alongside the protocol itself, and the protobuf toolchain generates idiomatic Go stubs. The interesting decisions are around the edges — buf vs raw protoc for schema management, connect-go as the HTTP-compatible rethink, grpc-gateway for REST fallback, and the perennial gotcha of load balancing long-lived HTTP/2 connections in Kubernetes.
Key points
- grpc-go (
google.golang.org/grpc): the reference implementation — HTTP/2 transport, generated client stubs and server interfaces,context.Contextthreading cancellation and deadlines through every call. - Toolchain: classic is
protoc+protoc-gen-go+protoc-gen-go-grpc; the modern default is buf —buf generatewith remote plugins (no local protoc install),buf lintfor style,buf breakingfor breaking-change detection against the previous version, and the Buf Schema Registry for sharing contracts. - Four streaming modes: unary, server-streaming, client-streaming, bidirectional —
streams surface as generated
Send/Recvmethods; a goroutine per direction is the natural consumption pattern. - Deadlines & metadata: deadlines propagate via
context.WithTimeoutand cross the wire (grpc-timeoutheader); metadata (metadata.NewOutgoingContext) carries auth tokens and tracing headers. - Interceptors: the middleware story — unary and stream variants, chained with
grpc.ChainUnaryInterceptor; otelgrpc (now viastats.Handlerrather than interceptors) for tracing, go-grpc-middleware for retry/auth/recovery stock parts. - connect-go (Connect RPC, now a CNCF project): same
.protocontracts, but plain HTTP semantics — handlers mount onnet/http, one endpoint speaks gRPC, gRPC-Web and Connect's own JSON/HTTP protocol, so browsers and curl work without a proxy. - grpc-gateway: generates a reverse proxy from
google.api.httpannotations, exposing a REST/JSON API that transcodes to your gRPC service — the older answer to the same "browsers can't speak gRPC" problem. - Operational endpoints: register the standard health service
(
grpc_health_v1, used by Kubernetes gRPC probes) and reflection (reflection.Register) sogrpcurl/grpcuiwork against the service.
Details
Load balancing in Kubernetes
The classic trap: gRPC holds one HTTP/2 connection, and a ClusterIP Service balances connections, not requests — so all traffic pins to one pod. Options, in rough order of adoption:
| Approach | Mechanism | Notes |
|---|---|---|
| Client-side LB + headless Service | dns:///svc.ns.svc.cluster.local, round_robin config |
No infra needed; DNS re-resolution lags pod churn |
| Service mesh / L7 proxy | Istio/Linkerd/Envoy balance per-request | Zero client changes; operational cost of the mesh |
| Proxyless xDS | grpc-go's xDS support talks to the mesh control plane directly | Newer; mesh features without the sidecar |
MaxConnectionAge on the server keeps connections cycling so client-side balancing
rediscovers pods.
connect-go vs grpc-go vs grpc-gateway
connect-go keeps the protobuf contract but drops grpc-go's bespoke HTTP/2 server for
plain net/http, which buys: standard middleware, HTTP/1.1 compatibility, browser
support without Envoy, and easily curl-able JSON. Interop is real — connect servers
accept gRPC clients and vice versa. grpc-gateway remains the choice when you must publish
a genuine REST API (OpenAPI included) from proto annotations rather than a third
protocol. Staying on grpc-go proper makes sense when you need its full feature surface
(xDS, advanced LB policies) or ecosystem parity with other reference implementations.
Examples
// Server with chained interceptors, health and reflection.
s := grpc.NewServer(
grpc.ChainUnaryInterceptor(recoveryInterceptor, authInterceptor),
grpc.StatsHandler(otelgrpc.NewServerHandler()),
)
pb.RegisterOrderServiceServer(s, &orderServer{})
grpc_health_v1.RegisterHealthServer(s, health.NewServer())
reflection.Register(s)
// Client call with a deadline; client-side round robin over a headless service.
conn, _ := grpc.NewClient("dns:///orders.default.svc.cluster.local:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"round_robin":{}}]}`))
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
resp, err := pb.NewOrderServiceClient(conn).GetOrder(ctx, &pb.GetOrderRequest{Id: id})
buf lint && buf breaking --against '.git#branch=main' && buf generate
Related
- Backend protocols in Go — the map — parent map of the options.
- REST & HTTP APIs in Go — what grpc-gateway/connect-go bridge to.
- Messaging in Go — Kafka, NATS & AMQP — the asynchronous alternative for service integration.
- Microservice architecture — where gRPC earns its keep.
- gRPC in Java — the JVM contrast: grpc-java stubs and framework wrappers vs Go's reference-implementation directness.