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.