Saltar al contenido principal
Jordi Izquierdo Montalbán
Lo que practico

Fundamentos

CI/CD

Cada push pasa lint, tipos, tests y auditoría antes de integrarse.

El problema que resuelve la entrega continua

Antes de automatizar nada, conviene entender qué se intenta evitar: las integraciones tardías. Cuando varias personas trabajan semanas sobre ramas largas, el momento de juntar el código se convierte en un evento doloroso, con conflictos, regresiones y una versión que nadie se atreve a publicar. La idea de fondo de CI/CD es la contraria: integrar y desplegar con tanta frecuencia que cada cambio sea pequeño, fácil de revisar y fácil de deshacer.

Un pipeline no es una herramienta, es un contrato: describe, en un fichero versionado junto al código, qué condiciones debe cumplir un cambio para llegar a producción y quién (o qué) las comprueba.

Integración, entrega y despliegue: tres cosas distintas

Cada nivel automatiza un tramo más del camino hacia producción.

Los tres términos se mezclan a menudo, pero describen niveles de automatización distintos.

PrácticaQué garantizaQuién pulsa el botón de producción
Integración continua (CI)Cada cambio se compila y se prueba en un entorno limpio y compartidoNo aplica
Entrega continuaAdemás, siempre existe un artefacto verificado y listo para desplegarUna persona, cuando el negocio lo decide
Despliegue continuoAdemás, todo cambio que pasa las puertas llega a producciónEl propio pipeline

La entrega continua desacopla dos decisiones que suelen confundirse: desplegar (poner código en producción) y liberar (que los usuarios lo vean). Con banderas de funcionalidad se puede desplegar a diario y liberar cuando convenga.

Anatomía de un pipeline

Un disparador lanza un workflow formado por trabajos que se ejecutan en runners.

Los conceptos básicos son los mismos en casi todas las plataformas (GitHub Actions, GitLab CI, Jenkins, CircleCI, Azure Pipelines), aunque cambie el vocabulario.

ConceptoDescripción
DisparadorEl evento que lanza el pipeline: push, pull request, etiqueta, programación con cron, lanzamiento manual o llamada desde otro workflow
Workflow o pipelineLa definición completa, escrita en YAML y versionada con el código
Job o trabajoUn conjunto de pasos que se ejecutan en la misma máquina. Por defecto, los jobs son independientes y corren en paralelo
Step o pasoUn comando de shell o una acción reutilizable
Runner o agenteLa máquina (alojada por la plataforma o propia) que ejecuta el job. Arranca limpia en cada ejecución si es efímera
ArtefactoFichero producido por un job y conservado: paquete, imagen, informe de cobertura
CachéDatos que se reutilizan entre ejecuciones para no repetir trabajo, como dependencias descargadas
MatrizUn mismo job repetido con combinaciones de parámetros: versiones del lenguaje, sistemas operativos
EntornoDestino con nombre (staging, producción) que puede llevar secretos, aprobaciones y reglas propias

Runners alojados y propios

Los runners alojados por la plataforma son cómodos, aislados y se descartan tras cada uso. Los propios (self-hosted) permiten hardware especial, acceso a redes privadas o ahorrar costes, pero heredan el riesgo de la persistencia: si un job malicioso deja algo en la máquina, el siguiente lo encontrará. Los runners propios deben ser efímeros (una máquina o contenedor nuevo por job) y no deberían aceptar código de repositorios públicos sin aislamiento.

Las etapas y su orden

De lo más barato a lo más caro: el error se detecta cuanto antes.

El orden sigue una regla económica: lo barato y lo que más falla, primero. Un error de formato no debe esperar diez minutos de tests.

EtapaQué detectaCoste típico
Formato y lintEstilo, errores evidentes, malas prácticasMuy bajo
Comprobación de tiposIncoherencias antes de ejecutarBajo
Escaneo de secretosCredenciales subidas por errorBajo
Tests unitariosLógica rotaMedio
Tests de integraciónFallos entre componentes, base de datos, colasAlto
Análisis estático de seguridad (SAST)Patrones vulnerables en el código propioMedio
Auditoría de dependencias (SCA)Librerías con vulnerabilidades conocidas y licencias incompatiblesBajo
Build y empaquetadoQue el artefacto se genera correctamenteMedio
Pruebas end to endFlujos completos desde la interfazMuy alto
Pruebas de humo tras desplegarQue lo desplegado arranca y respondeBajo

Puertas de calidad

Una puerta de calidad es una condición automática que bloquea el avance. Para que funcionen, deben ser objetivas, automáticas y respetadas por todos, incluidas las personas con permisos de administración.

  • Protección de la rama principal: exigir pull request, revisión aprobada y comprobaciones superadas antes de fusionar.
  • Comprobaciones obligatorias: el merge solo se habilita si los jobs designados pasan.
  • Cobertura sobre el código nuevo, mejor que un umbral global: un porcentaje global apenas se mueve y anima a escribir tests triviales.
  • Rama actualizada: exigir que la rama esté al día con la principal evita fusionar algo que solo pasa contra una base antigua. Una cola de fusión (merge queue) automatiza esa comprobación.
  • Revisión de código como puerta humana: lo que no se puede automatizar (diseño, intención, claridad).

