Una plataforma interna de desarrollo (IDP, Internal Developer Platform) es el conjunto de herramientas, plantillas y automatizaciones que un equipo de plataforma ofrece al resto de ingenieros para crear, desplegar y operar servicios sin abrir tickets. Esta guía explica los patrones de diseño que funcionan en la práctica, con ejemplos concretos sobre Kubernetes que puedes probar: un golden path basado en plantillas, el onboarding de equipos mediante GitOps y la forma de medir si la plataforma se usa.

Requisitos previos

Esta guía es conceptual, pero los ejemplos son ejecutables. Para probarlos necesitas:

  • Un clúster de Kubernetes con permisos de administrador, por ejemplo k3s en un VPS de CubePath o kind en tu equipo.
  • kubectl instalado en Ubuntu 24.04 LTS o similar.
  • Opcionalmente, pipx para instalar Cookiecutter y una instancia de Backstage si quieres probar su plantilla.

Qué es y qué no es una plataforma interna

Una IDP no es un producto que se instala: es una capa de abstracción que el equipo de plataforma construye combinando herramientas existentes. Los principios que separan una plataforma útil de un conjunto de scripts son:

  • Autoservicio: un desarrollador obtiene un namespace, un repositorio o una base de datos sin esperar a otra persona.
  • Golden paths: existe una forma recomendada de hacer cada cosa, y es también la más fácil. No es obligatoria, pero salirse de ella tiene un coste visible.
  • Abstracciones finas: se ocultan los detalles repetitivos (etiquetas, cuotas, observabilidad), no Kubernetes entero. Los equipos deben poder mirar debajo cuando lo necesiten.
  • Plataforma como producto: tiene usuarios, un roadmap, documentación y métricas de adopción, igual que cualquier producto.

Capas de una IDP y herramientas habituales

La mayoría de las plataformas se organizan en las mismas capas. No hace falta cubrirlas todas desde el principio:

CapaFunciónHerramientas habituales
PortalCatálogo de servicios, documentación, plantillasBackstage, Port
PlantillasCrear servicios nuevos con la estructura estándarBackstage Software Templates, Cookiecutter
EntregaConstruir y desplegar a partir de GitGitHub Actions, GitLab CI, Argo CD, Flux
ConfiguraciónManifiestos por entorno sin duplicar YAMLKustomize, Helm
InfraestructuraBases de datos, colas, buckets bajo demandaTerraform/OpenTofu, Crossplane
GuardarraílesCuotas, políticas y permisos por equipoResourceQuota, LimitRange, RBAC, Kyverno
ObservabilidadMétricas, logs y trazas incluidos por defectoPrometheus, Grafana, Loki, OpenTelemetry

Un error frecuente es empezar por el portal. Un portal sin golden paths detrás es solo una página de enlaces. Empieza por automatizar lo que más tickets genera hoy (normalmente, crear un servicio nuevo y dar acceso a un entorno) y añade el portal cuando haya algo que mostrar.

Patrón 1: golden path con plantillas de servicio

El golden path para un servicio nuevo produce, en un solo paso, un repositorio con código base, Dockerfile, pipeline, manifiestos y la configuración de observabilidad. La herramienta más sencilla para empezar es Cookiecutter, que genera un proyecto a partir de una plantilla con variables.

En Ubuntu 24.04, instala Cookiecutter con pipx (instalar paquetes con pip fuera de un entorno virtual está bloqueado por PEP 668):

sudo apt update
sudo apt install pipx
pipx ensurepath
pipx install cookiecutter

Crea la plantilla. Las variables se definen en cookiecutter.json y el directorio del proyecto usa una de ellas como nombre:

mkdir -p ~/plantilla-servicio/'{{cookiecutter.nombre_servicio}}'/k8s
nano ~/plantilla-servicio/cookiecutter.json
{
  "nombre_servicio": "mi-servicio",
  "equipo": "equipo-backend",
  "puerto": "8080"
}

Añade un manifiesto que ya incluye las etiquetas y anotaciones que la plataforma necesita (propietario, métricas, versión del golden path):

