DevOps y herramientas · 9 min de lectura

Plataforma interna de despliegue con Backstage, Argo CD y Kubernetes

Backstage, Argo CD y Kubernetes pueden convertir el despliegue en autoservicio. Guía práctica con plantillas, GitOps, promoción entre entornos y recuperación ante fallos.

backstage

¿Qué es una plataforma interna de desarrollo (IDP) y por qué la necesitas?

Una plataforma interna de desarrollo (IDP) es la capa que convierte tareas repetitivas de infraestructura en flujos de autoservicio. En lugar de que cada equipo pida a DevOps crear un repositorio, un pipeline y un entorno, los equipos lo solicitan desde un portal y reciben una estructura estándar ya conectada a Kubernetes.

Backstage es el portal: aporta el catálogo de servicios, las plantillas del Scaffolder y la documentación con TechDocs. Argo CD y Kubernetes ejecutan y sincronizan lo que la plataforma declara.

El problema que resuelve es concreto. Cuando varios equipos configuran por su cuenta repositorios, pipelines, entornos y despliegues, aparecen tres síntomas:

  • Dependencia del equipo DevOps para lo repetitivo, que deja de trabajar en lo que sí requiere criterio experto.
  • Diferencias de seguridad y operación: algunos servicios con pruebas, otros sin; unos con límites de recursos y otros no.
  • Despliegues difíciles de auditar: no queda claro qué versión corre en cada entorno ni quién la aprobó.

Una IDP no es "una herramienta más". Es un contrato: los equipos piden un servicio con una plantilla, la plataforma genera el repositorio con estándares y el despliegue pasa por un flujo conocido y trazable.

[COMPLETAR: describe el punto de partida real — cuántos equipos y servicios había, qué dolores concretos de despliegue o seguridad se repetían y cómo se aprobaban los cambios — sin cifras que no puedas respaldar.]

Cuándo NO construir una IDP

  • Pocos servicios y un solo equipo. Si despliegas tres aplicaciones, un pipeline compartido y una convención escrita bastan.
  • Sin Kubernetes ni contenedores. La IDP se apoya en un modelo declarativo; si tu despliegue es manual, primero moderniza eso.
  • Sin dueño para mantenerla. Un portal es software propio: tiene backlog, versiones y soporte.
  • Si el cuello de botella es otro. Si los incidentes vienen de pruebas insuficientes o de mala observabilidad, ataca eso primero.

Arquitectura de referencia: Backstage, Argo CD, Kubernetes y tu stack

La arquitectura se apoya en separar el repositorio de aplicación del repositorio de configuración. Esa separación permite que CI construya y GitOps despliegue, sin que ningún pipeline tenga credenciales de clúster.

Diagrama de arquitectura

código
                  ┌──────────────────────┐
   Dev ──pide──▶  │  Backstage (portal)  │──genera──▶ Repo de app
                  │  · Scaffolder        │
                  │  · Catálogo          │──registra──▶ catalog-info.yaml
                  │  · TechDocs          │
                  └──────────────────────┘
                             ▲
                             │ anotaciones (enlaces operativos)
                             │
  Repo de app ──push──▶ CI (GitHub Actions / GitLab CI)
                             │  tests · SAST · build
                             ▼
                    Registro de imágenes (tag inmutable: sha-xxxx)
                             │
                             │ commit de la etiqueta
                             ▼
                    Repo GitOps (declarativo)
                             │
                             ▼
                     Argo CD ──sync──▶ Kubernetes (dev / staging / prod)

Diagrama del flujo de despliegue

código
[1] Scaffold ─▶ [2] Repo + pipeline ─▶ [3] Tests + imagen ─▶ [4] Commit del tag en GitOps
                                                                       │
                                                                       ▼
[8] Catálogo/ownership ◀── [7] Argo CD sincroniza dev ──▶ [6] PR a staging/prod (aprobación)

Repositorios y responsabilidades

  • Repo de la app: código, Dockerfile, pipeline, catalog-info.yaml y documentación.
  • Repo GitOps: solo manifiestos y overlays, un path por entorno. Sin código de aplicación.
  • Registro de imágenes: imágenes con etiqueta inmutable, nunca latest.

Qué hace cada herramienta y qué no

HerramientaHaceNo hace
BackstageCatálogo, plantillas, docs, ownershipNo despliega ni ejecuta contenedores
CI (Actions / GitLab CI)Tests, análisis de seguridad, build y push de imagenNo decide el estado deseado del clúster
Argo CDSincroniza el estado declarado en Git con el clústerNo construye imágenes ni corre pruebas
KubernetesEjecuta y programa las cargasNo organiza el catálogo de servicios

