CI/CD funcional, Docker con criterio, Kubernetes cuando tenga sentido y FinOps desde el inicio. Métricas DORA como guía, no como trofeo de presentación.
"En TI, todo queda limitado por la parte más lenta. Los cimientos deben construirse de la mejor manera posible desde el inicio."
Cesar Zanis
01
Métricas DORA: cuatro señales útiles
La frecuencia de despliegue, el tiempo de entrega, la tasa de fallos y el tiempo de recuperación ayudan a diagnosticar la entrega de software. Úsalos para entender cuellos de botella, no como objetivos aislados.
Deployment Frequency
> 1 despliegue al día (Elite)vs< 1 despliegue al mes (Bajo)
Por qué importa
Los despliegues frecuentes permiten lotes pequeños y menos riesgo por cambio. Un despliegue mensual puede acumular riesgo en lugar de reducirlo.
Cómo mejorar
Reduce el tamaño de los lotes. Usa feature flags para separar el despliegue de la publicación de funcionalidades.
Lead Time for Changes
< 1 hora (Elite)vs> 6 meses (Bajo)
Por qué importa
Tiempo entre el commit y la producción. Un plazo largo supone feedback lento y problemas descubiertos tarde. Se trata del ciclo de aprendizaje, no de velocidad por sí misma.
Cómo mejorar
Automatiza las etapas del pipeline. Elimina aprobaciones manuales innecesarias. Prueba los pull requests antes del merge.
Change Failure Rate
< 5% (Elite)vs> 46% (Low)
Por qué importa
Porcentaje de despliegues que provocan un incidente. Una tasa alta puede indicar pruebas insuficientes o cambios demasiado grandes.
Cómo mejorar
Pruebas automatizadas con cobertura útil. Despliegue blue/green. Rollback en menos de cinco minutos.
MTTR (Mean Time to Restore)
< 1 hora (Elite)vs> 6 meses (Bajo)
Por qué importa
Tiempo necesario para restaurar el servicio tras un incidente. La recuperación rápida importa tanto como la prevención de fallos.
Cómo mejorar
Runbooks documentados. Alertas con contexto, más allá de 'CPU alta'. Análisis sin culpabilización después de cada incidente SEV-1.
02
CI/CD: pipelines que funcionan
Pipeline mínimo funcional (GitHub Actions)
YAML
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Caché de dependencias: evita descargas repetidas
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test -- --coverage
# Subir cobertura para dar visibilidad a las pruebas
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
# Compilar y publicar la imagen Docker
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha # Reutilizar capas de Docker entre ejecuciones
cache-to: type=gha,mode=max
Dockerfile multietapa: la diferencia entre 1,2 GB y 120 MB
YAML
# Dockerfile multietapa
# Etapa 1: Build (todo lo necesario para compilar)
FROM node:20-alpine AS builder
WORKDIR /app
# Copiar primero solo package.json para reutilizar la caché
# Esta capa se reutiliza cuando cambia el código pero no las dependencias
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Etapa 2: Runtime (solo lo necesario para ejecutar)
FROM node:20-alpine AS runner
WORKDIR /app
# Usuario sin privilegios root: protección básica
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Copiar solo el resultado de la compilación
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT=3000 NODE_ENV=production
# Health check: expone el estado del contenedor al orquestador
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget -qO- http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]
# Mide el tamaño final de la imagen de tu aplicación
03
FinOps: el costo como criterio de diseño
La factura mensual no debería ser la primera señal de desperdicio. FinOps conecta el gasto con el valor para el negocio, con visibilidad y decisiones durante todo el ciclo de desarrollo.
01
Dimensiona para la demanda P90, no para el pico máximo
Los picos máximos pueden durar poco. El autoescalado gestiona los picos; el dimensionamiento adecuado atiende la demanda habitual. Mide la carga real antes de elegir capacidad.
02
Instancias Spot/Preemptible para cargas sin estado
CI/CD, procesamiento por lotes y workers de colas pueden usar capacidad interrumpible con descuento. Diseña para interrupciones y verifica el ahorro según el proveedor y la carga.
03
Reserva instancias solo para una demanda base comprobada
Reserva la capacidad que sabes que utilizarás. En entornos cambiantes, los compromisos sin uso pueden costar más que el descuento obtenido.
04
Etiquetas de costo desde el inicio
Sin etiquetas por servicio, equipo y entorno, la facturación se vuelve una caja negra. Define la política de etiquetas antes de crear recursos; añadirla después es difícil.
05
Alertas antes de superar el presupuesto
Configura alertas al 50%, 80% y 100% del presupuesto. La alerta temprana te da tiempo para actuar antes de que la situación sea urgente.
Kubernetes: elígelo por una necesidad operativa real
Kubernetes resuelve problemas de orquestación a escala. Antes de adoptarlo, considera el número de servicios, la complejidad operativa y la capacidad del equipo para gestionar la plataforma.
Úsalo cuando: varios servicios tienen ciclos de despliegue independientes, se necesita autoescalado granular y el equipo tiene experiencia práctica en Kubernetes.
Evítalo cuando: un monolito funcional y un equipo pequeño no tienen una necesidad clara de orquestación. Evalúa Docker Compose, Cloud Run o ECS antes de añadir una plataforma que mantener.