En tancar el mòdul 3 va quedar escrit que aquesta lliçó dissecciona l'anatomia completa d'un pipeline —les seves etapes, com s'orquestren, què es paral·lelitza, què es pot saltar i com se'n dissenya un que continuï sent ràpid quan l'equip hagi triplicat la seva mida. Aquesta és exactament la feina d'avui, i arriba en el moment adequat: Reservalia té quatre workflows —ci.yml, cd.yml, infra.yml i rollback.yml— construïts peça a peça al llarg de dos mòduls, sense que ningú s'hagi assegut mai a mirar-los com un sol sistema. Fixarem el vocabulari precís, recorrerem les etapes canòniques i l'invariant que cadascuna garanteix, veurem per què el seu ordre no és arbitrari, dibuixarem el graf real de Reservalia de punta a punta, entendrem per què cada execució parteix d'una màquina neta i com es passa informació entre jobs, dissenyarem la porta de qualitat i acabarem amb els antipatrons que converteixen un pipeline en un problema. El que no farem aquí és optimitzar temps —això és la 04-04— ni extreure plantilles reutilitzables —això és la 04-05—: avui toca entendre la forma abans de tocar-la.

Contingut

  1. Vocabulari precís: què és exactament cada peça
  2. Les etapes canòniques i l'invariant de cadascuna
  3. Per què l'ordre no és arbitrari: fail fast
  4. Els models d'orquestració: seqüencial, graf i fan-out/fan-in
  5. El graf complet de Reservalia
  6. Pipelines efímers davant de pipelines de llarga vida
  7. Pas d'informació entre jobs
  8. La porta de qualitat: bloquejar, informar o esperar
  9. Quatre pipelines, no un
  10. Antipatrons de disseny
  11. Errors Comuns i Consells
  12. Exercicis
  13. Conclusió

  1. Vocabulari precís: què és exactament cada peça

Fins ara hem fet servir aquestes paraules amb una certa laxitud perquè el context les aclaria. A partir d'aquest mòdul la precisió importa: quan a la 04-05 extraguem plantilles o a la 04-04 mesurem temps, "job" i "step" deixaran de ser intercanviables.

Terme Definició A Reservalia
Pipeline El recorregut complet d'un canvi des del commit fins a producció ci.ymlcd.yml, encadenats
Workflow Un fitxer YAML amb el seu disparador i el seu propi graf de jobs ci.yml, cd.yml, infra.yml, rollback.yml
Job Unitat d'execució aïllada: la seva pròpia màquina, el seu propi disc qualitat, test, build, publicar
Step Una ordre o acció dins d'un job; comparteix disc amb els seus germans npm ci, npm run lint
Etapa (stage) Concepte lògic: un grup de jobs amb el mateix propòsit "verificació", "empaquetament", "desplegament"
Disparador (trigger) L'esdeveniment que arrenca un workflow pull_request, push, workflow_run, workflow_dispatch
Porta (gate) Punt on el pipeline s'atura fins que es compleix una condició Aprovació de l'entorn prod
Artefacte Sortida que sobreviu a la màquina que la va produir reservalia/api:a3f9c21 a ECR
Runner La màquina que executa un job ubuntu-22.04

Dues distincions causen més confusió del que sembla. Job i step no són el mateix, i la diferència és física. Dos steps del mateix job corren a la mateixa màquina i comparteixen el sistema de fitxers: per això npm ci en un step deixa node_modules/ disponible per al següent. Dos jobs corren en màquines diferents i netes, possiblement alhora, i no comparteixen res llevat del que es transfereixi explícitament. Aquesta és la raó que a ci.yml cadascun dels quatre jobs repeteixi el seu propi npm ci: no és un descuit, és una conseqüència de l'aïllament.

"Etapa" és un concepte manllevat. GitLab CI i Jenkins tenen stages com a paraula clau, amb la semàntica "tots els jobs de l'etapa N acaben abans que comenci la N+1". GitHub Actions no té stages: té un graf declarat amb needs. Quan diguem "etapa" ens referirem a l'agrupament lògic, no a una sintaxi; l'equivalència exacta es veu al mòdul 6.

  1. Les etapes canòniques i l'invariant de cadascuna

