Despliegue y Configuración de Servidor gRPC en Linux
gRPC es un framework RPC de alto rendimiento desarrollado por Google que utiliza Protocol Buffers como formato de serialización y HTTP/2 para el transporte, ofreciendo comunicación bidireccional, streaming y soporte para múltiples lenguajes. Es la elección ideal para comunicación entre microservicios donde el rendimiento y la eficiencia del protocolo son críticos. Esta guía cubre el despliegue de servicios gRPC en Linux con soporte para TLS, balanceo de carga con Envoy y verificaciones de salud.
Requisitos Previos
- Ubuntu 20.04+ o Rocky Linux 8+
- Go 1.20+ o Node.js 18+
- Acceso root o sudo
- Familiaridad básica con Protocol Buffers
# Instalar el compilador de Protocol Buffers
apt update && apt install -y protobuf-compiler
# Verificar instalación
protoc --version
# Instalar plugins de protoc para Go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
export PATH=$PATH:$(go env GOPATH)/bin
# Para Node.js
npm install -g grpc-tools
Configurar Protocol Buffers
// proto/servicio.proto - Definición del servicio gRPC
syntax = "proto3";
package miservicio.v1;
option go_package = "github.com/miempresa/miservicio/pb";
// Importar el protocolo de health check estándar de gRPC
import "google/protobuf/timestamp.proto";
// Definición del servicio
service UserService {
// RPC unario (solicitud/respuesta simple)
rpc GetUser (GetUserRequest) returns (User);
rpc CreateUser (CreateUserRequest) returns (User);
// Streaming del servidor (el servidor envía múltiples respuestas)
rpc ListUsers (ListUsersRequest) returns (stream User);
// Streaming del cliente (el cliente envía múltiples mensajes)
rpc BulkCreateUsers (stream CreateUserRequest) returns (BulkCreateResponse);
// Streaming bidireccional
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
message GetUserRequest {
string user_id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message User {
string id = 1;
string name = 2;
string email = 3;
google.protobuf.Timestamp created_at = 4;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message BulkCreateResponse {
int32 created_count = 1;
repeated string created_ids = 2;
}
message ChatMessage {
string user_id = 1;
string message = 2;
google.protobuf.Timestamp timestamp = 3;
}
# Generar código Go desde el archivo proto
protoc --proto_path=proto \
--go_out=pb --go_opt=paths=source_relative \
--go-grpc_out=pb --go-grpc_opt=paths=source_relative \
proto/servicio.proto
# Generar código Node.js
grpc_tools_node_protoc \
--proto_path=proto \
--js_out=import_style=commonjs,binary:pb \
--grpc_out=grpc_js:pb \
proto/servicio.proto
Servidor gRPC en Go
// server/main.go - Servidor gRPC en Go con TLS y autenticación
package main
import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"os"
"os/signal"
"syscall"
"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/reflection"
pb "github.com/miempresa/miservicio/pb"
)
// Implementación del servicio
type userServiceServer struct {
pb.UnimplementedUserServiceServer
}
func (s *userServiceServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
// Lógica de negocio aquí
return &pb.User{
Id: req.UserId,
Name: "Usuario de ejemplo",
Email: "[email protected]",
}, nil
}
func (s *userServiceServer) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
// Enviar múltiples usuarios via streaming
for i := 0; i < 10; i++ {
user := &pb.User{
Id: fmt.Sprintf("user-%d", i),
Name: fmt.Sprintf("Usuario %d", i),
Email: fmt.Sprintf("usuario%[email protected]", i),
}
if err := stream.Send(user); err != nil {
return err
}
}
return nil
}
func main() {
port := os.Getenv("GRPC_PORT")
if port == "" {
port = "50051"
}
lis, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Fatalf("Error al escuchar: %v", err)
}
// Configurar TLS
cert, err := tls.LoadX509KeyPair(
"/etc/grpc/certs/server.crt",
"/etc/grpc/certs/server.key",
)
if err != nil {
log.Fatalf("Error al cargar certificados TLS: %v", err)
}
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
})
// Crear servidor gRPC con opciones
grpcServer := grpc.NewServer(
grpc.Creds(creds),
grpc.MaxRecvMsgSize(16*1024*1024), // 16MB
grpc.MaxSendMsgSize(16*1024*1024), // 16MB
)
// Registrar servicios
pb.RegisterUserServiceServer(grpcServer, &userServiceServer{})
// Health check
healthServer := health.NewServer()
healthpb.RegisterHealthServer(grpcServer, healthServer)
healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
healthServer.SetServingStatus("miservicio.v1.UserService", healthpb.HealthCheckResponse_SERVING)
// Reflection para herramientas como grpcurl (solo en desarrollo)
if os.Getenv("GRPC_ENABLE_REFLECTION") == "true" {
reflection.Register(grpcServer)
}
// Apagado gracioso
go func() {
log.Printf("Servidor gRPC escuchando en :%s", port)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Error al servir: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Deteniendo servidor gRPC...")
grpcServer.GracefulStop()
log.Println("Servidor gRPC detenido")
}
Servidor gRPC en Node.js
// server/index.js - Servidor gRPC en Node.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
// Cargar el archivo proto
const PROTO_PATH = path.join(__dirname, '../proto/servicio.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const { miservicio } = grpc.loadPackageDefinition(packageDefinition);
// Implementar los métodos del servicio
const serviceImpl = {
getUser: (call, callback) => {
const { user_id } = call.request;
// Lógica de negocio
callback(null, {
id: user_id,
name: 'Usuario de ejemplo',
email: '[email protected]'
});
},
listUsers: (call) => {
// Streaming del servidor
for (let i = 0; i < 10; i++) {
call.write({
id: `user-${i}`,
name: `Usuario ${i}`,
email: `usuario${i}@ejemplo.com`
});
}
call.end();
},
createUser: (call, callback) => {
const { name, email } = call.request;
// Crear usuario en la base de datos
callback(null, {
id: `new-${Date.now()}`,
name,
email
});
}
};
// Crear servidor con TLS
const serverCredentials = grpc.ServerCredentials.createSsl(
null, // CA cert (null para verificación de cliente no requerida)
[{
cert_chain: require('fs').readFileSync('/etc/grpc/certs/server.crt'),
private_key: require('fs').readFileSync('/etc/grpc/certs/server.key')
}]
);
const server = new grpc.Server();
server.addService(miservicio.v1.UserService.service, serviceImpl);
const PORT = process.env.GRPC_PORT || '50051';
server.bindAsync(`0.0.0.0:${PORT}`, serverCredentials, (error, port) => {
if (error) {
console.error('Error al iniciar servidor gRPC:', error);
process.exit(1);
}
server.start();
console.log(`Servidor gRPC iniciado en puerto ${port}`);
});
Configuración TLS para gRPC
# Generar certificados TLS para gRPC
mkdir -p /etc/grpc/certs
# Generar CA privada
openssl genrsa -out /etc/grpc/certs/ca.key 4096
openssl req -new -x509 -key /etc/grpc/certs/ca.key \
-out /etc/grpc/certs/ca.crt \
-days 3650 \
-subj "/CN=gRPC CA/O=MiEmpresa"
# Generar clave y certificado del servidor
openssl genrsa -out /etc/grpc/certs/server.key 4096
openssl req -new -key /etc/grpc/certs/server.key \
-out /etc/grpc/certs/server.csr \
-subj "/CN=grpc-server/O=MiEmpresa"
# Firmar el certificado del servidor con la CA
openssl x509 -req \
-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 \
-days 365 \
-extfile <(echo "subjectAltName=DNS:localhost,DNS:grpc-server,IP:127.0.0.1")
# Configurar permisos
chmod 600 /etc/grpc/certs/server.key
chmod 644 /etc/grpc/certs/server.crt /etc/grpc/certs/ca.crt
Balanceo de Carga con Envoy
# Instalar Envoy (proxy para gRPC)
apt install apt-transport-https ca-certificates curl gnupg2
curl -sL 'https://deb.dl.getenvoy.io/public/gpg.8115BA8E629CC074.key' | gpg --dearmor -o /usr/share/keyrings/getenvoy-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/getenvoy-keyring.gpg] https://deb.dl.getenvoy.io/public/deb/ubuntu focal main" | tee /etc/apt/sources.list.d/getenvoy.list
apt update && apt install envoy
# Configuración de Envoy para gRPC
cat > /etc/envoy/envoy-grpc.yaml << 'EOF'
static_resources:
listeners:
- name: listener_0
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
stat_prefix: grpc_api
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: grpc_backend
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: grpc_service
timeout: 0s # Sin timeout para streaming
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
http2_protocol_options: {} # Habilitar HTTP/2 para gRPC
load_assignment:
cluster_name: grpc_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: grpc-server-1, port_value: 50051 }
- endpoint:
address:
socket_address: { address: grpc-server-2, port_value: 50051 }
- endpoint:
address:
socket_address: { address: grpc-server-3, port_value: 50051 }
# Configurar TLS hacia los backends
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
EOF
# Crear servicio systemd para Envoy
cat > /etc/systemd/system/envoy-grpc.service << 'EOF'
[Unit]
Description=Envoy Proxy para gRPC
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/envoy -c /etc/envoy/envoy-grpc.yaml
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now envoy-grpc.service
Health Checking
# Instalar grpc_health_probe para verificar salud del servidor
GRPC_HEALTH_PROBE_VERSION=v0.4.19
wget -qO /usr/local/bin/grpc_health_probe \
"https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64"
chmod +x /usr/local/bin/grpc_health_probe
# Verificar salud del servidor gRPC
grpc_health_probe -addr=localhost:50051
grpc_health_probe -addr=localhost:50051 -service=miservicio.v1.UserService
# Para servidores con TLS
grpc_health_probe -addr=localhost:50051 \
-tls \
-tls-ca-cert=/etc/grpc/certs/ca.crt
# Script de health check para monitoreo
cat > /usr/local/bin/grpc-healthcheck.sh << 'EOF'
#!/bin/bash
# Verificación de salud del servidor gRPC
SERVIDOR=${1:-"localhost:50051"}
SERVICIO=${2:-""}
if grpc_health_probe -addr="$SERVIDOR" -service="$SERVICIO" -tls \
-tls-ca-cert=/etc/grpc/certs/ca.crt &>/dev/null; then
echo "OK: gRPC $SERVIDOR $SERVICIO está saludable"
exit 0
else
echo "CRÍTICO: gRPC $SERVIDOR $SERVICIO no responde"
exit 2
fi
EOF
chmod +x /usr/local/bin/grpc-healthcheck.sh
gRPC-Web para Clientes de Navegador
Los navegadores no soportan HTTP/2 nativo para gRPC, pero Nginx puede hacer la traducción:
# Configurar Nginx con gRPC proxy (Nginx 1.13.10+)
cat > /etc/nginx/sites-available/grpc-web << 'EOF'
server {
listen 443 ssl http2;
server_name api.midominio.com;
ssl_certificate /etc/letsencrypt/live/api.midominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.midominio.com/privkey.pem;
# Proxy gRPC directo (para clientes gRPC nativos)
location /miservicio.v1.UserService {
grpc_pass grpcs://localhost:50051;
grpc_ssl_certificate /etc/grpc/certs/ca.crt;
error_page 502 = /error502grpc;
}
location = /error502grpc {
internal;
default_type application/grpc;
add_header grpc-status 14;
add_header content-length 0;
return 204;
}
}
EOF
nginx -t && systemctl reload nginx
Solución de Problemas
Error "transport: Error while dialing: connection refused":
# Verificar que el servidor está escuchando en el puerto correcto
ss -tlnp | grep 50051
# Verificar el servicio systemd
systemctl status grpc-server.service
journalctl -u grpc-server.service -n 50
Error "UNAVAILABLE: Connection closed before server preface received":
# El cliente intenta conectar sin TLS a un servidor que requiere TLS
# O viceversa - verificar configuración de credenciales
grpcurl -plaintext localhost:50051 list # Sin TLS
grpcurl -cacert /etc/grpc/certs/ca.crt localhost:50051 list # Con TLS
Error de certificado TLS "certificate signed by unknown authority":
# El cliente necesita el certificado de la CA
# Copiar /etc/grpc/certs/ca.crt al cliente
# O usar certificados firmados por una CA pública (Let's Encrypt)
openssl verify -CAfile /etc/grpc/certs/ca.crt /etc/grpc/certs/server.crt
Solicitudes de streaming se desconectan:
# Verificar timeout en Nginx/Envoy
grep -i timeout /etc/nginx/sites-available/grpc-web
# Para streaming, los timeouts deben ser 0 o muy altos
# En Nginx: grpc_read_timeout 3600s;
Conclusión
gRPC es una tecnología poderosa para comunicación entre microservicios que ofrece serialización eficiente, streaming bidireccional y generación de código tipo-seguro a partir de definiciones proto. La configuración correcta de TLS para seguridad, Envoy para balanceo de carga a nivel de solicitud individual (L7) y el protocolo de health check estándar de gRPC son los pilares de un despliegue de producción robusto. Para sistemas críticos, monitorea las métricas gRPC con Prometheus exportadas por Envoy para detectar errores y latencia en tiempo real.


