Práctica: Desplegar y monitorear un microservicio en un entorno real

Lectura
40 min~10 min lectura
Objetivo de la lección

El objetivo es recorrer el flujo completo, desde el código en tu máquina local hasta un servicio accesible, observable y mantenible.

Puntos de control
  • Concepto clave: El ciclo de vida de un microservicio en producción
  • Cómo funciona en la práctica: Arquitectura del entorno de práctica
  • Código en acción: El Dockerfile y la instrumentación de la app
  • Orquestación y configuración con Docker Compose
Práctica: Desplegar y monitorear un microservicio en un entorno real

Práctica: Desplegar y monitorear un microservicio en un entorno real

En esta lección práctica, consolidaremos todo el conocimiento adquirido en el módulo de Despliegue y Monitoreo. Vamos a tomar un microservicio REST construido con Go y Gorilla/Mux, empaquetarlo en un contenedor Docker, desplegarlo en un entorno controlado (simulando un entorno de producción) y configurar un sistema de monitoreo básico pero completo. El objetivo es recorrer el flujo completo, desde el código en tu máquina local hasta un servicio accesible, observable y mantenible. Asumiremos que tienes un microservicio funcional; nos centraremos en los pasos de containerización, orquestación (con Docker Compose para simplicidad), exposición de métricas y visualización.

Utilizaremos herramientas estándar de la industria: Docker para crear una imagen reproducible, Docker Compose para definir y ejecutar nuestro entorno multi-contenedor (la aplicación y una base de datos), Prometheus para recolectar métricas y Grafana para visualizarlas. Esta pila es común en entornos de microservicios reales y te proporcionará una experiencia invaluable. La lección es densa y práctica; prepárate para escribir código de configuración, comandos de terminal y analizar resultados.

Concepto clave: El ciclo de vida de un microservicio en producción

Imagina que tu microservicio es un motor de coche de carreras de alta precisión. El proceso de despliegue y monitoreo es el equipo de boxes y el panel de control del piloto. Containerización (Docker) es como colocar el motor en un chasis estandarizado y sellado que se puede transportar e instalar en cualquier coche (servidor) sin preocuparse por las diferencias específicas de ese coche. El orquestador (en nuestro caso, Docker Compose simulando conceptos de Kubernetes) es el jefe de equipo que asegura que el motor esté conectado al depósito de combustible (base de datos), al sistema de refrigeración (red) y que se encienda en el orden correcto.

Una vez el motor está en marcha, el monitoreo es el panel de control del piloto. No solo ves la velocidad (peticiones por segundo), sino también las revoluciones del motor (uso de CPU), la temperatura (uso de memoria), la presión del aceite (latencia de la base de datos) y si alguna luz de advertencia se enciende (errores HTTP 5xx). Prometheus actúa como el sistema de adquisición de datos que lee constantemente todos estos sensores. Grafana es la pantalla personalizable donde el piloto (el ingeniero de DevOps o desarrollador) puede ver todos estos datos organizados en gráficos y dashboards para tomar decisiones: ¿necesitamos hacer un pit stop (escalar)? ¿Hay una pieza a punto de fallar (un endpoint con latencia creciente)? Sin este ciclo de vida gestionado, estarías conduciendo a ciegas.

Cómo funciona en la práctica: Arquitectura del entorno de práctica

Vamos a construir un entorno local que refleje una arquitectura típica. Tendremos tres componentes principales ejecutándose en contenedores. Primero, nuestro Microservicio Go, que expondrá su API REST en el puerto 8080 y, crucialmente, expondrá un endpoint de métricas en `/metrics` en formato compatible con Prometheus. Segundo, una base de datos PostgreSQL para que el microservicio persista datos, simulando una dependencia externa. Tercero, el servidor de Prometheus, que se configurará para "raspar" (scrape) el endpoint de métricas de nuestro microservicio cada 15 segundos. Finalmente, Grafana, que se conectará a Prometheus como fuente de datos y nos permitirá crear dashboards.

El flujo de trabajo será: 1) Escribir un `Dockerfile` para construir una imagen optimizada de nuestra aplicación Go. 2) Crear un archivo `docker-compose.yml` que defina los cuatro servicios (app, db, prometheus, grafana), sus configuraciones, volúmenes de datos y dependencias de red. 3) Configurar Prometheus mediante un archivo `prometheus.yml` que indique dónde encontrar las métricas de nuestra app. 4) Iniciar todo el entorno con un solo comando. 5) Generar tráfico contra nuestra API y observar en tiempo real cómo las métricas fluyen hacia Prometheus y se visualizan en Grafana. Este flujo integrado es la esencia del despliegue moderno.

Código en acción: El Dockerfile y la instrumentación de la app