nano ~/plantilla-servicio/'{{cookiecutter.nombre_servicio}}'/k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ cookiecutter.nombre_servicio }}
  labels:
    app.kubernetes.io/name: {{ cookiecutter.nombre_servicio }}
    app.kubernetes.io/part-of: {{ cookiecutter.equipo }}
    plataforma.example.com/golden-path: v1
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ cookiecutter.nombre_servicio }}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ cookiecutter.nombre_servicio }}
    spec:
      containers:
        - name: app
          image: registry.example.com/{{ cookiecutter.nombre_servicio }}:latest
          ports:
            - name: http
              containerPort: {{ cookiecutter.puerto }}
          readinessProbe:
            httpGet:
              path: /healthz
              port: http
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 256Mi

Genera un servicio a partir de la plantilla sin preguntas interactivas:

cd ~
cookiecutter --no-input ~/plantilla-servicio nombre_servicio=pagos equipo=equipo-pagos
head -n 8 pagos/k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pagos
  labels:
    app.kubernetes.io/name: pagos
    app.kubernetes.io/part-of: equipo-pagos
    plataforma.example.com/golden-path: v1

La etiqueta plataforma.example.com/golden-path es importante: permite medir más adelante cuántos servicios usan la plantilla y en qué versión.

El mismo golden path en Backstage

Cuando la plataforma crece, las plantillas suelen pasar al Scaffolder de Backstage, que añade un formulario web, crea el repositorio y registra el servicio en el catálogo. Una plantilla mínima tiene esta forma:

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: servicio-python
  title: Servicio Python estándar
  description: Crea un servicio con Dockerfile, pipeline de CI y manifiestos de Kubernetes.
spec:
  owner: group:equipo-plataforma
  type: service
  parameters:
    - title: Datos del servicio
      required:
        - nombre
        - propietario
      properties:
        nombre:
          title: Nombre
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
        propietario:
          title: Equipo propietario
          type: string
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group
  steps:
    - id: plantilla
      name: Generar código
      action: fetch:template
      input:
        url: ./skeleton
        values:
          nombre: ${{ parameters.nombre }}
          propietario: ${{ parameters.propietario }}
    - id: publicar
      name: Crear repositorio
      action: publish:github
      input:
        repoUrl: github.com?owner=your_org&repo=${{ parameters.nombre }}
        description: Servicio ${{ parameters.nombre }}
    - id: registrar
      name: Registrar en el catálogo
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['publicar'].output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml
  output:
    links:
      - title: Repositorio
        url: ${{ steps['publicar'].output.remoteUrl }}

El directorio skeleton/ contiene los mismos archivos que la plantilla de Cookiecutter, con la sintaxis ${{ values.nombre }} y un catalog-info.yaml que declara el propietario del servicio.

Patrón 2: onboarding de equipos por GitOps

El segundo ticket más habitual es "necesito un entorno". En lugar de un script que crea namespaces a mano, define cada equipo como un directorio en un repositorio de plataforma y deja que Argo CD o Flux lo aplique. Los guardarraíles (cuotas, límites por defecto y permisos) forman parte de la definición.

Crea una base común para todos los equipos:

mkdir -p ~/plataforma/equipos/base ~/plataforma/equipos/equipo-pagos
nano ~/plataforma/equipos/base/equipo.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: equipo
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: cuota
  namespace: equipo
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.memory: 16Gi
    pods: "50"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: limites-por-defecto
  namespace: equipo
spec:
  limits:
    - type: Container
      defaultRequest:
        cpu: 100m
        memory: 128Mi
      default:
        memory: 256Mi
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: desarrolladores
  namespace: equipo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: edit
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: Group
    name: equipo
nano ~/plataforma/equipos/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - equipo.yaml

Cada equipo es un overlay de pocas líneas que fija el namespace, el grupo con permisos y, si lo necesita, una cuota distinta:

nano ~/plataforma/equipos/equipo-pagos/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../base
namespace: equipo-pagos
patches:
  - target:
      kind: Namespace
    patch: |-
      - op: replace
        path: /metadata/name
        value: equipo-pagos
  - target:
      kind: RoleBinding
    patch: |-
      - op: replace
        path: /subjects/0/name
        value: equipo-pagos
  - target:
      kind: ResourceQuota
    patch: |-
      - op: replace
        path: /spec/hard/requests.cpu
        value: "8"