Gairebé qualsevol pipeline del món, en qualsevol eina, és una variació de la mateixa seqüència de set etapes. L'important no és memoritzar-les, sinó entendre què afirma cadascuna: una etapa que passa és una promesa concreta sobre el canvi, i si no pots escriure aquella promesa en una frase, aquella etapa probablement sobra.

Etapa Pregunta que respon Invariant que garanteix en passar A Reservalia
Validar Està ben escrit? Compleix el format, no té errors de lint ni de tipus Job qualitat
Provar Fa el que ha de fer? El comportament observable és l'esperat Job test
Construir Es pot compilar? Existeix un dist/ reproduïble des d'aquest commit npm run build a build
Empaquetar És transportable? Hi ha una imatge autocontinguda que arrenca sense la màquina que la va crear docker build amb apps/api/Dockerfile
Publicar Existeix fora del runner? L'artefacte és immutable i adreçable per digest Job publicar → ECR
Desplegar Està corrent? L'entorn declara aquell digest i no un altre cd.yml sobre ECS
Verificar Funciona de debò? El servei respon i serveix la versió esperada Smoke test contra /version

Tres observacions sobre la taula. Construir i empaquetar solen fondre's en un sol job —a Reservalia tots dos viuen a build— però són invariants diferents: el primer diu que el codi compila, el segon que el resultat corre en una màquina que no és la teva. Publicar és la frontera de la immutabilitat: abans d'aquella etapa tot és reproduïble però volàtil; després existeix un objecte amb nom criptogràfic al qual apuntaran tots els entorns. I verificar no és opcional: sense ella, "desplegar" només afirma que una ordre d'AWS no ha donat error, que és exactament el diagnòstic de la lliçó 03-02.

  1. Per què l'ordre no és arbitrari: fail fast

La pregunta que ordena un pipeline és una de sola: si aquest canvi ha de fallar, com faig que falli com més aviat millor? D'aquí surt el principi de fail fast, amb dos criteris que de vegades entren en conflicte. Criteri 1: primer el que és barat. Un tsc --noEmit de 25 segons abans que una suite d'integració de tres minuts, i aquesta abans que un desplegament complet: costa menys descartar el canvi. Criteri 2: primer el que més falla. Si la meitat dels PR trencats ho estan per una prova de negoci, aquella prova s'hauria d'executar aviat encara que sigui cara; una etapa barata que no detecta mai res només afegeix latència.

En un graf el conflicte gairebé desapareix, perquè tot el que pot córrer alhora corre alhora i els criteris només ordenen dins de cada job. Aquest és l'ordre dels steps de qualitat, i no és casual:

    steps:
      - run: npx prettier --check .    # 1 · ~5 s  · format
      - run: npm run lint              # 2 · ~20 s · errors reals
      - run: npm run typecheck         # 3 · ~25 s · el mes car dels tres

Format primer perquè és instantani i la seva fallada es repara amb una ordre (npx prettier --write .); tipus al final perquè és el més lent. Si un PR porta els tres problemes, en Diego veu el més barat en cinc segons. I hi ha una asimetria de cost que justifica tot el disseny: el preu de detectar una fallada es multiplica per deu a cada etapa que avança. Un error de tipus detectat a qualitat costa un minut; el mateix error detectat a prod costa un incident, un rollback, un negoci que no va poder acceptar reserves i una fila a la taula incidents.

La regla dels 10 minuts que l'equip va fixar a la 02-01 mereix una formulació exacta: el temps des que empenys fins que saps si el teu canvi és acceptable no ha de superar els 10 minuts. No és un número màgic, és el llindar per sobre del qual la gent fa una altra cosa mentre espera, perd el context i torna mitja hora després. Tot disseny de pipeline és, en el fons, la gestió d'aquell pressupost, i avui Reservalia el compleix amb escreix: 50 s de validació, 3 min de proves, 4 min de construcció —l'únic que comença a estrènyer— i 2 min de publicació, en paral·lel llevat de l'últim.

  1. Els models d'orquestració: seqüencial, graf i fan-out/fan-in