Antes de orquestar, necesitamos una imagen de nuestra aplicación. Este Dockerfile utiliza una construcción multi-etapa para crear un binario pequeño y seguro.


# Dockerfile
# Etapa de construcción
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/api

# Etapa final mínima
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
COPY --from=builder /app/.env . # Si usas variables de entorno
EXPOSE 8080
CMD ["./main"]
    

Ahora, debemos instrumentar nuestro microservicio Go para exponer métricas. Usaremos el paquete `github.com/prometheus/client_golang/prometheus`. Este código muestra cómo integrarlo en un servicio con Gorilla/Mux.


// main.go (fragmento)
package main

import (
    "github.com/gorilla/mux"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "net/http"
    "time"
)

// Definir métricas
var (
    httpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Número total de peticiones HTTP",
        },
        []string{"method", "path", "status_code"},
    )
    httpRequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "Duración de las peticiones HTTP en segundos",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "path"},
    )
)

// Middleware para instrumentar las peticiones
func prometheusMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        rw := &responseWriter{w, http.StatusOK}
        next.ServeHTTP(rw, r)

        route := mux.CurrentRoute(r)
        path, _ := route.GetPathTemplate()

        duration := time.Since(start).Seconds()
        httpRequestDuration.WithLabelValues(r.Method, path).Observe(duration)
        httpRequestsTotal.WithLabelValues(r.Method, path, http.StatusText(rw.statusCode)).Inc()
    })
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

func main() {
    r := mux.NewRouter()

    // Registrar el endpoint de métricas de Prometheus
    r.Path("/metrics").Handler(promhttp.Handler())

    // Aplicar middleware a todas las demás rutas
    apiRouter := r.PathPrefix("/api").Subrouter()
    apiRouter.Use(prometheusMiddleware)

    apiRouter.HandleFunc("/users", getUsers).Methods("GET")
    apiRouter.HandleFunc("/users/{id}", getUser).Methods("GET")
    // ... más rutas

    http.ListenAndServe(":8080", r)
}
    

Orquestación y configuración con Docker Compose

Con la aplicación lista, definimos el entorno completo. El archivo `docker-compose.yml` es el plano de nuestro sistema. Nota cómo definimos redes, volúmenes para persistencia de la base de datos y Grafana, y dependencias de inicio (`depends_on`). La configuración de Prometheus se monta desde un archivo local.


# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    container_name: go-microservice
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_USER=postgres
      - DB_PASSWORD=secretpassword
      - DB_NAME=mydb
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - backend

  postgres:
    image: postgres:15-alpine
    container_name: postgres-db
    environment:
      POSTGRES_PASSWORD: secretpassword
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    ports:
      - "9090:9090"
    depends_on:
      - app
    networks:
      - backend

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
    networks:
      - backend

volumes:
  postgres_data:
  prometheus_data:
  grafana_data:

networks:
  backend:
    driver: bridge
    

El archivo de configuración de Prometheus le dice al servidor dónde encontrar las métricas de nuestra aplicación. Creamos un archivo `prometheus.yml` en el mismo directorio.


# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'go-microservice'
    static_configs:
      - targets: ['app:8080']
        labels:
          service: 'users-api'
          environment: 'practice'
    