Build reproducible y artefactos inmutables

El principio más valioso del continuo es construir una vez, desplegar muchas: el artefacto que se probó en staging es, byte a byte, el que llega a producción.

El mismo artefacto sube de entorno; lo único que cambia es la configuración.

Para conseguirlo:

  1. Fijar las entradas: versiones exactas de dependencias con fichero de bloqueo (lockfile), imagen base con versión o digest, herramientas con versión concreta.
  2. Instalaciones deterministas: usar el comando que respeta el lockfile y falla si no cuadra, no el que resuelve versiones de nuevo.
  3. Identificar el artefacto por el commit y por su resumen criptográfico (digest), no solo por una etiqueta móvil como latest.
  4. Separar configuración y artefacto: lo que cambia entre entornos se inyecta al arrancar, nunca se hornea en el build.
  5. Generar una lista de materiales (SBOM) y una firma del artefacto, de modo que se pueda verificar su procedencia.

Un build reproducible en sentido estricto (dos compilaciones producen exactamente los mismos bytes) es un ideal exigente; en la práctica, la inmutabilidad del artefacto publicado y la trazabilidad hasta el commit ya dan la mayor parte del valor.

Entornos y promoción

Los entornos habituales son desarrollo, staging (o preproducción) y producción, y a veces entornos efímeros por pull request, que se crean al abrir el cambio y se destruyen al cerrarlo.

  • Staging debería parecerse a producción en versiones, topología y configuración; si no, valida poco.
  • La promoción mueve el mismo artefacto de un entorno al siguiente cuando se cumplen las condiciones: pruebas superadas, aprobación manual donde proceda, ventana horaria permitida.
  • Los entornos protegidos de la plataforma permiten exigir revisores y limitar qué ramas pueden desplegar en ellos.
  • Los datos de pruebas no deben ser datos reales de producción sin anonimizar.

Secretos y credenciales

Los secretos son el punto donde un pipeline cómodo se convierte en un riesgo grave.

PrácticaRazón
Guardar secretos en el almacén de la plataforma o en un gestor externo, nunca en el repositorioEl historial de Git es permanente
Limitar cada secreto a un entorno o repositorio concretoReduce el alcance si se filtra
Preferir credenciales efímeras a claves de larga vidaUna clave robada caduca sola
No imprimir secretos ni variables de entorno completasLos logs se leen y se conservan
Rotar y revocar ante cualquier sospechaUn secreto expuesto se considera comprometido

OIDC: adiós a las claves estáticas

El pipeline no guarda claves: pide un token de vida corta con su identidad.

Con OpenID Connect, el job pide a la plataforma un token firmado que dice quién es (repositorio, rama, entorno) y el proveedor cloud lo cambia por credenciales temporales. No hay clave almacenada que robar, y se puede restringir el acceso a una rama o entorno concretos.

Seguridad del propio pipeline

El pipeline tiene acceso a código, secretos y producción: es un objetivo atractivo para ataques a la cadena de suministro.

RiesgoDefensa
Permisos excesivos del token del workflowDeclarar permisos mínimos a nivel de workflow y ampliar solo en el job que lo necesita
Acciones de terceros que cambian bajo los piesFijarlas por hash completo de commit, no por una etiqueta que el autor puede mover
Pull requests de forksNo exponer secretos a código externo; extremar el cuidado con disparadores que ejecutan con privilegios sobre código ajeno
Inyección en scriptsNo interpolar datos controlados por el atacante (título de PR, nombre de rama) directamente en comandos de shell; pasarlos como variables de entorno
Dependencias comprometidasLockfile, auditoría, y actualizaciones automatizadas revisadas
Cachés envenenadasNo restaurar cachés de ramas no confiables en builds de publicación
Runners persistentesEfímeros y aislados
Artefactos manipuladosFirmar y verificar antes de desplegar

Un workflow de ejemplo

Ejemplo de GitHub Actions con permisos mínimos, concurrencia, matriz, caché, artefacto y despliegue protegido. Las acciones se muestran con versión legible por claridad; en un proyecto real se fijarían por hash de commit.

name: ci
 
on:
  pull_request:
  push:
    branches: [main]
 
