Patrones de Diseño para Plataformas Internas de Desarrollador

Una Plataforma Interna de Desarrollador (IDP, por sus siglas en inglés) proporciona infraestructura de autoservicio y caminos dorados que permiten a los equipos de desarrollo desplegar y operar servicios de forma autónoma. Esta guía cubre los patrones arquitectónicos clave, las APIs de plataforma, las plantillas de servicio y cómo mejorar la experiencia del desarrollador en entornos DevOps modernos.

Requisitos Previos

  • Equipo de Platform Engineering dedicado (mínimo 2-3 personas)
  • Clúster Kubernetes como base de la plataforma
  • Herramientas de IaC: Terraform, Pulumi o Crossplane
  • Portal de desarrollador (Backstage recomendado)
  • Sistema de CI/CD establecido (GitLab CI, GitHub Actions, Argo CD)

Principios de una IDP

Una plataforma interna efectiva se basa en cuatro pilares:

  1. Autoservicio: los desarrolladores pueden obtener lo que necesitan sin esperar aprobaciones
  2. Abstracciones adecuadas: ocultar la complejidad de infraestructura sin perder control
  3. Caminos dorados: la forma recomendada de hacer las cosas es también la más fácil
  4. Producto, no proyecto: tratar la plataforma como un producto con usuarios reales
# Evaluar la madurez de tu plataforma actual
# Nivel 1: Manual - los desarrolladores abren tickets para todo
# Nivel 2: Scripts - algunos procesos automatizados pero frágiles
# Nivel 3: Self-service básico - portal o CLI para operaciones comunes
# Nivel 4: Self-service completo - desarrolladores autónomos con guardrails
# Nivel 5: Plataforma cognitiva - optimización continua basada en métricas

# Crear una CLI simple de plataforma con bash
cat << 'EOF' > /usr/local/bin/plat
#!/bin/bash
# CLI de plataforma - punto de entrada unificado para desarrolladores

COMANDO=${1}

case $COMANDO in
  "nuevo-servicio")
    curl -s https://portal.empresa.com/api/crear-servicio \
      -H "Authorization: Bearer ${PLAT_TOKEN}" \
      -d "{\"nombre\": \"${2}\", \"tipo\": \"${3:-microservicio}\"}"
    ;;
  "desplegar")
    kubectl apply -k "apps/${2}/overlays/${3:-staging}"
    ;;
  "logs")
    stern --namespace "${3:-default}" "${2}" --tail 100
    ;;
  *)
    echo "Uso: plat [nuevo-servicio|desplegar|logs] [argumentos]"
    ;;
esac
EOF
chmod +x /usr/local/bin/plat

El Camino Dorado

El "Golden Path" es la ruta predefinida y validada para crear, desplegar y operar servicios:

# Estructura del camino dorado para un nuevo microservicio
# Paso 1: Crear desde plantilla (vía Backstage o CLI)
plat nuevo-servicio mi-nuevo-servicio api-rest

# Esto genera automáticamente:
# ├── Repositorio Git con estructura estándar
# ├── Pipeline de CI/CD configurado
# ├── Manifiestos Kubernetes base
# ├── Configuración de observabilidad
# ├── Documentación inicial (TechDocs)
# └── Entrada en el catálogo de Backstage

Definir el camino dorado como código con Cookiecutter:

# Instalar cookiecutter
pip3 install cookiecutter

# Crear una plantilla de proyecto estándar
mkdir -p plantilla-microservicio/\{\{cookiecutter.nombre_servicio\}\}

cat << 'EOF' > plantilla-microservicio/cookiecutter.json
{
  "nombre_servicio": "mi-servicio",
  "lenguaje": ["go", "python", "nodejs", "java"],
  "tipo": ["api-rest", "worker", "grpc"],
  "equipo": "equipo-backend",
  "repo_base": "github.com/mi-org"
}
EOF

# Crear la estructura de la plantilla
mkdir -p "plantilla-microservicio/{{cookiecutter.nombre_servicio}}/k8s"
mkdir -p "plantilla-microservicio/{{cookiecutter.nombre_servicio}}/.github/workflows"

cat << 'EOF' > "plantilla-microservicio/{{cookiecutter.nombre_servicio}}/k8s/deployment.yaml"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ cookiecutter.nombre_servicio }}
  labels:
    app: {{ cookiecutter.nombre_servicio }}
    equipo: {{ cookiecutter.equipo }}