Com s'ordenen els jobs entre si determina el temps total i la qualitat del senyal.

Seqüencial. Cada job espera l'anterior. És el que surt per defecte quan algú encadena needs sense pensar:

jobs:
  qualitat: { runs-on: ubuntu-22.04 }
  test:     { needs: qualitat, runs-on: ubuntu-22.04 }
  build:    { needs: test,     runs-on: ubuntu-22.04 }

Temps total = 50 s + 3 min + 4 min = 7 min 50 s. Avantatge real: si qualitat falla, no es gasten minuts de runner en test ni en build. Inconvenient: si build està trencat, el desenvolupador triga gairebé vuit minuts a assabentar-se'n.

Graf de dependències (DAG). Cada job declara només de qui depèn de debò, i el motor executa en paral·lel tot el que pot. És el que fa ci.yml:

jobs:
  qualitat: { runs-on: ubuntu-22.04 }                 # sense needs → arrenca ja
  test:     { runs-on: ubuntu-22.04 }                 # sense needs → arrenca ja
  build:    { runs-on: ubuntu-22.04 }                 # sense needs → arrenca ja
  publicar: { needs: [qualitat, test, build] }        # espera els tres

Temps total = max(50 s, 3 min, 4 min) + 2 min = 6 min. I una cosa més valuosa que el minut i mig estalviat: si el PR té una fallada de lint i una prova trencada, es veuen totes dues a la mateixa execució, en lloc d'arreglar-ne una, esperar i descobrir l'altra.

Fan-out / fan-in. És un patró dins del graf, no un model a part: fan-out és un punt que s'obre en diversos jobs paral·lels, fan-in el punt on convergeixen. A ci.yml el fan-out passa al disparador i el fan-in a publicar. La manera automàtica de fer fan-out és matrix:

  test:
    strategy:
      matrix:
        particio: [1, 2, 3]        # 1 · tres jobs reals, no tres steps
      fail-fast: false             # 2 · que una fallada no cancel-li els germans
    steps:
      - run: npm run test:unitat -- --shard=${{ matrix.particio }}/3
  1. matrix genera jobs, amb tot el que implica: màquines diferents, npm ci repetit a cadascuna i cap fitxer compartit.
  2. fail-fast: false és gairebé sempre el que vols en proves: prefereixes saber que fallen les particions 1 i 3 que no pas que es cancel·li tot a la primera fallada.

Jobs condicionals. El tercer mecanisme de forma és if:, que decideix si un job existeix en aquesta execució. publicar porta if: github.ref == 'refs/heads/main'; els jobs de cd.yml porten if: github.event.workflow_run.conclusion == 'success'. Un job saltat per if no compta com a fallada, i aquella propietat és la que sosté l'execució selectiva de la 04-04.

Model Temps total Cost en minuts Qualitat del senyal Quan fer-lo servir
Seqüencial Suma de tots Mínim Parcial: només la primera fallada Jobs molt cars i amb dependència real
Graf (DAG) El camí més llarg Més gran: es gasta en jobs que fallaran Completa Per defecte
Fan-out/fan-in El més lent del ventall El més gran de tots Completa i granular Suites llargues i divisibles

La conclusió pràctica: paral·lelitza llevat que hi hagi una dependència real de dades. Un needs de més és una decisió que costa minuts cada dia a cada persona de l'equip.

  1. El graf complet de Reservalia

Els quatre workflows no s'han dibuixat mai junts. Aquest és el pipeline de debò, del commit a producció:

flowchart TD
    subgraph CI["ci.yml · pull_request i push a main"]
        Q["qualitat ~50 s"] --> P["publicar ~2 min<br/>nomes main"]
        T["test ~3 min"] --> P
        B["build ~4 min"] --> P
    end
    subgraph CD["cd.yml · workflow_run si conclusion == success"]
        D1["desplegar dev"] --> S1["smoke /version"]
        S1 --> D2["desplegar staging"]
        D2 --> G["porta: aprovacio<br/>entorn prod"]
        G --> C["canary 10 → 50 → 100 %"]
        C --> R["registrar a desplegaments"]
    end
    subgraph ALTRES["Sota demanda"]
        I["infra.yml<br/>plan → aplicar"]
        RB["rollback.yml<br/>workflow_dispatch"]
    end
    P ==>|"digest de a3f9c21"| D1
    R -.->|"metriques DORA"| Q
    C -.->|"circuit breaker"| RB

Tres propietats d'aquest graf són decisions de disseny, no accidents. L'enllaç entre ci.yml i cd.yml és un workflow_run, no un needs. needs només funciona dins d'un workflow. Separar-los en dos fitxers té una raó concreta: ci.yml corre a cada pull request i no ha de tenir permisos sobre AWS; cd.yml corre només sobre main i sí que en té. La frontera entre els dos fitxers és una frontera de privilegis, i això és matèria de la 04-03.

El que viatja per la fletxa gruixuda és un digest, no codi font. El principi de "construir una vegada, promocionar el mateix digest" es materialitza aquí: cd.yml no reconstrueix mai res. infra.yml i rollback.yml pengen fora de la cadena expressament. No formen part del camí d'un canvi de codi: un es dispara quan canvia infra/, l'altre quan una persona decideix tornar enrere. Un pipeline no és un únic fil, és un graf amb entrades per diversos llocs.

  1. Pipelines efímers davant de pipelines de llarga vida

Una decisió que gairebé ningú no pren conscientment perquè les eines modernes ja la porten presa: cada execució parteix d'una màquina neta que es destrueix en acabar.

Efímer (Actions, GitLab SaaS) Llarga vida (Jenkins clàssic)
Estat inicial Màquina nova, imatge coneguda El que va deixar l'execució anterior
Dependències del sistema Es declaren o s'instal·len Instal·lades a mà fa anys
Reproductibilitat Alta: dues execucions iguals donen el mateix Baixa: depèn de l'historial de l'agent
Fallada típica "Falta instal·lar X" en fred "A l'agent 3 falla i al 4 no"

Els tres arguments a favor de l'efímer són contundents. Un verd només val si es pot repetir: si un job passa perquè en aquell agent algú va instal·lar una eina el 2021, no has verificat el teu codi, has verificat aquell agent. Un contenidor que va quedar viu, un fitxer temporal o una variable exportada contaminen l'execució següent, i produeixen la pitjor fallada possible: intermitent i dependent d'a quina màquina caigui. I en seguretat, un runner reutilitzat que ha executat codi d'un PR d'un fork pot haver deixat alguna cosa esperant el job següent, que potser sí que té credencials. El preu és l'arrencada en fred: instal·lar dependències a cada execució. La resposta correcta no és conservar estat, sinó desar en memòria cau entrades verificables —dependències identificades pel hash del lockfile, capes de Docker pel seu digest—, que és reconstruir a partir d'una cosa la identitat de la qual es comprova. Aquell matís és la diferència entre una memòria cau i un agent brut, i es desenvolupa a la 04-04.

  1. Pas d'informació entre jobs

Com que els jobs no comparteixen disc, tota dada que travessi la frontera s'ha de transferir explícitament. Hi ha quatre mecanismes, i triar malament produeix pipelines lents o trencats.