Creando plantillas de servicios y pipelines de autoservicio

La plantilla de Backstage

Una plantilla del Scaffolder declara parámetros y pasos. Este ejemplo crea un servicio con repositorio, pipeline y estructura GitOps:

yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: servicio-laravel
  title: Servicio Laravel
  description: Crea repositorio con app Laravel, pipeline y estructura GitOps.
spec:
  owner: plataforma
  type: service
  parameters:
    - title: Datos del servicio
      required: [name, owner]
      properties:
        name:
          title: Nombre
          type: string
          pattern: '^[a-z][a-z0-9-]{2,30}$'
        owner:
          title: Equipo responsable
          type: string
          ui:field: OwnerPicker
  steps:
    - id: fetch
      name: Obtener esqueleto
      action: fetch:template
      input:
        url: ./skeleton/laravel
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
    - id: publish
      name: Publicar repositorio
      action: publish:github
      input:
        repoUrl: github.com?owner=mi-org&repo=${{ parameters.name }}
        defaultBranch: main
    - id: register
      name: Registrar en el catálogo
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

El esqueleto del repositorio queda así:

código
skeleton/laravel/
├── catalog-info.yaml        # metadatos, owner, enlaces
├── Dockerfile
├── .github/workflows/build.yml
├── gitops/                  # valores que el CI actualizará
└── docs/index.md            # fuente de TechDocs

Para React cambia el esqueleto y los comandos de build, pero los pasos del Scaffolder y el flujo CI/CD son los mismos. Esa reutilización es la ventaja central de estandarizar.

Pipeline de CI: pruebas, seguridad e imagen inmutable

yaml
name: build
on:
  push:
    branches: [main]
env:
  IMAGE: ghcr.io/mi-org/${{ github.event.repository.name }}
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - name: Pruebas
        run: |
          composer install --no-interaction --prefer-dist
          php artisan test
      - name: Análisis de dependencias
        run: composer audit
      - name: Login al registro
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build y push con tag inmutable
        run: |
          TAG="sha-${GITHUB_SHA::12}"
          docker build -t "$IMAGE:$TAG" .
          docker push "$IMAGE:$TAG"
          echo "TAG=$TAG" >> "$GITHUB_ENV"

La etiqueta sha-<commit> es inmutable: identifica exactamente un artefacto y no puede reescribirse. latest rompe la trazabilidad y la estrategia de recuperación.

Actualizar la configuración declarativa en Git

El mismo pipeline hace commit en el repo GitOps cambiando solo la etiqueta de la imagen:

yaml
      - name: Actualizar repo GitOps
        run: |
          git clone https://x-access-token:${{ secrets.GITOPS_TOKEN }}@github.com/mi-org/gitops-config.git
          cd gitops-config
          sed -i "s|image: .*|image: $IMAGE:$TAG|" "apps/${{ github.event.repository.name }}/dev/deployment.yaml"
          git commit -am "chore: ${{ github.event.repository.name }} $TAG"
          git push

Integración con entornos existentes y seguridad

Aislamiento de entornos

Cada entorno vive en su propio namespace, con cuotas de recursos, límites y políticas de red que impiden tráfico lateral entre entornos. Los overlays de dev, staging y producción solo cambian parámetros, no la estructura.

Permisos mínimos en Argo CD

Un AppProject limita qué repos y namespaces puede tocar cada equipo:

yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: equipo-pagos
  namespace: argocd
spec:
  sourceRepos:
    - https://github.com/mi-org/gitops-config.git
  destinations:
    - namespace: pagos-dev
      server: https://kubernetes.default.svc
  clusterResourceWhitelist: []

El detalle clave es clusterResourceWhitelist: []: sin esto, un equipo podría crear recursos de clúster y saltarse el aislamiento.

Gestión segura de secretos

Nada de secretos en el repositorio. Se inyectan desde el gestor de la nube con External Secrets Operator:

yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: laravel-secrets
spec:
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: laravel-secrets
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: prod/laravel/db
        property: password

Controles para producción

  • Sin automated en el overlay de producción: el sync requiere aprobación explícita.
  • PR obligatorio con revisión para cualquier cambio en producción.
  • RBAC del clúster que impida a los equipos escribir directamente en namespaces de producción; todo pasa por Git.