spec:
  replicas: 2
  selector:
    matchLabels:
      app: {{ cookiecutter.nombre_servicio }}
  template:
    metadata:
      labels:
        app: {{ cookiecutter.nombre_servicio }}
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
    spec:
      containers:
        - name: {{ cookiecutter.nombre_servicio }}
          image: registry.empresa.com/{{ cookiecutter.nombre_servicio }}:latest
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 9090
              name: metrics
EOF

# Usar la plantilla para crear un nuevo servicio
cookiecutter plantilla-microservicio/

Plantillas de Servicio

Las plantillas estandarizan las decisiones de arquitectura:

# Plantilla Backstage para microservicio Go
# templates/microservicio-go/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: microservicio-go
  title: Microservicio Go (Estándar Empresa)
  description: |
    Crea un microservicio Go con:
    - API REST con Gin
    - Métricas Prometheus
    - Health checks
    - Logging estructurado (zap)
    - Tests unitarios
    - Pipeline CI/CD configurado
spec:
  owner: equipo-plataforma
  type: service
  parameters:
    - title: Configuración del servicio
      properties:
        nombre:
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
        descripcion:
          type: string
        equipo:
          type: string
          ui:field: OwnerPicker
        sla:
          type: string
          enum: ['99.9', '99.5', '99.0']
          default: '99.5'
          description: SLA objetivo del servicio
  steps:
    - id: crear-repo
      action: publish:github
      input:
        repoUrl: github.com?repo=${{ parameters.nombre }}&owner=mi-org
    - id: registrar-catalogo
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['crear-repo'].output.repoContentsUrl }}

APIs de Plataforma

Las APIs de plataforma abstraen la complejidad de infraestructura:

# API REST de plataforma para autoservicio
# Implementación básica con FastAPI

cat << 'EOF' > platform_api.py
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
import subprocess
import yaml

app = FastAPI(title="Platform API", version="1.0.0")

class SolicitudServicio(BaseModel):
    nombre: str
    tipo: str = "api-rest"
    equipo: str
    namespace: str = "default"

@app.post("/servicios")
async def crear_servicio(solicitud: SolicitudServicio):
    """Crear un nuevo servicio con la configuración estándar"""
    # Verificar que el nombre es válido
    if not solicitud.nombre.replace('-', '').isalnum():
        raise HTTPException(status_code=400, detail="Nombre de servicio inválido")
    
    # Crear namespace si no existe
    subprocess.run(
        ['kubectl', 'create', 'namespace', solicitud.namespace, '--dry-run=client', '-o', 'yaml'],
        check=True
    )
    
    # Generar y aplicar los manifiestos estándar
    manifiestos = generar_manifiestos(solicitud)
    
    return {
        "estado": "creado",
        "servicio": solicitud.nombre,
        "namespace": solicitud.namespace,
        "url_catalogo": f"https://portal.empresa.com/catalog/{solicitud.nombre}"
    }

def generar_manifiestos(solicitud: SolicitudServicio) -> dict:
    """Generar manifiestos Kubernetes estándar para el servicio"""
    return {
        "deployment": {
            "apiVersion": "apps/v1",
            "kind": "Deployment",
            "metadata": {"name": solicitud.nombre, "namespace": solicitud.namespace},
            "spec": {"replicas": 2}
        }
    }
EOF

# Ejecutar la API de plataforma
uvicorn platform_api:app --host 0.0.0.0 --port 8000

Autoservicio de Infraestructura

# Namespace Request - los equipos piden namespaces via GitOps
# teams/equipo-backend/namespace-request.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: namespace-request-equipo-backend
  namespace: platform-system
  labels:
    platform.empresa.com/tipo: namespace-request
data:
  equipo: equipo-backend
  proyecto: sistema-pagos
  entorno: produccion
  cuota-cpu: "10"
  cuota-memoria: "20Gi"
  cuota-pods: "50"
# Operador de plataforma que procesa las solicitudes
# (simplificado con un script, en producción usaría un operador Kubernetes real)

cat << 'EOF' > /usr/local/bin/platform-operator.sh
#!/bin/bash
# Operador de plataforma - procesa solicitudes de namespace