Mecanisme Què transporta Mida Persistència Ús típic
outputs Cadenes curtes Bytes L'execució Un SHA, un digest, un booleà
Artefactes d'execució Fitxers Fins a GB Dies (retenció) dist/, informes, captures
Registre extern Imatges, paquets GB Mesos o anys ECR, npm
Variables i secrets Configuració Bytes Permanent AWS_REGION, ARN de rols
  build:
    outputs:
      digest: ${{ steps.imatge.outputs.digest }}     # 1 · s exposa al graf
    steps:
      - id: imatge
        uses: docker/build-push-action@v5
        with: { context: ., file: apps/api/Dockerfile, push: true }
      - uses: actions/upload-artifact@v4               # 2 · fitxers, no cadenes
        with: { name: web-dist, path: apps/web/dist, retention-days: 7 }

  publicar:
    needs: [build]
    steps:
      - run: echo "Promociono ${{ needs.build.outputs.digest }}"   # 1
  1. Un output de job es declara a nivell de job i s'alimenta de l'output d'un step amb id: és el canal correcte per al digest, una dada petita i d'identitat. Es llegeix amb needs.<job>.outputs.<nom>, cosa que implica una dependència real: només pots llegir l'output d'un job que sigui al teu needs.
  2. Els artefactes són per a fitxers, i retention-days importa: per defecte són 90 dies, i en un repositori actiu això són gigabytes facturables de dist/ que ningú no tornarà a obrir.

La regla pràctica: cadenes curtes per outputs, fitxers per artefactes, i qualsevol cosa que hagi de sobreviure al workflow, a un registre extern. Pujar una imatge Docker com a artefacte d'execució per "passar-la" al job següent és un antipatró car: centenars de megues de pujada i baixada davant d'un docker pull des d'ECR, que a més deixa l'artefacte on ha de ser.

  1. La porta de qualitat: bloquejar, informar o esperar

No tot el que un pipeline executa ha de tenir dret de veto. Confondre aquestes categories produeix, o bé un main fràgil, o bé un equip que aprèn a saltar-se les regles. I hi ha dues portes diferents, amb criteris diferents: la que bloqueja el merge i la que bloqueja el desplegament.

Comprovació Bloqueja el merge? Bloqueja el desplegament? Efecte si falla
qualitat (format, lint, tipus) Sí, indirectament Botó de fusionar desactivat
test (unitàries + integració) Sí, indirectament Botó de fusionar desactivat
build (compilar + imatge) Sí, indirectament Botó de fusionar desactivat
Quality gate de SonarQube (codi nou) No Botó de fusionar desactivat
Cobertura o mida de la imatge fora de llindar No No Comentari al PR
Smoke test contra /version Desplegament vermell, rollback
Mètriques del canary fora de llindar S'atura la promoció
Aprovació de l'entorn prod Espera Pausa fins que la Marta o la Nuria aprovin
Suite E2E completa (nocturna) No No Tiquet l'endemà al matí

El criteri per classificar és una sola pregunta: una fallada aquí significa sempre que el codi està malament? Si la resposta admet un "depèn", no és bloquejant. Una prova E2E que falla el 3 % de les vegades per un problema de xarxa no compleix el criteri, i convertir-la en bloquejant ensenya l'equip que "el vermell de vegades no importa", cosa que destrueix el valor de tots els altres checks. I un job pot ser informatiu de dues maneres, que no són equivalents:

  cobertura:
    continue-on-error: true          # el job queda en groc, el workflow continua verd
    steps:
      - run: npm run test -- --coverage --coverage.thresholds.lines=70

continue-on-error: true deixa el job marcat però no tomba el workflow. L'alternativa —simplement no incloure'l als checks obligatoris de la regla de protecció de branca— és més neta, perquè el vermell continua sent vermell i només deixa de bloquejar. Prefereix la segona: continue-on-error sobre un check que sí que hauria de bloquejar és una manera silenciosa de desactivar una regla sense que ningú se n'adoni.

  1. Quatre pipelines, no un

Un error freqüent és ficar-ho tot en un únic workflow que es dispara sempre. El resultat són PR de vint minuts amb verificacions que només tenen sentit una vegada al dia.

Pull request main Nocturn Sota demanda
Disparador pull_request push a main schedule: cron workflow_dispatch
Objectiu És segur fusionar? Produir i desplegar El que és lent i el que no bloqueja Operacions puntuals
Pressupost < 10 min < 20 min Sense límit pràctic El que calgui
E2E Només els 5 crítics Els 5 crítics Tots
Imatge i desplegament Es construeix, no es publica Es publica i es desplega fins a prod rollback.yml, infra.yml
Escaneig de seguretat Ràpid Ràpid Profund (04-03)