permissions:
  contents: read
 
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  calidad:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
 
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    strategy:
      fail-fast: false
      matrix:
        node: [20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm test
 
  build:
    needs: [calidad, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist
 
  desplegar:
    if: github.ref == 'refs/heads/main'
    needs: build
    runs-on: ubuntu-latest
    environment: produccion
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
      - run: ./desplegar.sh

Detalles que merece la pena notar: los permisos globales son de solo lectura y solo el job de despliegue pide el token de identidad; la concurrencia cancela ejecuciones obsoletas de una misma rama; el despliegue depende del build y reutiliza su artefacto en lugar de recompilar; el entorno protegido puede exigir aprobación manual.

Pipelines rápidos

Un pipeline lento se esquiva. Como orientación general, cuanto más corto el ciclo de retroalimentación, más se respeta.

  • Paralelismo: ejecutar en paralelo lo que no depende entre sí (lint, tipos y tests).
  • Caché de dependencias y de builds, con claves derivadas del lockfile para que se invaliden cuando toca.
  • Caché de capas de contenedor y builds incrementales.
  • Test impact analysis: ejecutar solo los tests afectados por el cambio, con una ejecución completa periódica o en la rama principal.
  • Particionar las suites largas entre varios runners.
  • Cancelar ejecuciones obsoletas cuando llega un commit nuevo a la misma rama.
  • Fallar pronto: las comprobaciones baratas delante.
  • Vigilar la inestabilidad (flaky tests): un test que falla al azar enseña a reintentar en lugar de investigar. Se aísla, se arregla o se elimina.

Estrategias de ramas y su relación con el pipeline

Ramas cortas, integración diaria y funcionalidades incompletas tras un flag.
EstrategiaIdeaEncaje con CI/CD
Desarrollo basado en troncoRamas muy cortas que se integran a diario en la principalEl más natural para entrega continua; requiere banderas de funcionalidad y buenas pruebas
GitHub FlowRama por cambio, pull request y fusión a la principal, que siempre es desplegableSencillo y suficiente para muchos equipos
GitflowRamas de desarrollo, release y hotfix de larga vidaPensado para versiones empaquetadas con soporte paralelo; añade fricción si se despliega a menudo
Ramas de releaseUna rama por versión mantenidaÚtil con varias versiones en producción a la vez

Las ramas de larga vida acumulan divergencia, y la divergencia es el riesgo que CI intenta eliminar.

GitOps

Un agente dentro del clúster compara el estado deseado con el real y lo reconcilia.

GitOps lleva la idea del control de versiones al despliegue: el estado deseado de la infraestructura y de las aplicaciones vive en un repositorio, y un agente dentro del entorno (Argo CD, Flux) lo compara con el estado real y lo reconcilia.

  • Desplegar es fusionar un cambio en el repositorio de estado; volver atrás es revertir ese cambio.
  • El pipeline de CI construye y publica la imagen y propone el cambio de versión; el agente hace el despliegue en modo pull, de modo que el pipeline no necesita credenciales del clúster.
  • Se obtiene auditoría gratuita (quién cambió qué y cuándo) y detección de deriva (alguien tocó algo a mano).

Medir: las métricas DORA

Dos miden velocidad y dos miden estabilidad; se leen juntas.

El programa de investigación DORA propone cuatro métricas para medir el rendimiento de la entrega de software:

MétricaQué mide
Frecuencia de despliegueCon qué regularidad se publica en producción
Tiempo de entrega de cambiosDesde que un cambio se confirma hasta que está en producción
Tasa de fallo de cambiosQué proporción de despliegues provoca un fallo que exige intervención
Tiempo de recuperaciónCuánto se tarda en restablecer el servicio tras un fallo

Dos miden velocidad y dos estabilidad, y deben leerse juntas: ir rápido rompiendo cosas o ir despacio sin romper nada son, ambos, resultados pobres. Cada equipo debería comparar su evolución con su propio pasado, no perseguir cifras ajenas.

Lista de comprobación

  • Cada cambio pasa por un pull request con pipeline obligatorio.
  • La rama principal está protegida y siempre es desplegable.
  • El pipeline completo de un cambio normal tarda lo bastante poco como para esperarlo.
  • Hay un único build por versión; los entornos solo difieren en configuración.
  • Las dependencias están bloqueadas con lockfile y se auditan.
  • Los permisos del workflow son mínimos y las acciones de terceros están fijadas por hash.
  • No hay claves de larga vida en el almacén; se usan credenciales efímeras cuando es posible.
  • El despliegue incluye una prueba de humo y una vía de vuelta atrás probada.
  • Hay un procedimiento para los tests inestables.
  • Se miden las cuatro métricas DORA y se revisan periódicamente.

Antipatrones

  • Pipeline decorativo: existe, pero se puede saltar o se ignora cuando falla.
  • Reintentar hasta que pase, en lugar de investigar la causa.
  • Recompilar en cada entorno, con el riesgo de desplegar algo distinto de lo probado.
  • Configuración horneada en la imagen, que obliga a construir de nuevo para cambiar un valor.
  • Secretos en variables visibles o en el repositorio.
  • Etiquetas móviles (latest, versiones mayores de acciones) como base de un build reproducible.
  • Despliegues manuales «solo esta vez», que dejan el entorno fuera del control del código.
  • Entornos de staging que no se parecen a producción.
  • Ramas de larga vida que se integran de golpe antes de la entrega.
  • Pipelines monolíticos de una hora que nadie espera.
  • Cobertura como objetivo en sí misma, sin mirar la calidad de las pruebas.