# Obtener todas las solicitudes pendientes
kubectl get configmaps -n platform-system \
  -l platform.empresa.com/tipo=namespace-request \
  -o json | jq -r '.items[].metadata.name' | while read solicitud; do
  
  # Extraer datos de la solicitud
  EQUIPO=$(kubectl get configmap $solicitud -n platform-system -o jsonpath='{.data.equipo}')
  CUOTA_CPU=$(kubectl get configmap $solicitud -n platform-system -o jsonpath='{.data.cuota-cpu}')
  
  # Crear el namespace con las políticas correctas
  kubectl create namespace "$EQUIPO" --dry-run=client -o yaml | kubectl apply -f -
  
  # Aplicar cuotas de recursos
  kubectl apply -f - << YAML
apiVersion: v1
kind: ResourceQuota
metadata:
  name: cuota-${EQUIPO}
  namespace: ${EQUIPO}
spec:
  hard:
    requests.cpu: "${CUOTA_CPU}"
    pods: "50"
YAML

  echo "Namespace ${EQUIPO} configurado correctamente"
done
EOF
chmod +x /usr/local/bin/platform-operator.sh

Observabilidad Integrada

Toda la observabilidad debe estar incluida por defecto en el camino dorado:

# Plantilla de ServiceMonitor para Prometheus (parte del camino dorado)
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: {{ nombre_servicio }}
  namespace: monitoring
  labels:
    release: prometheus-stack
spec:
  selector:
    matchLabels:
      app: {{ nombre_servicio }}
  endpoints:
    - port: metrics
      interval: 30s
      path: /metrics
# Dashboard de Grafana estándar para todos los servicios
# Incluido automáticamente vía plantilla
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDashboard
metadata:
  name: servicio-estandar
spec:
  folder: "Servicios"
  json: |
    {
      "title": "Servicio - Métricas Estándar",
      "panels": [
        {"title": "Tasa de Peticiones", "type": "graph"},
        {"title": "Latencia P95", "type": "graph"},
        {"title": "Tasa de Errores", "type": "graph"},
        {"title": "Uso de CPU", "type": "graph"}
      ]
    }

Adopción y Cultura de Plataforma

# Medir la adopción de la plataforma
cat << 'EOF' > medir_adopcion.sh
#!/bin/bash
# Script para medir el éxito de la plataforma

echo "=== Métricas de Adopción de Plataforma ==="

# Servicios usando la plantilla estándar
SERVICIOS_ESTANDAR=$(kubectl get deployments --all-namespaces \
  -l gestionado-por=plataforma --no-headers | wc -l)
TOTAL_SERVICIOS=$(kubectl get deployments --all-namespaces --no-headers | wc -l)

echo "Servicios usando plantilla estándar: ${SERVICIOS_ESTANDAR}/${TOTAL_SERVICIOS}"
echo "Tasa de adopción: $(echo "scale=1; ${SERVICIOS_ESTANDAR}*100/${TOTAL_SERVICIOS}" | bc)%"

# Tiempo medio de despliegue (DORA metrics)
# Requiere integración con el sistema de CI/CD

echo ""
echo "=== Métricas DORA ==="
echo "Para calcular métricas DORA completas, integrar con el sistema CI/CD"
EOF
chmod +x medir_adopcion.sh

Solución de Problemas

# Diagnóstico de problemas comunes en IDPs

# Los desarrolladores no pueden desplegar (problemas de permisos)
kubectl auth can-i create deployments --as=system:serviceaccount:equipo-backend:default -n equipo-backend

# Verificar que las cuotas no están agotadas
kubectl describe resourcequota -n equipo-backend

# Ver eventos del namespace del equipo
kubectl get events -n equipo-backend --sort-by='.lastTimestamp' | tail -20

# Depurar pipeline de CI/CD
# Ver los últimos runs de GitHub Actions
gh run list --limit 5

# Verificar la sincronización de Argo CD
argocd app sync mi-servicio --dry-run
argocd app diff mi-servicio

Conclusión

Una Plataforma Interna de Desarrollador bien diseñada multiplica la productividad de los equipos de ingeniería al eliminar la fricción entre escribir código y verlo en producción. El éxito depende de tratar la plataforma como un producto real con sus propios usuarios, SLAs y ciclo de retroalimentación constante. Los caminos dorados y las plantillas de servicio son la inversión más rentable: hacen que hacer las cosas bien sea también la opción más sencilla.