La lògica de fons és un intercanvi entre latència i cobertura. Al PR es paga per latència: cada minut el pateix una persona esperant, així que només hi entra el que respon a "és segur fusionar?". Al nocturn la latència és gratis —ningú no espera— i hi caben una suite E2E de quaranta minuts i un escaneig complet.

# .github/workflows/nocturn.yml — el cinque workflow de Reservalia
name: Nocturn
on:
  schedule: [{ cron: '0 2 * * 1-5' }]   # el cron s interpreta en UTC, no en la zona del repo
  workflow_dispatch:                     # i a ma, per reproduir sense esperar a l endema

El nocturn té un parany conegut: si ningú no en mira els resultats, no existeix. Reservalia ho resol amb una regla que la Marta va escriure al README.md: el nocturn en vermell es revisa a primera hora i, si no s'arregla el mateix dia, s'obre un tiquet amb propietari.

  1. Antipatrons de disseny

El pipeline monolític d'un sol job. Tot en steps seguits: lint, proves, build, push, desplegament. Surt de manera natural en començar, perquè evita el problema de compartir fitxers entre jobs. I funciona… fins que triga divuit minuts, no hi ha res paral·lelitzable, una fallada de format al segon minut impedeix saber si les proves passen i —el més greu— el job que executa npm ci és el mateix que té les credencials de producció. La cura és partir-lo per responsabilitats i acceptar el cost de transferir artefactes.

El job que ho fa tot. Variant més subtil de l'anterior: el graf existeix, però un dels jobs acumula responsabilitats perquè "ja que la màquina és calenta". Un build que a més desplega, notifica i actualitza un tiquet és impossible de reintentar per parts: quan falla la notificació, s'ha de repetir la construcció sencera. Un job hauria de poder rellançar-se tot sol, i això exigeix que tingui un únic invariant.

La lògica de negoci amagada al YAML. Un run: | de setanta línies de Bash que decideix a quin entorn es desplega segons la branca, calcula el número de versió i aplica regles de negoci. No es pot provar, no es pot executar en local, no té control de tipus, no el revisa ningú perquè és dins d'un fitxer de configuració, i el dia que es migri d'eina s'ha de reescriure sencer. La regla: el YAML orquestra, els scripts fan. Per això Reservalia té infra/scripts/desplegar.sh, pes-canary.sh i comprovar-metriques.sh com a fitxers versionats i executables des d'un portàtil, i no com a blocs incrustats.

El pipeline que només entén la Nuria. Quatre-centes línies amb condicions imbricades, expressions il·legibles i noms com job2. Funciona, però és un punt únic de fallada humana: quan la Nuria és de vacances i el pipeline es trenca, l'equip es queda aturat. Els símptomes són fàcils de detectar: ningú més no ha tocat .github/ en sis mesos i, quan algú ho intenta, prova a base de commits amb missatge "fix ci" repetits. La cura té tres parts —noms explícits, comentaris que expliquin el perquè i no el què, i un CODEOWNERS que reparteixi el coneixement— i es desenvolupa a la 04-05. Un últim antipatró tanca la llista: el pipeline que acaba en un portàtil, en què el workflow construeix i publica però l'últim pas el fa una persona a la seva màquina. És el ritual del divendres de la 01-04, i reapareix amb disfresses —l'arreglament ràpid pujat a mà, la migració que en Diego llança des de psql—. Si un camí cap a producció no passa pel pipeline, el pipeline no és la font de veritat.

Errors Comuns i Consells

Error 1: encadenar needs per costum. Cada needs innecessari converteix un graf en una fila índia: declara només les dependències de dades reals. Error 2: confondre job amb step. Esperar que un job vegi el node_modules/ de l'anterior produeix una fallada desconcertant —"però si acabo d'instal·lar-lo"—; són màquines diferents.

Error 3: un únic workflow per a tot. Fica verificacions nocturnes a cada PR i permisos de producció al pipeline dels PR; separa'ls per disparador i per privilegi. Error 4: continue-on-error sobre un check que sí que ha de bloquejar, que desactiva una regla de protecció sense que ningú se n'adoni.