Aplica el overlay (en producción lo haría Argo CD al detectar el commit) y comprueba el resultado:

kubectl apply -k ~/plataforma/equipos/equipo-pagos
kubectl describe resourcequota cuota -n equipo-pagos
Name:            cuota
Namespace:       equipo-pagos
Resource         Used  Hard
--------         ----  ----
limits.memory    0     16Gi
pods             0     50
requests.cpu     0     8
requests.memory  0     8Gi

Verifica que un miembro del grupo puede desplegar en su namespace pero no en otros:

kubectl auth can-i create deployments -n equipo-pagos --as=ana --as-group=equipo-pagos
kubectl auth can-i create deployments -n kube-system --as=ana --as-group=equipo-pagos
yes
no

Dar de alta un equipo nuevo pasa a ser una pull request de tres líneas que el equipo de plataforma revisa y fusiona. El historial de Git sirve además como registro de auditoría.

Patrón 3: observabilidad incluida por defecto

Si cada equipo tiene que configurar sus métricas, la mitad no lo hará. El golden path debe incluir la integración. Con Prometheus Operator (por ejemplo, instalado con el chart kube-prometheus-stack), añade a la plantilla un ServiceMonitor que recoge las métricas del puerto llamado metrics del Service de cada servicio generado (la plantilla debe incluir ese puerto en el Deployment y en el Service):

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: {{ cookiecutter.nombre_servicio }}
  labels:
    release: kube-prometheus-stack
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ cookiecutter.nombre_servicio }}
  endpoints:
    - port: metrics
      path: /metrics
      interval: 30s

La etiqueta release debe coincidir con el serviceMonitorSelector de tu instalación de Prometheus. Complementa esto con un dashboard de Grafana común que filtre por la etiqueta app.kubernetes.io/name, de modo que cada servicio nuevo tenga paneles de peticiones, errores y latencia desde el primer despliegue.

Patrón 4: medir la plataforma

Tratar la plataforma como producto implica medirla. Tres indicadores sencillos:

  • Adopción del golden path: proporción de workloads con la etiqueta de la plantilla.
  • Tiempo hasta el primer despliegue: desde que se crea el repositorio hasta que el servicio responde en un entorno. Se obtiene de las fechas del sistema de CI/CD.
  • Métricas DORA: frecuencia de despliegue, tiempo de entrega, tasa de fallos en cambios y tiempo de recuperación.

La adopción se puede consultar directamente en el clúster gracias a la etiqueta de la plantilla:

kubectl get deployments -A -l plataforma.example.com/golden-path --no-headers | wc -l
kubectl get deployments -A --no-headers | wc -l

El primer número son los Deployments creados desde la plantilla (en cualquier versión) y el segundo el total. Para ver qué versiones de la plantilla siguen en uso:

kubectl get deployments -A -L plataforma.example.com/golden-path

Si la adopción no sube, pregunta a los equipos antes de añadir funcionalidades: normalmente el golden path es más lento o más rígido de lo que parece desde el equipo de plataforma.

Por dónde empezar

No todas las organizaciones necesitan todas las capas. Una guía orientativa según el tamaño:

SituaciónQué construir primero
Menos de 5 equipos, un clústerPlantilla Cookiecutter, overlays de Kustomize por equipo, Argo CD
5 a 20 equiposLo anterior más Backstage con catálogo y Software Templates, observabilidad por defecto
Más de 20 equipos o varios clústeresInfraestructura bajo demanda con Crossplane o Terraform, políticas con Kyverno, métricas DORA automatizadas

Conclusión

Una plataforma interna eficaz se construye por capas a partir de los problemas reales de los equipos: primero un golden path que crea servicios con todo lo necesario, después el onboarding de equipos por GitOps con cuotas y permisos, y la observabilidad incluida por defecto. Como siguientes pasos, puedes gestionar los overlays de equipos con Argo CD, añadir políticas de admisión con Kyverno para exigir las etiquetas de la plantilla, o desplegar Backstage para dar a todo ello un catálogo y un portal.