Para ejecutar el entorno, navega al directorio del proyecto y ejecuta: docker-compose up --build. Este comando construirá la imagen de la aplicación (si es necesario) y levantará los cuatro contenedores. Verás los logs de todos ellos en la terminal. Una vez iniciados, podrás acceder a: App (http://localhost:8080/api/users), Prometheus (http://localhost:9090), Grafana (http://localhost:3000 - usuario: admin, password: admin).

Errores comunes y cómo evitarlos

Al desplegar y monitorear por primera vez, es fácil tropezar con ciertos problemas. Aquí tienes los más comunes y sus soluciones:

1. La aplicación en el contenedor no puede conectar con la base de datos. Este es el error clásico de configuración de red. En el código de la app, usa el nombre del servicio de Docker Compose (`postgres`) como host, no `localhost`. Asegúrate de que los contenedos estén en la misma red Docker (definida en `docker-compose.yml`) y que las variables de entorno (`DB_HOST`, `DB_PORT`) estén correctamente pasadas al contenedor de la app. Usa `depends_on` con `condition: service_healthy` para asegurar que la DB esté lista.

2. Prometheus no puede raspar (scrape) las métricas del microservicio. Verifica dos cosas: primero, que el endpoint `/metrics` esté efectivamente expuesto y accesible. Segundo, que en `prometheus.yml` el `target` esté correctamente apuntando al nombre del servicio y puerto interno de Docker (`app:8080`), no a `localhost:8080`. Desde la interfaz de Prometheus (http://localhost:9090/targets), puedes ver el estado de tus objetivos (targets). Un estado "DOWN" indica un problema de conexión.

3. Las métricas no aparecen con las etiquetas (labels) esperadas en Grafana. Esto suele ser un problema en la instrumentación del código. Asegúrate de que las etiquetas que usas al registrar métricas (como `method`, `path`, `status_code`) sean consistentes y no estén vacías. El `path` capturado en el middleware debe ser la plantilla de la ruta (ej. `/api/users/{id}`), no la URL completa, para evitar la cardinalidad explosiva. Usa `prometheus.DefBuckets` para el histograma de duración a menos que tengas requisitos específicos.

4. Olvidar la persistencia de datos. Si no defines volúmenes Docker para PostgreSQL, Prometheus y Grafana, todos tus datos (usuarios, métricas históricas, dashboards) se perderán al detener los contenedores. Siempre define volúmenes con nombre (como `postgres_data`) en tu `docker-compose.yml` para los servicios que manejan estado. Para entornos de producción, considera ubicaciones de host bind mounts o soluciones de almacenamiento en la nube.

5. Imágenes de contenedor demasiado grandes o inseguras. Usar `golang:latest` como imagen final resulta en contenedores de ~1GB. Siempre utiliza construcciones multi-etapa (como la mostrada) para terminar con una imagen basada en `alpine` o `scratch` que solo contenga el binario estático y certificados. Esto reduce la superficie de ataque y acelera los despliegues.

Tip Pro: No expongas los puertos de todos los servicios al host en producción. En este ejemplo, exponemos Prometheus (9090) y Grafana (3000) para facilitar el acceso. En un entorno real, estos servicios estarían detrás de un reverse proxy (como Nginx o Traefik) con autenticación y TLS. Considera el uso de redes internas de Docker y un proxy de acceso para la administración.

Checklist de dominio

Antes de considerar esta lección completa, asegúrate de poder verificar cada uno de los siguientes puntos:

  • He escrito un Dockerfile multi-etapa para mi aplicación Go que produce una imagen final pequeña y segura.
  • He instrumentado mi microservicio con el cliente de Prometheus, exponiendo un endpoint `/metrics` con métricas personalizadas (contadores, histogramas) para peticiones HTTP.
  • He creado un archivo docker-compose.yml funcional que define al menos los servicios de aplicación, base de datos, Prometheus y Grafana, con sus respectivas configuraciones de red, volúmenes y variables de entorno.
  • He configurado Prometheus mediante un archivo `prometheus.yml` para raspar automáticamente las métricas de mi microservicio, y puedo ver el target como "UP" en la interfaz de Prometheus.
  • He iniciado todo el entorno con `docker-compose up`, generado tráfico de prueba contra mi API y puedo consultar métricas básicas (como `rate(http_requests_total[1m])`) en la consola de Prometheus.
  • He iniciado sesión en Grafana, añadido Prometheus como fuente de datos y creado al menos un dashboard simple con un gráfico que muestre la tasa de peticiones y la latencia promedio de mi microservicio.
  • Comprendo los errores comunes de configuración de red, scraping de métricas y persistencia, y sé cómo diagnosticarlos y solucionarlos.
  • Puedo explicar la analogía del motor de carreras y cómo cada herramienta (Docker, Prometheus, Grafana) se relaciona con una parte del ciclo de vida de un microservicio en producción.
Falar no WhatsApp
Laboratorio de práctica

Antes de marcar esta lección como completa, escribí una evidencia breve para Go para APIs de Alto Rendimiento: Construcción de Microservicios REST con Gorilla/Mux: un ejemplo, una decisión, una captura, una mini demo o una nota que puedas reutilizar en portfolio.

Reflexión rápida

¿Qué cambiarías en tu forma de trabajar después de aplicar práctica: desplegar y monitorear un microservicio en un entorno real?

De lección a portfolio

Convertí esta lección en una prueba técnica visible.

Una app pequeña publicada, con README y decisiones explicadas, funciona mejor que una lista de tecnologías sueltas.

Paso 1

Creá una demo mínima que use el concepto de la lección.

Paso 2

Escribí un README corto con objetivo, stack, decisión técnica y mejora futura.

Paso 3

Publicá la demo y enlazala desde tu perfil profesional.

Newsletter Cursalo

Recibí rutas y cursos nuevos

Sumate para recibir recursos orientados a empleo y portfolio.

  • Rutas de empleo
  • Cursos prácticos
  • Portfolio y entrevistas

Sin spam. También podés entrar con tu cuenta para guardar progreso. Iniciá sesión

Práctica: Desplegar y monitorear un microservic... | Cursalo