Error 5: passar imatges de contenidor com a artefactes d'execució. Centenars de megues de pujada i baixada on n'hi havia prou amb un docker pull. Error 6: donar per bo un verd que depèn de l'estat del runner: si el job passa per alguna cosa que algú va instal·lar a mà a l'agent, no has verificat el teu codi. Consell 1: dibuixa el teu pipeline; un diagrama de vint línies revela dependències absurdes que en YAML passen desapercebudes durant mesos. Consell 2: posa timeout-minutes a tots els jobs, proper al doble de l'habitual: sense això, un job penjat consumeix sis hores de minuts facturables abans de rendir-se. Consell 3: escriu al costat de cada job quant triga, que és la línia base sense la qual la 04-04 no pot començar.

Exercicis

Exercici 1

Un equip té un ci.yml amb cinc jobs encadenats en seqüència: install (1 min) → lint (40 s) → test (4 min) → build (3 min) → e2e (7 min). Cada job repeteix el npm ci perquè l'anterior no li deixa res. Calcula el temps total, proposa un redisseny amb el model adequat indicant els needs de cada job i estima el nou temps. Justifica què fas amb e2e.

Exercici 2

A Reservalia, algú proposa afegir al job build un step que executi proves de càrrega contra staging "ja que la imatge està construïda". Argumenta si és bona idea fent servir els conceptes del tema, i digues on col·locaries aquella verificació i per què.

Exercici 3

Un pipeline publica la imatge al job build, i el job desplegar la reconstrueix des del codi font perquè "així és més simple que passar el digest". Explica quin invariant es trenca, què pot sortir malament en concret i reescriu el pas d'informació amb el mecanisme correcte.

Solucions

Solució 1. Temps actual: 1 + 0,67 + 4 + 3 + 7 = 15 min 40 s, més el npm ci repetit cinc vegades.

Redisseny amb un graf. install desapareix com a job: instal·lar en una màquina no serveix de res en una altra, així que cada job fa el seu propi npm ci amb memòria cau de dependències (04-04). Queden:

  lint:  { }                       # sense needs
  test:  { }                       # sense needs
  build: { }                       # sense needs
  e2e:   { needs: [build] }        # necessita la imatge: dependencia real de dades

Els tres primers arrenquen alhora. El camí crític és build (3 min) + e2e (7 min) = 10 minuts, davant de 15:40. Però 10 minuts continuen sent al límit del pressupost d'un PR, i el culpable és e2e. La decisió correcta és treure la suite E2E completa del pipeline de PR: deixar els cinc escenaris crítics (≈ 90 s) i moure la resta al pipeline nocturn de l'apartat 9. El PR baixa aleshores a uns 4 min 30 s i la cobertura completa es conserva, només que amb la latència desplaçada allà on no la pateix ningú.

Solució 2. No és bona idea, per tres raons que s'apuntalen en tres conceptes diferents. (1) Confon categories: una prova de càrrega no respon a "és segur fusionar aquest canvi?" sinó a "el sistema aguanta?", i el seu resultat depèn de l'entorn i del trànsit del moment; és informativa, no bloquejant, així que no pertany al camí que bloqueja el merge. (2) Trenca el pressupost de temps: una prova de càrrega amb sentit dura entre deu i trenta minuts, multiplica per cinc el temps del PR i fa que l'equip deixi de mirar el pipeline. (3) Trenca l'invariant del job i la frontera de privilegis: build té un únic invariant —"existeix una imatge que arrenca"— i apuntar contra staging n'hi afegeix un altre; a més build corre a cada PR, així que dotze persones podrien estar carregant staging alhora, cap mesura no seria fiable i l'entorn quedaria inservible per validar desplegaments. I build no té, ni ha de tenir, credencials sobre entorns desplegats.

On va: al pipeline nocturn, contra staging en repòs, comparant amb una línia base històrica i obrint tiquet si es degrada. Com es dissenya aquella prova és matèria de la 04-04.