La aplicación de Argo CD

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: laravel-dev
  namespace: argocd
spec:
  project: equipo-pagos
  source:
    repoURL: https://github.com/mi-org/gitops-config.git
    targetRevision: main
    path: apps/laravel/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: laravel-dev
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Para producción se omite el bloque automated.

Promoción a staging y producción

La promoción no vuelve a construir: mueve la misma imagen que pasó las pruebas.

  1. CI valida la imagen en dev.
  2. Se abre un PR que copia la etiqueta a apps/laravel/staging.
  3. Al aprobarse y hacer merge, Argo CD sincroniza staging.
  4. Para producción, un segundo PR más un revisor adicional (configurable con environment protection rules de GitHub o GitLab).
  5. Argo CD sincroniza producción en el momento aprobado o según ventana de cambio.

Versiones asumidas: plantillas de Backstage scaffolder.backstage.io/v1beta3, Argo CD 2.x con argoproj.io/v1alpha1 y External Secrets Operator external-secrets.io/v1beta1. Verifica la versión de tu instalación antes de copiar.

Un despliegue fallido: diagnóstico y recuperación

Supón que dev queda en ImagePullBackOff porque la etiqueta publicada no existe o el registro cambió de repositorio.

bash
argocd app get laravel-dev
kubectl -n laravel-dev get pods
kubectl -n laravel-dev describe pod laravel-7d9c8f-xxxx

Si el origen es la aplicación y el contenedor arranca y muere:

bash
kubectl -n laravel-dev logs laravel-7d9c8f-xxxx --previous

La recuperación en GitOps es determinista: el estado deseado vive en Git. Se revierte el commit que cambió la etiqueta:

bash
cd gitops-config
git revert HEAD
git push
argocd app sync laravel-dev

Argo CD vuelve a llevar el clúster a la versión anterior. En un incidente real también sirve argocd app rollback, pero úsalo como acción temporal: el arreglo permanente es el revert en Git, para que el repositorio no quede desincronizado del clúster.

Centralizando el servicio en Backstage

Cada repositorio lleva un catalog-info.yaml que relaciona el servicio con su equipo y sus enlaces:

yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: laravel
  annotations:
    argocd/app-name: laravel-dev
    backstage.io/techdocs-ref: dir:.
spec:
  type: service
  lifecycle: production
  owner: equipo-pagos

Con eso, el portal muestra quién es responsable, la documentación y el enlace a la aplicación en Argo CD. Cuando alguien pregunta "¿quién mantiene esto?", la respuesta está en el catálogo, no en un chat.

Lecciones y métricas de adopción

  • Empieza con una plantilla. Laravel y React con un flujo claro vale más que veinte plantillas a medias.
  • El owner es obligatorio. Un servicio sin dueño en el catálogo genera deuda, no autoservicio.
  • La plataforma es producto. Necesita dueño, roadmap y ciclo de versiones.
  • Progresión de autonomía. Primero self-service de repo y pipeline; después, promoción con aprobaciones; al final, automatizaciones más agresivas solo si el equipo las entiende.

Métricas útiles para medir adopción, todas orientativas y dependientes del contexto:

  • Tiempo desde "pido un servicio" hasta el primer despliegue en dev.
  • Porcentaje de servicios creados por plantilla frente a los creados a mano.
  • Despliegues con etiqueta inmutable frente al total.
  • Tiempo medio de recuperación ante un despliegue fallido.

No hay umbrales universales: define tu línea base al inicio y compárala contigo mismo, no con otro equipo.

[COMPLETAR: comparte cifras reales de adopción o de reducción de tiempo de entrega que hayas medido, con su período de referencia y la forma en que las obtuviste.]

Resumen

Una plataforma interna de despliegue con Backstage, Argo CD y Kubernetes no es magia: es una plantilla que genera un repositorio estándar, un CI que construye una imagen inmutable, un repositorio GitOps que declara el estado deseado y Argo CD que lo sincroniza. La seguridad se apoya en permisos mínimos, entornos aislados, secretos externos y aprobación para producción.

Empieza por un equipo y un tipo de servicio, mide, y amplía cuando el flujo sea aburrido de tan predecible.

Si estás montando algo parecido, escríbeme y cuéntame en qué punto estás y qué te cuesta más: el catálogo, las plantillas o la promoción entre entornos. Con gusto lo revisamos juntos.

Stiven Laiton

Desarrollador de software. ¿Te sirvió o tienes un caso parecido? Escríbeme.

Artículos relacionados

← Volver al blog