gRPC Server Deployment and Configuration
gRPC is a high-performance RPC framework from Google that uses Protocol Buffers for serialization and HTTP/2 for transport, making it ideal for microservices communication with strong typing and bidirectional streaming. This guide covers deploying gRPC services in production on Linux, including protobuf compilation, TLS setup, load balancing with Envoy, health checking, and client-side configuration.
Prerequisites
- Ubuntu 20.04+ or CentOS/Rocky 8+
- Go 1.21+ or Python 3.9+ (this guide uses Go examples)
- Root access
- Basic understanding of Protocol Buffers
Installing gRPC and Protocol Buffers
# Install protoc (Protocol Buffer compiler)
PB_VERSION="25.3"
wget https://github.com/protocolbuffers/protobuf/releases/download/v${PB_VERSION}/protoc-${PB_VERSION}-linux-x86_64.zip
sudo unzip protoc-${PB_VERSION}-linux-x86_64.zip -d /usr/local
sudo chmod +x /usr/local/bin/protoc
# Verify
protoc --version
Install Go protoc plugins:
# Install Go
export PATH=$PATH:/usr/local/go/bin
# Install protoc-gen-go and protoc-gen-go-grpc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# Add Go bin to PATH
export PATH=$PATH:$(go env GOPATH)/bin
echo 'export PATH=$PATH:$(go env GOPATH)/bin' >> ~/.bashrc
For Python:
pip install grpcio grpcio-tools
# Compile with: python -m grpc_tools.protoc ...
Defining Protobuf Services
Create your service definitions:
mkdir -p /opt/mygrpc/proto
cat > /opt/mygrpc/proto/user.proto << 'EOF'
syntax = "proto3";
package user;
option go_package = "github.com/yourorg/mygrpc/gen/user;userpb";
import "google/protobuf/timestamp.proto";
service UserService {
// Unary RPC
rpc GetUser (GetUserRequest) returns (User);
rpc CreateUser (CreateUserRequest) returns (User);
// Server-side streaming
rpc ListUsers (ListUsersRequest) returns (stream User);
// Client-side streaming
rpc BulkCreateUsers (stream CreateUserRequest) returns (BulkCreateResponse);
// Bidirectional streaming
rpc SyncUsers (stream UserSyncRequest) returns (stream UserSyncResponse);
}
message User {
string id = 1;
string name = 2;
string email = 3;
google.protobuf.Timestamp created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message BulkCreateResponse {
int32 created = 1;
repeated string errors = 2;
}
message UserSyncRequest {
string user_id = 1;
string action = 2; // "create", "update", "delete"
}
message UserSyncResponse {
bool success = 1;
string message = 2;
}
EOF
Compile the protobuf:
cd /opt/mygrpc
# Create output directory
mkdir -p gen/user
# Generate Go code
protoc \
--go_out=./gen/user \
--go_opt=paths=source_relative \
--go-grpc_out=./gen/user \
--go-grpc_opt=paths=source_relative \
-I ./proto \
proto/user.proto
# Verify generated files
ls gen/user/
# user.pb.go user_grpc.pb.go
Implementing a gRPC Server (Go Example)
cat > /opt/mygrpc/cmd/server/main.go << 'EOF'
package main
import (
"context"
"log"
"net"
"os"
"os/signal"
"syscall"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/reflection"
userpb "github.com/yourorg/mygrpc/gen/user"
)
type userServer struct {
userpb.UnimplementedUserServiceServer
}
func (s *userServer) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*userpb.User, error) {
// Implement your logic here
return &userpb.User{
Id: req.Id,
Name: "John Doe",
Email: "[email protected]",
}, nil
}
func (s *userServer) ListUsers(req *userpb.ListUsersRequest, stream userpb.UserService_ListUsersServer) error {
// Stream users to client
users := []userpb.User{
{Id: "1", Name: "Alice", Email: "[email protected]"},
{Id: "2", Name: "Bob", Email: "[email protected]"},
}
for _, u := range users {
user := u
if err := stream.Send(&user); err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
func main() {
addr := os.Getenv("GRPC_ADDR")
if addr == "" {
addr = ":50051"
}
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
// Server options
opts := []grpc.ServerOption{
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: 15 * time.Second,
MaxConnectionAge: 30 * time.Second,
MaxConnectionAgeGrace: 5 * time.Second,
Time: 5 * time.Second,
Timeout: 1 * time.Second,
}),
grpc.MaxConcurrentStreams(100),
}
grpcServer := grpc.NewServer(opts...)
// Register service
userpb.RegisterUserServiceServer(grpcServer, &userServer{})
// Register health check service
healthServer := health.NewServer()
healthServer.SetServingStatus("user.UserService", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(grpcServer, healthServer)
// Enable reflection (for grpcurl and debugging - disable in production)
if os.Getenv("GRPC_REFLECTION") == "true" {
reflection.Register(grpcServer)
}
// Graceful shutdown
go func() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
log.Println("Shutting down gRPC server...")
grpcServer.GracefulStop()
}()
log.Printf("gRPC server listening on %s", addr)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
EOF
TLS Configuration
Generate certificates for mTLS:
mkdir -p /etc/grpc/certs
# Generate CA key and certificate
openssl genrsa -out /etc/grpc/certs/ca.key 4096
openssl req -new -x509 -days 3650 \
-key /etc/grpc/certs/ca.key \
-out /etc/grpc/certs/ca.crt \
-subj "/CN=gRPC CA/O=MyOrg"
# Generate server key and CSR
openssl genrsa -out /etc/grpc/certs/server.key 2048
openssl req -new \
-key /etc/grpc/certs/server.key \
-out /etc/grpc/certs/server.csr \
-subj "/CN=grpc.example.com/O=MyOrg"
# Sign server certificate with CA
openssl x509 -req -days 365 \
-in /etc/grpc/certs/server.csr \
-CA /etc/grpc/certs/ca.crt \
-CAkey /etc/grpc/certs/ca.key \
-CAcreateserial \
-out /etc/grpc/certs/server.crt
sudo chmod 600 /etc/grpc/certs/*.key
Add TLS to the Go server:
// Load TLS credentials
creds, err := credentials.NewServerTLSFromFile(
"/etc/grpc/certs/server.crt",
"/etc/grpc/certs/server.key",
)
if err != nil {
log.Fatalf("Failed to load TLS: %v", err)
}
opts := []grpc.ServerOption{
grpc.Creds(creds),
// ... other options
}
Streaming RPC Types
Test streaming with grpcurl:
# Install grpcurl
wget https://github.com/fullstorydev/grpcurl/releases/latest/download/grpcurl_linux_x86_64.tar.gz
tar -xzf grpcurl_linux_x86_64.tar.gz
sudo mv grpcurl /usr/local/bin/
# List services (requires reflection enabled)
grpcurl -plaintext localhost:50051 list
# Call unary RPC
grpcurl -plaintext \
-d '{"id": "123"}' \
localhost:50051 \
user.UserService/GetUser
# Call with TLS
grpcurl \
-cacert /etc/grpc/certs/ca.crt \
-d '{"id": "123"}' \
grpc.example.com:50051 \
user.UserService/GetUser
# Server streaming (gets all users)
grpcurl -plaintext \
-d '{"page_size": 10}' \
localhost:50051 \
user.UserService/ListUsers
Load Balancing with Envoy
# Install Envoy
sudo apt install apt-transport-https ca-certificates gnupg
echo "deb [signed-by=/usr/share/keyrings/getenvoy-keyring.gpg] https://deb.dl.getenvoy.io/public/deb/ubuntu $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/getenvoy.list
# Or use Docker
docker pull envoyproxy/envoy:v1.29-latest
cat > /etc/envoy/grpc-proxy.yaml << 'EOF'
static_resources:
listeners:
- name: grpc_listener
address:
socket_address:
address: 0.0.0.0
port_value: 9090 # Envoy listens here
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_proxy
route_config:
name: grpc_route
virtual_hosts:
- name: grpc_backend
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: grpc_service
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: grpc_service
connect_timeout: 0.25s
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: {} # gRPC requires HTTP/2
load_assignment:
cluster_name: grpc_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 50051
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 50052 # Second gRPC instance
EOF
Health Checking
# Check gRPC health with grpc-health-probe
wget https://github.com/grpc-ecosystem/grpc-health-probe/releases/latest/download/grpc_health_probe-linux-amd64
sudo mv grpc_health_probe-linux-amd64 /usr/local/bin/grpc-health-probe
sudo chmod +x /usr/local/bin/grpc-health-probe
# Check health
grpc-health-probe -addr=localhost:50051
# output: status: SERVING
# Check specific service
grpc-health-probe -addr=localhost:50051 -service=user.UserService
# With TLS
grpc-health-probe \
-addr=grpc.example.com:50051 \
-tls \
-tls-ca-cert=/etc/grpc/certs/ca.crt
systemd Service Configuration
sudo tee /etc/systemd/system/grpc-user-service.service << 'EOF'
[Unit]
Description=gRPC User Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=grpcapp
Group=grpcapp
WorkingDirectory=/opt/mygrpc
Environment=GRPC_ADDR=:50051
Environment=GRPC_REFLECTION=false
EnvironmentFile=/etc/grpc/config.env
ExecStart=/opt/mygrpc/bin/server
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/log/grpc
LimitNOFILE=65536
StandardOutput=journal
StandardError=journal
SyslogIdentifier=grpc-user-service
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now grpc-user-service.service
Troubleshooting
"transport: Error while dialing dial tcp: connection refused":
# Check server is listening
ss -tlnp | grep 50051
sudo systemctl status grpc-user-service.service
journalctl -u grpc-user-service.service -n 30
TLS handshake failures:
# Verify certificate validity
openssl x509 -in /etc/grpc/certs/server.crt -noout -text | grep -E "Subject|Not"
# Test TLS connection
openssl s_client -connect grpc.example.com:50051 -CAfile /etc/grpc/certs/ca.crt
# Check client is using same CA
grpcurl -cacert /etc/grpc/certs/ca.crt -v localhost:50051 list
"Unimplemented" error for methods:
# The server must embed UnimplementedXxxServer for forward compatibility
# Check your server struct embeds:
# type myServer struct {
# userpb.UnimplementedUserServiceServer
# }
High latency or timeouts:
# Check keepalive settings match between client and server
# Server MaxConnectionIdle should be > client keepalive interval
# Enable gRPC verbose logging
export GRPC_GO_LOG_VERBOSITY_LEVEL=99
export GRPC_GO_LOG_SEVERITY_LEVEL=info
./server
Conclusion
Deploying gRPC services in production requires careful attention to TLS configuration for secure communication, health checks for orchestration platforms like Kubernetes, and keepalive settings to handle long-lived connections. Envoy provides production-grade load balancing and protocol translation (gRPC-Web for browsers), while the standard gRPC health checking protocol integrates cleanly with both Kubernetes liveness probes and Envoy's health detection. Use grpcurl for testing and grpc-health-probe for automated health verification.