Solució 3. Es trenca l'invariant de l'etapa publicar: "l'artefacte és immutable i és el mateix a tots els entorns", el principi de construir una vegada i promocionar el digest de la 02-06. Reconstruir produeix un binari diferent encara que el codi font sigui idèntic: hi pot entrar una versió nova d'una dependència transitiva no fixada, la imatge base node:20.11.0-bookworm-slim pot haver rebut pedaços amb la mateixa etiqueta, o pot canviar qualsevol detall de l'entorn de construcció. Conseqüència concreta: el que es va verificar a build no és el que es desplega, i /version pot dir la veritat sobre el commit mentre menteix sobre el contingut. Si la fallada apareix només en producció, no hi ha manera de reproduir-la, perquè l'artefacte verificat ja no existeix.

El pas correcte és propagar el digest immutable per outputs:

  build:
    outputs: { digest: '${{ steps.imatge.outputs.digest }}' }
    steps:
      - id: imatge
        uses: docker/build-push-action@v5
        with: { context: ., file: apps/api/Dockerfile, push: true,
                tags: '${{ env.ECR }}/reservalia/api:${{ github.sha }}' }
  desplegar:
    needs: [build]
    steps:
      - run: ./infra/scripts/desplegar.sh dev "${{ needs.build.outputs.digest }}"

El digest (sha256:…) és preferible a l'etiqueta fins i tot quan l'etiqueta és el SHA del commit: una etiqueta es pot reescriure apuntant a una altra imatge, un digest no. És la diferència entre un nom i una empremta.

Conclusió

Ara hi ha una visió de conjunt on abans hi havia quatre fitxers YAML. Tenim el vocabulari precís —workflow, job, step, etapa, porta, disparador, artefacte— i sabem que la separació entre jobs és física i per això costa travessar-la. Coneixem les set etapes canòniques i, més important, l'invariant que cadascuna promet en passar; sabem per què l'ordre obeeix al fail fast i a una asimetria de cost que multiplica per deu el preu de la fallada a cada etapa que avança. Coneixem els models d'orquestració i per què el graf és el que es fa servir per defecte: no només estalvia temps, sinó que lliura tot el senyal de cop en lloc de degotant. Hem dibuixat el graf real de Reservalia i hem vist que la frontera entre ci.yml i cd.yml és una frontera de privilegis i que el que la travessa és un digest. Sabem per què cada execució parteix d'una màquina neta, com moure informació entre jobs amb el mecanisme correcte, i com classificar cada comprovació en bloquejant, informativa o d'espera, amb dues portes diferents: la del merge i la del desplegament.

Queda també un inventari del que un pipeline no ha de ser: un sol job que ho fa tot amb les credencials de producció a la mà, un job que acumula responsabilitats i no es pot reintentar per parts, setanta línies de Bash amb lògica de negoci dins d'un fitxer de configuració, quatre-centes línies que només entén una persona, o una cadena que acaba al portàtil d'algú. Amb la forma del pipeline ja clara, el mòdul ataca els quatre fronts d'un en un, i el següent és el que més silenciosament trenca la reproductibilitat que tanta feina va costar aconseguir. La lliçó Gestió de Dependències entra al package-lock.json de Reservalia per respondre la pregunta que la Marta no va saber contestar en tancar el mòdul 3: quants paquets entren de debò a reservalia/api:a3f9c21, qui els manté, què passa quan un d'ells publica una versió menor amb un canvi de comportament, i com es manté tot allò al dia sense que l'equip es passi el dilluns revisant quaranta pull requests d'actualització.

Curs de CI/CD: Integració i Desplegament Continu

Mòdul 1: Introducció al CI/CD

Mòdul 2: Integració Contínua (CI)

Mòdul 3: Desplegament Continu (CD)

Mòdul 4: Pràctiques Avançades de CI/CD

Mòdul 5: Implementació de CI/CD en Projectes Reals

Mòdul 6: Eines i Tecnologies

Mòdul 7: Exercicis Pràctics

Mòdul 8: Recursos Addicionals

© Copyright 2026. Tots els drets reservats