Cómo Construir un Microservicio gRPC Escalable con Go: Tutorial Paso a Paso
Este tutorial completo está diseñado para desarrolladores backend de nivel intermedio que desean construir un microservicio gRPC escalable usando Go (Golang 1.20+). Cubriremos desde la definición de APIs con Protocol Buffers hasta el despliegue en Kubernetes, incluyendo autenticación JWT, observabilidad con OpenTelemetry, y mejores prácticas de rendimiento y resiliencia.
1. Requisitos Previos e Instalación para Microservicio gRPC en Go
Resumen
Antes de comenzar, aseguraremos la instalación y versiones adecuadas de las herramientas necesarias para desarrollar, probar e implementar un microservicio gRPC en Go con base de datos PostgreSQL y despliegue en contenedores.
Herramientas Requeridas
- Go 1.20+: lenguaje principal.
- Protocol Buffers (protoc) y plugins:
protoc-gen-goprotoc-gen-go-grpc- buf (opcional) para linting y generación.
- Docker y Docker Compose para contenerización y entornos locales.
- Kubernetes CLI (kubectl) con
kindominikubeopcional. - PostgreSQL, local o en Docker.
- golang-migrate para migraciones.
- sqlc para consultas SQL tipadas en Go.
- Git para control de versiones.
Instalación y Comprobación
Ejecuta cada paso en tu terminal para instalar y verificar:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# Go 1.20+ (macOS con Homebrew) brew install go # Verificar versión go version # Protocol Buffers (protoc) (Ejemplo Linux x86_64) wget https://github.com/protocolbuffers/protobuf/releases/download/v22.2/protoc-22.2-linux-x86_64.zip unzip protoc-22.2-linux-x86_64.zip -d $HOME/.local export PATH="$HOME/.local/bin:$PATH" protoc --version # Plugins Go para protoc go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 # Añadir GOPATH/bin al PATH si no está export PATH="$PATH:$(go env GOPATH)/bin" protoc-gen-go --version # Buf (opcional) brew install bufbuild/buf/buf buf --version # Docker y Docker Compose docker --version docker compose version # kubectl (Kubernetes CLI) brew install kubectl kubectl version --client # kind (para cluster local Kubernetes) brew install kind kind --version # PostgreSQL local o Docker: docker run --name pg-demo -e POSTGRES_PASSWORD=secret -p 5432:5432 -d postgres:15 # golang-migrate (CLI) brew install golang-migrate migrate -version # sqlc (Generar código SQL en Go) go install github.com/kyleconroy/sqlc/cmd/sqlc@v1.19.0 sqlc version # Git git --version |
Checklist Requisitos Previos
- [ ] Go 1.20+ instalado y en PATH
- [ ] protoc y plugins correctamente instalados
- [ ] Docker y Docker Compose funcionando
- [ ] kubectl y kind/minikube disponibles
- [ ] PostgreSQL accesible (local o docker)
- [ ] golang-migrate y sqlc instalados y accesibles
- [ ] Git configurado
2. Estructura Recomendada del Proyecto Go para Microservicios gRPC
Resumen
Una estructura de proyecto clara mejora mantenibilidad y escalabilidad en microservicios. Utilizaremos convenciones que ayudan a separar responsabilidades y acelerar nuevos desarrollos.
Estructura de Carpetas
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
my-grpc-service/ ├── cmd/ │ └── service/ │ └── main.go # Punto de entrada del microservicio ├── api/ │ └── proto/ # Archivos .proto de gRPC ├── pkg/ │ ├── api/ # Código generado y wrappers API │ ├── server/ # Registro servidores e interceptores │ ├── service/ # Lógica de negocio │ ├── repo/ # Acceso a datos (repositorios sqlc) │ └── db/ # Pool de conexiones, migraciones ├── migrations/ # Scripts SQL para migraciones ├── deploy/ │ ├── docker/ # Dockerfile, compose │ └── k8s/ # Manifiestos Kubernetes ├── tools/ # Scripts de apoyo (migraciones, generación) ├── configs/ # Archivos de configuración estática └── test/ # Tests unit y de integración |
Propósito de Carpetas
- cmd/service/main.go: bootstrap principal.
- api/proto/: definición de APIs en protobuf.
- pkg/api/: código auto-generado para protos.
- pkg/server/: registro servicios, interceptores.
- pkg/service/: implementación lógica negocio.
- pkg/repo/: consultas generadas con sqlc.
- pkg/db/: gestión conexiones DB y migraciones.
- migrations/: versiones y scripts SQL.
- deploy/: infraestructura y manifiestos.
- tools/: helpers y scripts CLI.
- configs/: configuraciones JSON/YAML.
- test/: pruebas con mocks o integración.
Checklist Estructura
- [ ] Estructura según convención creada
- [ ] Separación clara lógica y infraestructura
- [ ] Archivos .proto ubicados en api/proto
- [ ] Scripts de migración separados
3. Definición de API con Protocol Buffers y gRPC en Go
Resumen
Definiremos servicios gRPC con .proto que luego generaremos en Go. Incluye reglas robustas (timestamp, validaciones).
Ejemplo Completo api/proto/service.proto
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
syntax = "proto3"; package api; option go_package = "github.com/tu_usuario/my-grpc-service/pkg/api"; import "google/protobuf/timestamp.proto"; message User { int64 id = 1; string email = 2; string full_name = 3; google.protobuf.Timestamp created_at = 4; } message RegisterUserRequest { string email = 1; string password = 2; string full_name = 3; } message AuthenticateUserRequest { string email = 1; string password = 2; } message AuthenticateUserResponse { string token = 1; } message GetUserRequest { int64 id = 1; } message GetUserResponse { User user = 1; } service UserService { rpc Register(RegisterUserRequest) returns (User); rpc Authenticate(AuthenticateUserRequest) returns (AuthenticateUserResponse); rpc GetUser(GetUserRequest) returns (GetUserResponse); } message Product { int64 id = 1; string name = 2; string description = 3; double price = 4; } message ListProductsRequest { int32 page = 1; int32 limit = 2; } message ListProductsResponse { repeated Product products = 1; } message GetProductRequest { int64 id = 1; } message GetProductResponse { Product product = 1; } service ProductService { rpc ListProducts(ListProductsRequest) returns (ListProductsResponse); rpc GetProduct(GetProductRequest) returns (GetProductResponse); } message OrderItem { int64 product_id = 1; int32 quantity = 2; } message CreateOrderRequest { int64 user_id = 1; repeated OrderItem items = 2; } message Order { int64 id = 1; int64 user_id = 2; repeated OrderItem items = 3; google.protobuf.Timestamp created_at = 4; } message GetOrderRequest { int64 id = 1; } message GetOrderResponse { Order order = 1; } service OrderService { rpc CreateOrder(CreateOrderRequest) returns (Order); rpc GetOrder(GetOrderRequest) returns (GetOrderResponse); } |
Generar Código Go con protoc
|
1 2 |
protoc --go_out=pkg/api --go-grpc_out=pkg/api api/proto/service.proto |
Si usas buf para generar y linting:
|
1 2 3 |
buf generate buf lint |
Explicación
- Define servicios separados para usuarios, productos y órdenes.
- Usa
google.protobuf.Timestamppara fechas. option go_packageasegura que el código Go generado tenga el paquete correcto.- Separar mensajes request/response por método.
Checklist API gRPC
- [ ] Archivo .proto válido y completo
- [ ] Código Go generado con protoc o buf
- [ ] Servicios User, Product, Order definidos
- [ ] Uso correcto de Timestamp y validaciones
4. Acceso a Datos y Migraciones para Microservicio gRPC con PostgreSQL
Resumen
Configuraremos un contenedor PostgreSQL para desarrollo, escribiremos migraciones SQL para tablas esenciales, y generaremos acceso tipado usando sqlc.
docker-compose.yml para PostgreSQL y Healthchecks
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
version: "3.8" services: postgres: image: postgres:15-alpine restart: always environment: POSTGRES_USER: myuser POSTGRES_PASSWORD: mypassword POSTGRES_DB: mydb ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U myuser"] interval: 10s timeout: 5s retries: 5 volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata: |
Migraciones SQL Ejemplo migrations/0001_init.up.sql
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash TEXT NOT NULL, full_name VARCHAR(255), created_at TIMESTAMPTZ DEFAULT now() NOT NULL ); CREATE TABLE products ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, price NUMERIC(10,2) NOT NULL ); CREATE TABLE orders ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at TIMESTAMPTZ DEFAULT now() NOT NULL ); CREATE TABLE order_items ( order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, product_id BIGINT NOT NULL REFERENCES products(id), quantity INT NOT NULL CHECK (quantity > 0), PRIMARY KEY(order_id, product_id) ); CREATE INDEX idx_orders_user_id ON orders(user_id); |
El archivo
0001_init.down.sqldebe contener las operaciones inversas para rollback.
Ejecutar Migraciones con golang-migrate
|
1 2 |
migrate -path ./migrations -database "postgres://myuser:mypassword@localhost:5432/mydb?sslmode=disable" up |
Consultas SQL y sqlc
Archivo pkg/repo/queries.sql ejemplo:
|
1 2 3 4 5 6 |
-- name: GetUserByID SELECT id, email, full_name, created_at FROM users WHERE id = $1; -- name: CreateUser INSERT INTO users (email, password_hash, full_name) VALUES ($1, $2, $3) RETURNING id, email, full_name, created_at; |
Archivo sqlc.yaml mínimo:
|
1 2 3 4 5 6 7 8 9 10 11 |
version: "1" packages: - name: "repo" path: "pkg/repo" queries: "pkg/repo/queries.sql" schema: "migrations/" engine: postgresql gen: go: true go_package: "github.com/tu_usuario/my-grpc-service/pkg/repo" |
Generar:
|
1 2 |
sqlc generate |
Pooling de Conexiones en Go
Usar pgxpool para manejo eficiente de conexiones:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import ( "context" "github.com/jackc/pgx/v5/pgxpool" "time" ) func NewDBPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) { cfg, err := pgxpool.ParseConfig(dsn) if err != nil { return nil, err } cfg.MaxConns = 20 cfg.HealthCheckPeriod = 1 * time.Minute pool, err := pgxpool.NewWithConfig(ctx, cfg) return pool, err } |
Checklist Acceso Datos y Migraciones
- [ ] PostgreSQL corriendo en Docker con healthcheck
- [ ] Migraciones SQL completas y probadas
- [ ] Golang-migrate instalado y operativo
- [ ] Consultas SQL tipadas con sqlc generadas
- [ ] Pool de conexiones configurado con pgx
5. Implementación del Servidor gRPC en Go: Arranque y Servicios
Resumen
Crearemos el bootstrap del servidor que lee la configuración, inicializa bases de datos, migraciones, logger y registra servicios gRPC con interceptores.
cmd/service/main.go ejemplo simplificado
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
package main import ( "context" "log" "net" "github.com/spf13/viper" "go.uber.org/zap" "google.golang.org/grpc" "github.com/tu_usuario/my-grpc-service/pkg/api" "github.com/tu_usuario/my-grpc-service/pkg/server" "github.com/tu_usuario/my-grpc-service/pkg/db" ) func main() { logger, _ := zap.NewProduction() defer logger.Sync() // Configuración con viper viper.SetDefault("PORT", 50051) port := viper.GetInt("PORT") dsn := viper.GetString("DATABASE_URL") ctx := context.Background() pool, err := db.NewDBPool(ctx, dsn) if err != nil { logger.Fatal("cannot connect to db", zap.Error(err)) } // Ejecutar migraciones (opcional) if err := db.MigrateUp(dsn, "./migrations"); err != nil { logger.Fatal("migrations failed", zap.Error(err)) } lis, err := net.Listen("tcp", ":"+viper.GetString("PORT")) if err != nil { logger.Fatal("fail to listen", zap.Error(err)) } grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( server.LoggingInterceptor(logger), server.AuthInterceptor, ), ) // Registrar servicios api.RegisterUserServiceServer(grpcServer, server.NewUserServiceServer(pool, logger)) api.RegisterOrderServiceServer(grpcServer, server.NewOrderServiceServer(pool, logger)) logger.Info("starting gRPC server", zap.Int("port", port)) if err := grpcServer.Serve(lis); err != nil { logger.Fatal("server error", zap.Error(err)) } } |
Explicación
- Configuración flexible via Viper para puerto y datos BD.
- Logger estructurado con zap.
- Pool pgx para conexiones a la DB.
- Migraciones aplicadas al arrancar.
- Registro de interceptores para logging y auth.
- Registro de servicios implementados.
Checklist Implementación Servidor
- [ ] Bootstrap en main.go listo
- [ ] Logger y configuración implementados
- [ ] Pool de DB inicializado
- [ ] Migraciones ejecutadas al inicio
- [ ] Interceptores aplicados
- [ ] Servicios registrados y disponibles
6. Autenticación JWT en gRPC Go para Microservicio Seguro
Resumen
Implementaremos autenticación JWT usando HS256, creando endpoints para login que retornan token, y un interceptor que valida el token en cada llamada.
Middleware gRPC para Validar JWT
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import ( "context" "strings" "github.com/golang-jwt/jwt/v5" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var jwtSecret = []byte("TU_CLAVE_SECRETA_JWT") func AuthInterceptor( ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (interface{}, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, status.Error(codes.Unauthenticated, "missing context metadata") } authHeaders := md["authorization"] if len(authHeaders) == 0 { return nil, status.Error(codes.Unauthenticated, "missing authorization header") } tokenStr := strings.TrimPrefix(authHeaders[0], "Bearer ") token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, status.Error(codes.Unauthenticated, "unexpected signing method") } return jwtSecret, nil }) if err != nil || !token.Valid { return nil, status.Error(codes.Unauthenticated, "invalid token") } // Añadir claims al contexto si hace falta ctx = context.WithValue(ctx, "user", token.Claims) return handler(ctx, req) } |
Endpoint Login que Devuelve Token
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import ( "time" "github.com/golang-jwt/jwt/v5" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func (s *UserServiceServer) Authenticate(ctx context.Context, req *api.AuthenticateUserRequest) (*api.AuthenticateUserResponse, error) { user, err := s.repo.GetUserByEmail(ctx, req.Email) if err != nil { return nil, status.Error(codes.NotFound, "user not found") } if !checkPasswordHash(req.Password, user.PasswordHash) { return nil, status.Error(codes.PermissionDenied, "invalid credentials") } claims := jwt.MapClaims{ "sub": user.ID, "exp": time.Now().Add(time.Hour * 1).Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtSecret) if err != nil { return nil, status.Error(codes.Internal, "could not generate token") } return &api.AuthenticateUserResponse{Token: tokenString}, nil } |
TLS para gRPC
Generar certificados autofirmados para desarrollo:
|
1 2 |
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost" |
Configurar servidor:
|
1 2 3 4 5 6 7 8 |
import("google.golang.org/grpc/credentials") creds, err := credentials.NewServerTLSFromFile("cert.pem", "key.pem") if err != nil { log.Fatal(err) } s := grpc.NewServer(grpc.Creds(creds)) |
Checklist Autenticación y Seguridad
- [ ] Implementado interceptor JWT
- [ ] Endpoint Authenticate con generación token
- [ ] TLS configurado para cifrado
- [ ] Considere rotación y short-lived tokens
7. Middleware e Interceptores gRPC en Go para Mejorar Microservicios
Resumen
Utilizaremos interceptores para logging, tracing (OpenTelemetry), autenticación y limitación de tasa.
Interceptor de Logging Unary
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func LoggingInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { logger.Info("start unary", zap.String("method", info.FullMethod), ) resp, err := handler(ctx, req) if err != nil { logger.Error("error unary", zap.Error(err)) } else { logger.Info("finish unary") } return resp, err } } |
Interceptor para OpenTelemetry (esquemático)
|
1 2 3 4 5 6 7 8 |
// Usar otel grpc instrumentation: import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" srv := grpc.NewServer( grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()), ) |
Rate Limiting
Puede usar paquetes como golang.org/x/time/rate o middleware Envoy para limitar llamadas.
Checklist Middleware gRPC
- [ ] Interceptor logging implementado
- [ ] Intercepción OpenTelemetry configurada
- [ ] Middleware autenticación activo
- [ ] Limitación de tasa considerada
8. Proxies y Gateways HTTP para gRPC con grpc-gateway y Envoy
Resumen
Expón APIs gRPC como HTTP/JSON para que navegadores y clientes REST consuman.
grpc-gateway Setup básico
Define en el .proto opciones:
|
1 2 3 4 5 6 7 8 |
import "google/api/annotations.proto"; rpc GetUser(GetUserRequest) returns (GetUserResponse) { option (google.api.http) = { get: "/v1/users/{id}" }; } |
Generar código grpc-gateway:
|
1 2 3 4 |
protoc -I api/proto \ --go_out=pkg/api --go-grpc_out=pkg/api \ --grpc-gateway_out=pkg/api api/proto/service.proto |
grpc-gateway server Go mínimo
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import ( "net/http" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" ) func runGateway(ctx context.Context, grpcEndpoint string) error { mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} // TLS en producción err := api.RegisterUserServiceHandlerFromEndpoint(ctx, mux, grpcEndpoint, opts) if err != nil { return err } return http.ListenAndServe(":8080", mux) } |
Envoy proxy yaml mínimo (traducción HTTP->gRPC)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
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 codec_type: AUTO route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: { prefix: "/" } route: { cluster: grpc_service } typed_per_filter_config: envoy.filters.http.grpc_json_transcoder: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor: "/path/to/proto.pb" services: ["api.UserService"] http_filters: - name: envoy.filters.http.grpc_json_transcoder - name: envoy.filters.http.router clusters: - name: grpc_service connect_timeout: 0.25s type: LOGICAL_DNS lb_policy: ROUND_ROBIN http2_protocol_options: {} load_assignment: cluster_name: grpc_service endpoints: - lb_endpoints: - endpoint: address: socket_address: address: host.docker.internal port_value: 50051 |
Probar con curl
|
1 2 |
curl -X GET http://localhost:8080/v1/users/123 |
Checklist HTTP Gateway
- [ ] grpc-gateway configurado y funcionando
- [ ] Envoy configurado para HTTP/JSON
- [ ] Documentación/curl de consumo HTTP
9. Tests Unitarios e Integración para Servicios gRPC en Go
Resumen
Implementamos pruebas unitarias usando table-driven tests y tests de integración con base de datos efímera levantada por testcontainers.
Test Unitario Ejemplo
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func TestGetUser(t *testing.T) { tests := []struct { name string id int64 wantErr bool }{ {"exists", 1, false}, {"not found", 999, true}, } repo := NewMockUserRepo() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := repo.GetUserByID(context.Background(), tt.id) if (err != nil) != tt.wantErr { t.Errorf("error mismatch: got %v, want %v", err, tt.wantErr) } }) } } |
Test Integración Levantando PostgreSQL con testcontainers
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import ( "context" "testing" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) func TestGrpcServerIntegration(t *testing.T) { ctx := context.Background() req := testcontainers.ContainerRequest{ Image: "postgres:15-alpine", Env: map[string]string{ "POSTGRES_USER": "test", "POSTGRES_PASSWORD": "test", "POSTGRES_DB": "testdb", }, WaitingFor: wait.ForListeningPort("5432/tcp"), } pgContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) if err != nil { t.Fatal(err) } defer pgContainer.Terminate(ctx) // Aquí levantar servidor gRPC en memoria apuntando a la BD de test // Crear cliente gRPC y hacer llamadas de prueba } |
Ejecutar tests
|
1 2 |
go test ./... -v |
Checklist Tests
- [ ] Tests unitarios table-driven implementados
- [ ] Tests integración con base efímera ejecutados
- [ ] Usan contexto con cancelación
- [ ] Cobertura y linter aprobados
10. Observabilidad y Métricas gRPC en Go con OpenTelemetry y Prometheus
Resumen
Instrumentamos el servidor para tracing distribuido y métricas, con exposición de endpoint /metrics para Prometheus.
Instrumentación OpenTelemetry
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/trace" ) func setupTracer() (*trace.TracerProvider, error) { exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { return nil, err } tp := trace.NewTracerProvider(trace.WithBatcher(exporter)) otel.SetTracerProvider(tp) return tp, nil } |
Prometheus Metrics /metrics
|
1 2 3 4 5 6 7 8 9 10 |
import ( "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" ) func startMetricsServer() { http.Handle("/metrics", promhttp.Handler()) go http.ListenAndServe(":9090", nil) } |
Healthchecks y Kubernetes Probes
|
1 2 3 4 5 |
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) |
En manifiestos Kubernetes se usan para readiness y liveness probes.
Checklist Observabilidad
- [ ] OpenTelemetry para tracing configurado
- [ ] Prometheus metricas expuestas
- [ ] Health endpoints funcionando
- [ ] Logs estructurados con correlación de request ID
11. Contenerización y Despliegue Kubernetes para Microservicio gRPC
Resumen
Dockerfile multi-stage optimizados, docker-compose para desarrollo, y manifiestos Kubernetes para producción.
Dockerfile multi-stage
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Builder FROM golang:1.20-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o /server ./cmd/service/main.go # Runtime FROM scratch COPY --from=builder /server /server EXPOSE 50051 ENTRYPOINT ["/server"] |
docker-compose.yml para desarrollo
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
version: "3.8" services: app: build: . ports: - "50051:50051" environment: - DATABASE_URL=postgres://myuser:mypassword@postgres:5432/mydb?sslmode=disable depends_on: - postgres postgres: image: postgres:15-alpine environment: POSTGRES_USER: myuser POSTGRES_PASSWORD: mypassword POSTGRES_DB: mydb ports: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata: |
Kubernetes Deployment y Service (deploy/k8s/service-deployment.yaml)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
apiVersion: apps/v1 kind: Deployment metadata: name: grpc-service spec: replicas: 3 selector: matchLabels: app: grpc-service template: metadata: labels: app: grpc-service spec: containers: - name: grpc-service image: tu-registry/grpc-service:latest ports: - containerPort: 50051 readinessProbe: tcpSocket: port: 50051 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: tcpSocket: port: 50051 initialDelaySeconds: 15 periodSeconds: 20 resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "512Mi" --- apiVersion: v1 kind: Service metadata: name: grpc-service spec: selector: app: grpc-service ports: - protocol: TCP port: 50051 targetPort: 50051 type: ClusterIP |
Checklist Contenerización y Despliegue
- [ ] Dockerfile multi-stage confirmado
- [ ] docker-compose para desarrollo configurado
- [ ] Manifiestos Kubernetes con readiness/liveness
- [ ] Recursos y escalabilidad definidos
12. CI/CD y GitOps para Microservicio gRPC en Go
Resumen
Ejemplo de pipeline básico con GitHub Actions para lint, tests, build y despliegue
GitHub Actions Workflow (simplificado)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
name: CI/CD on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.20 - name: Install dependencies run: go mod download - name: Run linters run: golangci-lint run ./... - name: Run tests run: go test -v ./... - name: Build docker image run: docker build -t myrepo/grpc-service:${{ github.sha }} . - name: Push Docker image uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Push image run: docker push myrepo/grpc-service:${{ github.sha }} - name: Deploy to Kubernetes env: KUBECONFIG: ${{ secrets.KUBECONFIG }} run: | kubectl apply -f deploy/k8s/service-deployment.yaml ./tools/migrate.sh migrate/up |
Script migraciones tools/migrate.sh
|
1 2 3 4 5 6 7 8 9 10 11 |
#!/bin/bash set -e if [ "$1" = "migrate/up" ]; then migrate -path ./migrations -database "$DATABASE_URL" up elif [ "$1" = "migrate/down" ]; then migrate -path ./migrations -database "$DATABASE_URL" down else echo "usage: $0 migrate/up | migrate/down" fi |
Checklist CI/CD
- [ ] Pipeline válido y exitoso
- [ ] Linter y tests ejecutados
- [ ] Imagen Docker construida y enviada
- [ ] Despliegue automático a clúster
13. Rendimiento y Prácticas para Escalar Microservicios gRPC en Go
Resumen
Exploramos técnicas para maximizar rendimiento y escalabilidad.
Buenas Prácticas
- Connection pooling con pgx para evitar overhead.
- Batch inserts/updates para reducir round-trips.
- Índices en tablas para acelerar consultas.
- Prepared statements para eficiencia y seguridad.
- Evitar problemas N+1 con joins o consultas planificadas.
- Caching en Redis para read-heavy endpoints.
- Rate limiting para controlar carga.
- Ajustes del GC en Go (GOGC) y optimización CPU.
- Multiplexado y control de concurrencia nativa gRPC.
Benchmarking gRPC con ghz
|
1 2 |
ghz --insecure --proto api/proto/service.proto --call api.UserService.GetUser -d '{"id":1}' localhost:50051 |
Go benchmarks:
|
1 2 3 4 5 6 |
func BenchmarkCreateUser(b *testing.B) { for i := 0; i < b.N; i++ { // código para crear usuario } } |
Ejecutar:
|
1 2 |
go test -bench=. ./... |
Checklist Rendimiento
- [ ] Pooling y batch inserts implementados
- [ ] Índices y prepared statements
- [ ] Benchmarks escritos y ejecutados
- [ ] Uso de cache y limitación
14. Manejo de Errores y Patrones de Resiliencia en gRPC Go
Resumen
Implementar códigos gRPC adecuados y patrones de retry y circuit breakers.
Manejo Errores gRPC
|
1 2 3 4 5 6 7 8 |
import "google.golang.org/grpc/codes" import "google.golang.org/grpc/status" if err == sql.ErrNoRows { return nil, status.Error(codes.NotFound, "resource not found") } return nil, status.Error(codes.Internal, err.Error()) |
Retry con Backoff (cliente o gateway)
Común usar librerías externas o configuraciones Envoy.
Circuit Breakers
Librerías como sony/gobreaker en Go o circuit breakers nativos en proxies.
Checklist Resiliencia
- [ ] Errores mapeados a códigos gRPC
- [ ] Retries implementados en cliente
- [ ] Circuit breakers considerados
15. Checklist Global para Microservicios gRPC en Go en Producción
- [ ] Go 1.20+ y herramientas instaladas
- [ ] Estructura de proyecto organizada
- [ ] Proto con servicios definidos
- [ ] Base de datos con migraciones y sqlc
- [ ] Servidor gRPC con interceptores
- [ ] Autenticación JWT y TLS activo
- [ ] Pruebas unitarias e integración
- [ ] Observabilidad configurada
- [ ] Contenedores optimizados
- [ ] Manifiestos Kubernetes preparados
- [ ] Pipeline CI/CD funcionando
- [ ] Benchmarks y resiliencia implementados
Próximos Pasos Recomendados
- Modelar eventos y servicio de streaming (Kafka, gRPC streaming)
- Uso de buf para versionado y validación de protos
- Implementar políticas de versionado y compatibilidad
- Profundizar en seguridad avanzada (OAuth2, mTLS)
FAQ (Preguntas Frecuentes)
¿Qué versión de Go se recomienda?
Go 1.20 o superior para mejor desempeño y soporte.
¿Se puede usar otro ORM en lugar de sqlc?
Sí, pero sqlc genera código SQL tipado, ideal para performance y control.
¿Cómo manejar actualizaciones de proto?
Usar versionado semántico y herramientas como buf para compatibilidad.
¿Se recomienda TLS obligatorio?
Sí, siempre para entornos productivos.
¿Es necesario Kubernetes para producción?
No siempre, pero facilita escalado y gestión de servicios.
¿Cómo probar gRPC desde un navegador?
Mediante grpc-gateway o proxies como Envoy que transliteran HTTP/JSON.
