- networking
- l2
- topic-pack
- grpc
- http --- Portal | Level: L2: Operations | Topics: gRPC & Protocol Buffers, HTTP Protocol | Domain: Networking
gRPC & Protocol Buffers - Primer¶
Why This Matters¶
REST APIs serialize data as JSON over HTTP/1.1 — human-readable, broadly supported, and slow. In a microservice architecture with hundreds of services making thousands of internal calls per second, JSON parsing and HTTP/1.1 connection overhead become measurable costs. gRPC replaces this with Protocol Buffers (binary serialization, 3-10x smaller than JSON) over HTTP/2 (multiplexed streams, header compression, bidirectional communication). Google built gRPC for exactly this problem: their internal services make billions of RPCs per second.
For DevOps teams, gRPC changes the operational surface. Services communicate over HTTP/2 on a single TCP connection with multiplexed streams. Load balancing requires L7 awareness (L4 load balancers cannot distribute streams within a single connection). Health checking uses a standardized protocol (grpc.health.v1). Debugging requires tools like grpcurl instead of curl. If you run Kubernetes, most of the control plane (etcd, kubelet, API server) uses gRPC internally.
Core Concepts¶
1. Protocol Buffers (Protobuf)¶
Protobuf is a language-neutral, platform-neutral serialization format. You define message types in .proto files and generate code for your target language.
// user.proto
syntax = "proto3";
package userservice;
option go_package = "github.com/example/userservice/pb";
message User {
string id = 1; // field number, NOT value
string email = 2;
string name = 3;
repeated string roles = 4; // repeated = list
google.protobuf.Timestamp created_at = 5;
}
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
User user = 1;
}
service UserService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
rpc ListUsers (ListUsersRequest) returns (stream User); // server streaming
rpc UpdateUsers (stream UpdateUserRequest) returns (UpdateUsersResponse); // client streaming
rpc Chat (stream ChatMessage) returns (stream ChatMessage); // bidirectional
}
Compile protobuf to language-specific code:
# Install protoc compiler
apt install -y protobuf-compiler
# Install Go gRPC plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# Generate Go code
protoc --go_out=. --go-grpc_out=. proto/user.proto
# Generate Python code
pip install grpcio-tools
python -m grpc_tools.protoc -I proto/ --python_out=. --grpc_python_out=. proto/user.proto
Field number rules:
- Field numbers 1-15 use 1 byte in the wire format (use for frequently set fields)
- Field numbers 16-2047 use 2 bytes
- Never reuse a field number after removing a field — use reserved
- reserved 6, 15, 9 to 11; prevents future reuse
2. gRPC Communication Patterns¶
| Pattern | Description | Use Case |
|---|---|---|
| Unary | One request, one response | Simple CRUD |
| Server streaming | One request, stream of responses | Log tailing, large result sets |
| Client streaming | Stream of requests, one response | File upload, batch writes |
| Bidirectional streaming | Both sides stream independently | Chat, real-time sync |
All four patterns multiplex over a single HTTP/2 connection. Each RPC is an independent stream with its own flow control.
3. gRPC Health Checking Protocol¶
gRPC defines a standard health checking protocol (grpc.health.v1) that Kubernetes, load balancers, and service meshes use:
// Standard health check service (built into gRPC libraries)
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
Kubernetes gRPC health check (v1.24+):
apiVersion: v1
kind: Pod
metadata:
name: user-service
spec:
containers:
- name: app
image: example/user-service:v1
ports:
- containerPort: 50051
livenessProbe:
grpc:
port: 50051
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
grpc:
port: 50051
service: "user-service" # optional: check specific service
initialDelaySeconds: 5
periodSeconds: 3
4. Debugging with grpcurl¶
grpcurl is curl for gRPC. It requires the server to support reflection or you to provide proto files.
# Install
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
# List available services (requires server reflection enabled)
grpcurl -plaintext localhost:50051 list
# Describe a service
grpcurl -plaintext localhost:50051 describe userservice.UserService
# Call a method
grpcurl -plaintext -d '{"id": "user-123"}' \
localhost:50051 userservice.UserService/GetUser
# Call with TLS (default — no -plaintext flag)
grpcurl -cacert ca.pem -cert client.pem -key client-key.pem \
api.example.com:443 userservice.UserService/GetUser
# Use proto file instead of reflection
grpcurl -plaintext -import-path ./proto -proto user.proto \
-d '{"id": "user-123"}' localhost:50051 userservice.UserService/GetUser
# Stream output
grpcurl -plaintext -d '{"filter": "active"}' \
localhost:50051 userservice.UserService/ListUsers
Enable server reflection (required for grpcurl discovery):
# Python
from grpc_reflection.v1alpha import reflection
reflection.enable_server_reflection(SERVICE_NAMES, server)
5. Load Balancing¶
gRPC over HTTP/2 multiplexes all RPCs over a single TCP connection. An L4 load balancer (TCP) sees one connection and sends all traffic to one backend. This is the single most common gRPC operational mistake.
Solutions:
| Approach | How it works | When to use |
|---|---|---|
| L7 load balancer | Envoy, Nginx, Traefik inspect HTTP/2 frames | External-facing, sidecar-free |
| Client-side LB | Client resolves multiple backends, round-robins | Internal services, high-performance |
| Service mesh sidecar | Envoy/Linkerd proxy handles LB | Kubernetes with Istio/Linkerd |
| Lookaside LB | External LB service (gRPC-LB protocol) | Large-scale, Google-style |
Envoy gRPC load balancing config:
# envoy.yaml
static_resources:
listeners:
- address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: grpc
route_config:
virtual_hosts:
- name: grpc_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: grpc_backend }
clusters:
- name: grpc_backend
type: STRICT_DNS
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
load_assignment:
cluster_name: grpc_backend
endpoints:
- lb_endpoints:
- endpoint: { address: { socket_address: { address: backend1, port_value: 50051 }}}
- endpoint: { address: { socket_address: { address: backend2, port_value: 50051 }}}
6. Interceptors (Middleware)¶
gRPC interceptors are the equivalent of HTTP middleware — logging, auth, metrics, tracing:
// Go unary server interceptor for logging
func loggingInterceptor(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("method=%s duration=%s error=%v", info.FullMethod, time.Since(start), err)
return resp, err
}
s := grpc.NewServer(grpc.UnaryInterceptor(loggingInterceptor))
7. Deadlines and Timeouts¶
gRPC propagates deadlines across service boundaries. If Service A calls Service B with a 5s deadline, and Service B calls Service C, Service C inherits the remaining deadline automatically.
// Set a 5 second deadline
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "123"})
if err != nil {
st, _ := status.FromError(err)
if st.Code() == codes.DeadlineExceeded {
// Handle timeout
}
}
Always set deadlines. A gRPC call without a deadline can hang forever, leaking goroutines and connections.
Quick Reference¶
# grpcurl — list, describe, call
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext localhost:50051 describe myservice.MyService
grpcurl -plaintext -d '{}' localhost:50051 myservice.MyService/MyMethod
# grpc_health_probe — standalone health check binary
grpc_health_probe -addr=localhost:50051
grpc_health_probe -addr=localhost:50051 -service=my-service
# protoc — compile proto files
protoc --go_out=. --go-grpc_out=. proto/*.proto
# ghz — gRPC load testing
ghz --insecure --call userservice.UserService/GetUser \
-d '{"id":"user-1"}' -n 10000 -c 50 localhost:50051
# Check HTTP/2 connectivity
curl -v --http2 https://api.example.com/
Common gRPC status codes: | Code | Name | Meaning | |------|------|---------| | 0 | OK | Success | | 1 | CANCELLED | Client cancelled | | 3 | INVALID_ARGUMENT | Bad request | | 4 | DEADLINE_EXCEEDED | Timeout | | 5 | NOT_FOUND | Resource missing | | 7 | PERMISSION_DENIED | Auth failed | | 8 | RESOURCE_EXHAUSTED | Rate limited | | 13 | INTERNAL | Server bug | | 14 | UNAVAILABLE | Transient failure (retry) |
Wiki Navigation¶
Prerequisites¶
- Networking Deep Dive (Topic Pack, L1)
Related Content¶
- HTTP Protocol (Topic Pack, L0) — HTTP Protocol
- HTTP Protocol Flashcards (CLI) (flashcard_deck, L1) — HTTP Protocol