Aquesta lliçó té un angle diferent de les anteriors, i convé dir-ho des del principi: no triaràs Travis CI per a un projecte nou. El que sí que és molt probable és que te'l trobis —en un repositori intern que ningú ha tocat des del 2019, en una dependència de codi obert on vols contribuir, en la migració que t'encarreguen en entrar en un equip— i que hagis d'entendre què fa aquell .travis.yml de trenta línies i traduir-lo. I hi ha una segona raó per dedicar-li una lliçó sencera: Travis va inventar bona part del que aquest curs dóna per evident. El fitxer de CI versionat al costat del codi, la matriu de versions de llenguatge, el CI gratuït i il·limitat per a codi obert i la insígnia verda al README són seus; GitHub Actions i GitLab CI hereten la seva idea central. I després va passar el que va passar, que és la part més instructiva: un canvi de propietari, un canvi de model de negoci, una migració massiva a una altra eina i un incident de seguretat que va deixar una lliçó duríssima sobre confiar secrets a un tercer. Veurem què va aportar, com funciona el seu model de fases fixes, el pipeline de Reservalia forçat a cabre-hi, què va passar, i sobretot com migrar un .travis.yml al que facis servir avui.
Contingut
- Què va aportar Travis i per què importa
- El model de fases fixes
.travis.ymlbàsic i matriu de versions- El pipeline de Reservalia forçat a cabre-hi
- Stages, memòries cau i secrets xifrats
- Què va passar: propietat, model de negoci i èxode
- L'incident del 2021 i la lliçó sobre secrets
- Migrar un
.travis.yml: taula d'equivalències - Migració completa, pas a pas
- La lliçó de portabilitat
- Quan Travis encara té sentit
- Errors Comuns i Consells
- Exercicis
- Conclusió
- Què va aportar Travis i per què importa
Travis CI va aparèixer el 2011, quan el CI normal era una instal·lació de Jenkins que algú mantenia i on configurar un projecte significava demanar un job a un administrador i omplir un formulari (06-01). Travis va proposar una altra cosa: un fitxer al teu repositori i prou.
# .travis.yml del 2013. Això era tot. I era revolucionari.
language: node_js
node_js:
- "0.10"
- "0.12"Amb aquestes quatre línies, cada push disparava una construcció, en dues versions de Node, en una màquina neta, sense haver demanat permís a ningú ni instal·lat res. Quatre innovacions concretes, totes avui invisibles perquè són l'estàndard:
| Aportació | Què va canviar | On viu avui |
|---|---|---|
| Configuració al repositori | El pipeline es ramifica i es revisa amb el codi | Jenkinsfile, .gitlab-ci.yml, config.yml, workflows: tots |
| Convenció sobre configuració | language: node_js implica instal·lar Node, executar npm ci i npm test sense dir-ho |
Les accions setup-*, els buildpacks, cimg/* |
| Matriu de versions | Provar en diverses versions era una llista, no N jobs copiats | matrix / parallel: matrix (02-04) |
| Gratuït i il·limitat per a codi obert | El CI va deixar de ser un privilegi d'empreses | Els plans gratuïts actuals, que existeixen perquè Travis els va normalitzar |
L'efecte cultural va ser enorme: durant anys, la insígnia verda de Travis al README va ser el senyal que un projecte de codi obert era seriós. Milers de projectes van adoptar proves automatitzades perquè el cost d'entrada va baixar a un fitxer. És difícil exagerar quanta cultura de CI actual surt d'aquí.
Aquest és el motiu d'estudiar Travis encara que no el facis servir: entendre el .travis.yml és entendre el model mental del qual tots els altres deriven, incloses les seves limitacions, que és el que va empènyer els successors a dissenyar-se diferent.
- El model de fases fixes
Aquí hi ha la diferència estructural amb tot el que hem vist. Jenkins, GitLab, CircleCI i GitHub Actions et deixen definir les etapes i les seves dependències. Travis no: té un cicle de vida de fases predefinides i tu omples les que necessitis.
flowchart TD
A["apt addons"] --> B["before_install"]
B --> C["install<br/>per defecte segons language"]
C --> D["before_script"]
D --> E["script<br/>el que decideix exit o fallada"]
E --> F{"resultat"}
F -->|"exit"| G["after_success"]
F -->|"fallada"| H["after_failure"]
G --> I["before_deploy"]
I --> J["deploy"]
J --> K["after_deploy"]
G --> L["after_script"]
H --> L
| Fase | Per a què | Nota important |
|---|---|---|
before_install |
Preparar el sistema (repositoris apt, eines) | Una fallada aquí és un error, no una fallada de build |
install |
Instal·lar dependències | Té valor per defecte segons language |
before_script |
Preparar l'entorn (arrencar base de dades, migrar) | Una fallada aquí també és error |
script |
El que decideix si el build passa | Una fallada aquí és una fallada de build. La distinció és subtil i confon |
after_success / after_failure |
Cobertura, notificacions | El seu resultat no afecta el build |
before_deploy / deploy / after_deploy |
Publicar i desplegar | deploy fa servir proveïdors predefinits |
after_script |
Neteja | Sempre s'executa |
Què es guanya i què es perd amb les fases fixes:
Es guanya simplicitat brutal. No cal decidir l'estructura del pipeline: l'estructura ja hi és i tu omples forats. Per al 90 % dels projectes de codi obert del 2013 —instal·la, prova, informa— era exactament el que calia, i el fitxer cabia en una pantalla. A més fa els fitxers comparables entre projectes: saps on mirar.
Es perd tot el que necessita un pipeline real de lliurament:
- No hi ha graf. No pots dir "publicar depèn de qualitat i de test però no de la documentació". L'ordre és el que hi ha.
- No hi ha jobs amb dependències arbitràries. Els
stagesvan arribar tard i són seqüencials, no un DAG. - No hi ha paral·lelització dins d'un job. El sharding de la 02-04 s'ha de simular amb entrades de matriu i variables d'entorn.
- El pas d'artefactes entre jobs no existeix de manera nativa. Cada job de la matriu és independent i arrenca net; per passar el
dist/d'un job a un altre cal pujar-lo a S3 o a un altre magatzem tu mateix. Aquest és el límit més sever: la promoció de l'artefacte únic de la 02-06 no es pot expressar en el model. - La condicionalitat és pobra.
if:existeix, però no hi ha res semblant arulesni a expressions riques.
La conclusió, que és la que fa didàctica aquesta lliçó: el model de fases fixes és excel·lent per a CI i estructuralment insuficient per a CD. I això explica per què els successors —GitLab amb stages i després needs, CircleCI amb workflows des del principi, GitHub Actions amb needs— van posar el graf al centre. No va ser moda: va ser la correcció d'una limitació concreta.
.travis.yml bàsic i matriu de versions
.travis.yml bàsic i matriu de versionslanguage: node_js
dist: jammy # 1 · imatge base d'Ubuntu
node_js: [ "20", "22" ] # 2 · matriu implícita: 2 jobs
services: [ postgresql ] # 3 · serveis preinstal·lats a la VM
cache:
npm: true # 4 · memòria cau coneguda pel proveïdor
directories: [ node_modules ]
before_install:
- psql -c 'CREATE DATABASE reservalia_test;' -U postgres
install:
- npm ci # substitueix l'install per defecte
script: # 5 · el que decideix el resultat
- npm run lint
- npm test
after_success:
- bash <(curl -s https://codecov.io/bash) # 6 · la seva fallada no trenca el build
notifications:
email: false
slack:
rooms:
secure: "AbCd...==" # 7 · valor xifrat amb la clau pública del repositoridisttria la imatge base (trusty,xenial,bionic,focal,jammy). És el més semblant aruns-on, i una font clàssica de builds trencats: projectes ancorats adist: trustyque van deixar de funcionar quan aquella imatge es va retirar.- La matriu implícita és la idea que més es va copiar: llistar valors de la clau del llenguatge genera un job per valor.
servicesactiva serveis ja instal·lats a la VM. És diferent delsservicesde GitLab o GitHub, que aixequen contenidors: aquí no tries la versió amb precisió, corres la que porta la imatge. Menys control, menys cerimònia.cache: npm: trueés memòria cau per convenció: el proveïdor sap què cal desar per a cada llenguatge. Còmode, i amb la contrapartida de sempre —desarnode_modulesen lloc del directori de descàrrega d'npm produeix estats corromputs quan canvia la versió de Node, exactament el problema d'invalidació de la 04-02—.scriptés la fase que decideix. Cada ordre de la llista s'executa i si una falla, les següents s'executen igualment; el build acaba fallant. És diferent de gairebé totes les eines modernes, on un step fallat talla el job, i és una font de confusió en llegir logs.after_successno afecta el resultat. Si la pujada de cobertura falla, el build continua verd. De vegades és el que vols i de vegades amaga que fa un mes que no puges cobertura.secure:és un valor xifrat amb la clau pública del repositori, generat ambtravis encrypt. Apartat 5.
Matriu explícita, amb el que la feia potent:
jobs: # abans es deia "matrix"
include:
- node_js: "22"
env: SUITE=integracio
services: [ postgresql, redis ]
- node_js: "22"
os: osx # macOS, un dels seus arguments històrics
- node_js: "22"
arch: arm64
exclude:
- node_js: "20"
os: osx
allow_failures: # jobs que poden fallar sense trencar el build
- node_js: "nightly"
fast_finish: true # informa del resultat sense esperar els allow_failuresallow_failures amb fast_finish és una bona idea que continua vigent: provar contra la versió en desenvolupament del llenguatge sense bloquejar ningú, i assabentar-te aviat que alguna cosa es trencarà. És l'equivalent de continue-on-error amb la matriu de la 02-04.
- El pipeline de Reservalia forçat a cabre-hi
Quarta traducció del mateix pipeline, i la primera en què cal explicar què no es pot expressar.
language: node_js
node_js: [ "22" ]
dist: jammy
os: linux
services: [ postgresql, docker ]
cache:
directories: [ $HOME/.npm ]
env:
global:
- IMATGE=reservalia/api
- ECR=123456789012.dkr.ecr.eu-west-1.amazonaws.com
stages: # 1 · seqüencials, sense graf
- verificar
- construir
- name: publicar
if: branch = main AND type = push # 2
jobs:
include:
# ---------------- verificar ----------------
- stage: verificar
name: "Qualitat"
install: npm ci --prefer-offline
script:
- npx prettier --check .
- npm run lint
- npm run typecheck
- stage: verificar # 3 · el sharding, a mà
name: "Tests 1/4"
env: SHARD=1
install: npm ci --prefer-offline
before_script:
- psql -c 'CREATE DATABASE reservalia_test;' -U postgres
- npm run migrate
script: npm test -- --shard=$SHARD/4
- stage: verificar
name: "Tests 2/4"
env: SHARD=2
install: npm ci --prefer-offline
before_script:
- psql -c 'CREATE DATABASE reservalia_test;' -U postgres
- npm run migrate
script: npm test -- --shard=$SHARD/4
# … i dues entrades més gairebé idèntiques per a 3/4 i 4/4
# ---------------- construir ----------------
- stage: construir
name: "Build imatge"
install: skip
script:
- docker build -f apps/api/Dockerfile -t $IMATGE:$TRAVIS_COMMIT .
- trivy image --severity HIGH,CRITICAL --exit-code 1 $IMATGE:$TRAVIS_COMMIT
after_success:
# 4 · perquè el stage següent vegi la imatge cal pujar-la JA
- echo "$ECR_PASSWORD" | docker login -u AWS --password-stdin $ECR
- docker tag $IMATGE:$TRAVIS_COMMIT $ECR/$IMATGE:$TRAVIS_COMMIT
- docker push $ECR/$IMATGE:$TRAVIS_COMMIT
# ---------------- publicar ----------------
- stage: publicar
name: "Promocionar per digest"
install: skip
script:
# 5 · el digest s'ha de tornar a consultar: no va viatjar des del stage anterior
- DIGEST=$(aws ecr describe-images --repository-name reservalia/api
--image-ids imageTag=$TRAVIS_COMMIT
--query 'imageDetails[0].imageDigest' --output text)
- ./scripts/promocionar.sh "$ECR/$IMATGE@$DIGEST"
deploy: # 6 · proveïdors predefinits
provider: s3
bucket: reservalia-web
local_dir: apps/web/dist
skip_cleanup: true
on: { branch: main }Què queda forçat, que és la part instructiva:
- Els
stagessón seqüencials, no un graf.construirno pot començar fins que acabin els cinc jobs deverificar, encara que només depengui de les fonts. És la barrera d'etapa de la 04-01 sense escapatòria: no existeixneeds. - La condicionalitat és limitada. El llenguatge d'
if:cobreix branca, tipus d'esdeveniment, tag i variables d'entorn, i poca cosa més. No hi ha filtres per ruta com els de la 02-07, així que l'execució selectiva en monorepo de la 04-04 no es pot expressar. - El sharding és copiar i enganxar quatre entrades. No hi ha
parallelism: 4ni matriu que generi shards amb una línia; cada partició és una entrada dejobs.includeamb el seuinstalli el seubefore_scriptrepetits. Quatre blocs gairebé idèntics: exactament la duplicació que la 04-05 va ensenyar a eliminar i que aquí no té solució dins del fitxer. - No hi ha pas d'artefactes entre jobs. El
dist/i la imatge no viatgen del stageconstruiral stagepublicar: cada job arrenca en una màquina neta. L'única sortida és pujar l'artefacte a un magatzem extern immediatament i tornar a descarregar-lo o a referenciar-lo després. Funciona, però converteix el magatzem extern en part obligatòria del pipeline i fa que la construcció i la publicació deixin de ser passos separables. - La conseqüència més greu: com que el digest no pot viatjar, cal tornar a consultar-lo per etiqueta. I això trenca una garantia que el curs defensa des de la 02-06: la promoció per digest exigeix que l'identificador que es promociona sigui exactament el que va produir el build. Consultar-lo per etiqueta introdueix una finestra —petita, però real— en què aquella etiqueta podria apuntar a una altra imatge. A Travis es pot mitigar (etiquetes úniques per commit, registres amb etiquetes immutables), no eliminar netament.
deployamb proveïdors és la part més còmoda de Travis: dotzenes de proveïdors (S3, Heroku, npm, PyPI, GitHub Releases) configurats amb quatre línies. Per publicar un paquet de codi obert continua sent de les coses més directes que existeixen. Per desplegar a ECS amb canari, no hi ha proveïdor i acabes escrivint l'script igualment.
Balanç honest: el pipeline hi cap, però perd el graf, perd el sharding declaratiu, perd l'execució selectiva i compromet la promoció per digest. No és que Travis estigui mal fet; és que es va dissenyar per a "construeix i prova un projecte de codi obert" i aquest pipeline és de lliurament continu.
- Stages, memòries cau i secrets xifrats
Els stages van arribar el 2017, tard, i són grups seqüencials de jobs paral·lels —el model original de GitLab, sense el needs posterior—. Van afegir una cosa important: if: a nivell de stage, i la possibilitat d'una fase de desplegament després de les proves.
Memòries cau: per convenció (cache: npm|bundler|pip) o per directoris explícits. Amb dos avisos que valen per a qualsevol eina. Primer, desar node_modules a la memòria cau és una mala idea davant de desar el directori de descàrrega: node_modules conté binaris compilats per a una versió concreta de Node i arquitectura, i restaurar-lo en un altre context produeix fallades críptiques (04-02). Segon, la invalidació de la memòria cau de Travis es feia esborrant-la des de la interfície o amb la CLI, no canviant una clau: no hi ha clau per checksum, així que la memòria cau pot quedar-se obsoleta indefinidament. És justament el control que CircleCI va fer explícit (06-03).
Secrets xifrats, la part més característica i la més rellevant per al que ve:
# Xifra un valor amb la clau pública del repositori i l'afegeix al .travis.yml
travis encrypt AWS_SECRET_ACCESS_KEY="AKIA..." --add env.global
# Xifra un fitxer sencer (una clau privada, un keystore)
travis encrypt-file claus/deploy.pem --addLa idea era elegant: el secret xifrat viu al repositori, així que el .travis.yml és autocontingut i funciona per a qualsevol que faci fork sense exposar res, perquè només Travis té la clau privada. Dues propietats de disseny que el feien raonablement segur: els valors xifrats no es desxifren en builds de pull requests des de forks —justament perquè un PR maliciós no els pogués llegir, que és el mateix raonament que pull_request_target de la 04-03—, i Travis emmascarava els valors als logs.
I en aquestes dues propietats hi ha la història de l'apartat 7.
- Què va passar: propietat, model de negoci i èxode
Els fets, en ordre, perquè el patró importa més que les dates:
Gener del 2019: canvi de propietari. Travis CI va ser adquirit per Idera. Poc després es va produir una sortida significativa de personal d'enginyeria, àmpliament comentada a la comunitat. Per als usuaris, l'efecte immediat va ser incertesa sobre el rumb del producte.
Finals del 2020: fi del model gratuït il·limitat per a codi obert. Es va introduir un sistema de crèdits amb una assignació limitada per a projectes oberts, renovable sota sol·licitud. La justificació era raonable —el cost del còmput és real i l'abús per mineria de criptomonedes era un problema real del sector—, però el canvi afectava la proposta de valor que havia definit Travis durant deu anys.
2019-2021: GitHub Actions. GitHub va llançar Actions al lloc on ja vivia el codi de la majoria dels projectes de codi obert, amb minuts gratuïts generosos per a repositoris públics i zero configuració d'integració. La combinació de tots dos factors va produir una migració massiva: milers de projectes van canviar el seu .travis.yml per .github/workflows/ci.yml en qüestió de mesos.
El que cal endur-se d'això no és una crítica a Travis ni a Idera, sinó tres observacions aplicables a qualsevol eina que triïs avui:
- El model de negoci del teu proveïdor de CI pot canviar, i no ho decideixes tu. Un pla gratuït, uns minuts inclosos o un nivell de preus poden canviar amb un preavís curt. Si la teva operació depèn críticament d'aquestes condicions, tens una exposició que convé reconèixer.
- La integració venç la qualitat tècnica. Actions no va guanyar per ser millor eina que Travis el 2020 —discutible en diversos aspectes—, sinó per ser on ja era el codi. És el criteri que la 06-07 posarà en primer lloc per pes real: on viu el codi i la identitat de l'equip decideix la majoria de les eleccions.
- El cost de sortida es paga quan ja no hi ha alternativa. Els projectes que tenien la lògica en scripts del repositori van migrar en una tarda; els que la tenien dispersa al YAML van trigar setmanes. Apartat 10.
- L'incident del 2021 i la lliçó sobre secrets
El setembre del 2021, Travis CI va publicar un avís de seguretat sobre una fallada que provocava que variables d'entorn segures —inclosos tokens i claus— quedessin exposades en builds de pull requests procedents de forks de repositoris públics. És a dir, es va trencar precisament la propietat de disseny que feia segur el model de secrets xifrats: que un fork no hi pot accedir. Qualsevol que obrís un PR contra un repositori públic afectat podia, amb un .travis.yml modificat, llegir secrets que mai no haurien d'haver estat al seu abast.
Se'l va identificar com a CVE-2021-41077, i la comunicació inicial va generar força crítiques pel seu abast i el seu detall, cosa que va portar a una publicació posterior més explícita. La recomanació operativa per a qualsevol projecte públic afectat va ser rotar totes les credencials.
Quatre lliçons concretes, que no són sobre Travis:
1. Un secret confiat a un tercer és un secret la seguretat del qual no controles. No és un argument contra els SaaS —la 06-03 esmentava l'incident de CircleCI del 2023, amb la mateixa conclusió de rotar-ho tot, i cap plataforma és immune—. És un argument a favor d'assumir que passarà i dissenyar en conseqüència.
2. Les credencials de llarga vida són el problema; les efímeres acoten el dany. Una clau d'accés permanent filtrada és vàlida fins que algú la revoca, i aquest "algú" se n'assabenta tard. Un token OIDC de quinze minuts limitat a un rol concret (03-02) és material robat que ja no serveix per a gairebé res. Aquesta és la raó pràctica —no teòrica— per la qual el curs insisteix tant en la identitat federada davant dels secrets emmagatzemats.
3. Cal saber rotar-ho tot, i haver-ho assajat. La pregunta útil és: si demà el teu proveïdor et diu "rota totes les teves credencials", quant trigues i què es trenca? Si la resposta és "no ho sé", això és un pla de recuperació pendent, exactament en el sentit de la 03-05. Un inventari de secrets amb el seu propietari i el seu procediment de rotació val molt el dia que fa falta.
4. Els forks són la frontera de confiança més delicada de qualsevol CI. El mateix problema, amb un mecanisme diferent, és el de pull_request_target de la 04-03 que la 06-06 tornarà a tractar. La regla és universal i independent d'eina: el codi d'un fork no pot accedir a secrets, i qualsevol flux que sembli donar-li accés cal examinar-lo amb lupa.
- Migrar un
.travis.yml: taula d'equivalències
.travis.yml: taula d'equivalènciesAquesta és la part que probablement facis servir de debò. La traducció és majoritàriament mecànica, amb dues o tres decisions de disseny.
| Travis CI | GitHub Actions | GitLab CI/CD |
|---|---|---|
language: node_js |
uses: actions/setup-node@v4 |
image: node:22 |
node_js: ["20","22"] |
strategy: matrix: node: [20, 22] |
parallel: matrix: NODE: ["20","22"] |
dist: jammy |
runs-on: ubuntu-22.04 |
image: / etiqueta de runner |
os: [linux, osx] |
runs-on: [ubuntu-latest, macos-latest] |
Runners amb tags |
services: [postgresql] |
services: amb imatge de contenidor |
services: amb imatge |
before_install |
Steps al principi del job | before_script |
install |
- run: npm ci |
before_script o script |
before_script |
Steps previs | before_script |
script |
- run: (cadascun talla en fallar) |
script: |
after_success |
- if: success() |
after_script o job amb when: on_success |
after_failure |
- if: failure() |
when: on_failure |
after_script |
- if: always() |
after_script |
deploy: proveïdor |
Action del proveïdor o run |
Job de desplegament amb environment |
cache: directories: |
actions/cache amb clau |
cache: key/paths |
env.global |
env: a nivell de workflow |
variables: |
secure: "..." |
Secrets del repositori | Variables protegides i emmascarades |
stages |
needs: entre jobs |
stages + needs |
allow_failures |
continue-on-error: true |
allow_failure: true |
fast_finish |
fail-fast (semàntica inversa) |
Comportament per defecte |
if: branch = main |
if: github.ref == 'refs/heads/main' |
rules: - if: $CI_COMMIT_BRANCH == ... |
TRAVIS_COMMIT |
github.sha |
$CI_COMMIT_SHA |
TRAVIS_PULL_REQUEST |
github.event_name == 'pull_request' |
$CI_PIPELINE_SOURCE |
TRAVIS_BRANCH |
github.ref_name |
$CI_COMMIT_BRANCH |
Les quatre decisions que no són mecàniques:
- La distinció entre fases que trenquen el build i fases que no desapareix. A Travis,
after_successno afecta el resultat; a les altres, un step que falla trenca el job llevat que hi posiscontinue-on-error. En migrar, decideix explícitament què ha de continuar sent no bloquejant. scriptamb diverses ordres no talla a la primera fallada; a les altres sí. Si el teu.travis.ymldepenia que totes s'executessin per recollir tots els errors d'una vegada, cal reproduir-ho a propòsit.- Els secrets
secure:no es migren: es roten. No es poden desxifrar sense la clau del repositori a Travis, i encara que es pogués, moure un secret de lloc és el moment perfecte per canviar-lo. I si el repositori és públic i va estar actiu el 2021, la rotació no és opcional (apartat 7). - Aprofita per introduir el graf. Una migració un a un reprodueix les esperes del model seqüencial. És el moment de posar
needsi guanyar temps de franc.
- Migració completa, pas a pas
Partim d'un .travis.yml realista, del tipus que et trobaràs:
# ABANS — .travis.yml heretat
language: node_js
node_js: [ "18", "20" ]
dist: focal
services: [ postgresql ]
cache:
directories: [ node_modules ]
env:
global:
- CI=true
- secure: "K3jd...==" # NPM_TOKEN xifrat
before_install:
- psql -c 'CREATE DATABASE app_test;' -U postgres
install:
- npm ci
before_script:
- npm run migrate
script:
- npm run lint
- npm test
after_success:
- bash <(curl -s https://codecov.io/bash)
deploy:
provider: npm
email: [email protected]
api_key: { secure: "9dLp...==" }
on: { tags: true }# DESPRÉS — .github/workflows/ci.yml
name: CI
on:
push: { branches: [main] }
pull_request:
release: { types: [published] } # 1 · substitueix `on: tags`
permissions:
contents: read # 2 · mínim privilegi (04-03)
concurrency: # 3 · no existia a Travis
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-22.04
strategy:
fail-fast: false # 4 · volem veure els dos resultats
matrix:
node: [18, 20]
services:
postgres: # 5 · versió explícita, no la de la imatge
image: postgres:16-alpine
env: { POSTGRES_PASSWORD: test, POSTGRES_DB: app_test }
options: >-
--health-cmd pg_isready --health-interval 5s --health-retries 10
ports: [ '5432:5432' ]
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/app_test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm # 6 · desa ~/.npm, NO node_modules
- run: npm ci
- run: npm run migrate
- run: npm run lint
- run: npm test -- --coverage
- uses: codecov/codecov-action@v4 # 7
if: always()
with: { token: '${{ secrets.CODECOV_TOKEN }}' }
publicar:
needs: [test] # 8 · graf explícit
if: github.event_name == 'release'
runs-on: ubuntu-22.04
permissions:
contents: read
id-token: write # 9 · publicació amb procedència
environment: npm-produccio # 10 · aprovació i secrets acotats
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, registry-url: 'https://registry.npmjs.org' }
- run: npm ci
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # 11 · token NOU, rotaton: tags→on: release: publicar en crear una release és més explícit i evita publicar per una etiqueta accidental.permissionsexplícits són un concepte que Travis no tenia; aprofita la migració per posar-los (04-03).concurrencycancel·la execucions obsoletes: no existia a Travis i és estalvi immediat.fail-fast: falsereprodueix el comportament habitual de la matriu de Travis, on veure els dos resultats era el normal.- PostgreSQL com a contenidor amb versió fixada i health check, en comptes de "el que porta la imatge": més reproduïble, i elimina la cursa d'arrencada que a Travis es resolia amb reintents.
- Canvi deliberat:
node_modulessurt de la memòria cau i hi entra~/.npm. Corregeix el problema de binaris de la 04-02 que el fitxer original arrossegava. - La pujada de cobertura amb
if: always()reprodueix la semàntica d'after_successsense trencar el build, però de manera explícita. needs: [test]és el guany estructural: ara hi ha graf.id-token: writeamb--provenanceafegeix procedència verificable al paquet (02-06 i 04-03): una cosa que al model de Travis no existia.environmentaporta aprovació i secrets acotats a la publicació.NPM_TOKENés un token nou. El desecure:es revoca. No es migra un secret: es rota.
Fases recomanades de la migració, amb el mateix criteri de la 05-04 —cada pas amb valor propi—:
| Fase | Què es fa | Quan passar a la següent |
|---|---|---|
| 1. Inventari | Què fa el .travis.yml, quins secrets fa servir, qui depèn de la insígnia |
Quan estigui escrit |
| 2. Pipeline paral·lel | El workflow nou conviu amb Travis, tots dos a cada PR | Quan coincideixin els resultats 2 setmanes |
| 3. Canvi de senyal | El check obligatori passa a ser el nou; Travis queda informatiu | Quan ningú miri ja el de Travis |
| 4. Retirada | Esborrar .travis.yml, desactivar el projecte, revocar tots els secrets, actualitzar la insígnia del README |
— |
Què no migrar: jobs que ningú mira, la matriu de versions ja sense suport —migrar node_js: "0.10" és feina llençada—, i les fases after_* que fallen en silenci des de fa mesos. Una migració és la millor ocasió per esborrar, i desaprofitar-la és carregar el deute a l'eina nova.
- La lliçó de portabilitat
De tot l'episodi Travis en surt una recomanació pràctica que la 06-07 desenvoluparà i que convé ja:
# Fràgil: la lògica viu al YAML de l'eina
script:
- npm ci
- npm run lint
- npx tsc --noEmit
- npm test -- --coverage --shard=$SHARD/4
- docker build -t $IMATGE:$TRAVIS_COMMIT -f apps/api/Dockerfile .
- trivy image --severity HIGH,CRITICAL --exit-code 1 $IMATGE:$TRAVIS_COMMIT#!/usr/bin/env bash
# scripts/verificar.sh — la lògica viu al repositori
set -euo pipefail
SHARD="${SHARD:-1}"; TOTAL="${TOTAL:-1}"
npm ci --prefer-offline
npm run lint
npx tsc --noEmit
npm test -- --coverage --shard="${SHARD}/${TOTAL}"# El YAML queda com a orquestrador prim, i migrar és reescriure 15 línies
script:
- ./scripts/verificar.shEls avantatges van més enllà de la migració, i per això val la pena encara que no canviïs mai d'eina: el desenvolupador pot executar exactament el mateix a la seva màquina —cosa que elimina la classe sencera de fallades "només passa en CI"—, l'script es prova i es revisa com a codi, i el YAML deixa de ser un lloc on amagar lògica de negoci, que era un dels antipatrons de la 04-01.
El límit honest: no tot es pot treure del YAML. Els disparadors, la matriu, els permisos, els entorns, la concurrència i la memòria cau són propis de cada eina i no s'abstreuen sense construir una capa d'indirecció que sol costar més del que estalvia. L'objectiu raonable no és la portabilitat total, és que el 80 % de l'esforç de migració sigui reescriure orquestració i no reconstruir lògica. Els projectes que van migrar de Travis en una tarda eren els que tenien un script: make ci.
- Quan Travis encara té sentit
Amb honestedat i sense nostàlgia:
- Un projecte que ja el té, funciona i no es toca. Un
.travis.ymlestable en un repositori de manteniment no és una emergència. Migrar costa hores que potser rendeixen més en un altre lloc. Amb dues condicions: revisa que els secrets continuen sent vàlids i necessaris, i que les imatgesdist:que fa servir continuen suportades. - Plataformes i arquitectures poc comunes. Travis va mantenir suport per a arquitectures com IBM Power, IBM Z o ARM64 abans i de manera més accessible que altres, i alguns projectes de sistemes el continuen fent servir per això. Si el teu problema és exactament aquest, continua sent una opció a considerar.
- Publicació senzilla de paquets de codi obert. El bloc
deploy:amb proveïdors predefinits continua sent de les coses més curtes que existeixen per publicar a npm, PyPI o GitHub Releases.
Quan no: qualsevol projecte nou, qualsevol pipeline amb lliurament continu real, qualsevol equip que ja sigui a GitHub o GitLab, i qualsevol cas on la promoció per digest o l'execució selectiva importin. No és una qüestió de gust: són limitacions estructurals del model de fases fixes, no defectes corregibles.
Errors Comuns i Consells
Migrar els secrets en comptes de rotar-los. Canviar d'eina és el moment ideal per rotar. I si el repositori és públic i va estar actiu el 2021, la rotació és obligatòria.
Migrar un a un sense introduir el graf. Reprodueix les esperes del model seqüencial i malbarata el millor guany de la migració: posar needs.
Copiar la memòria cau de node_modules a l'eina nova. Era una mala pràctica a Travis i ho continua sent. Desa el directori de descàrrega amb clau per lockfile.
Oblidar la insígnia del README. Queda apuntant a un servei inactiu i mostra un estat fals durant mesos. És cosmètic i és la primera cosa que veu qui arriba al projecte.
Suposar que script talla a la primera fallada. A Travis no ho fa; a les altres sí. Si l'ordre de les ordres amagava fallades, la migració les traurà totes de cop, i això és bo encara que incomodi el primer dia.
Deixar Travis actiu després de migrar. Consumeix crèdits, envia notificacions que ningú llegeix i manté vius uns secrets que ja no controla ningú. Desactivar el projecte i revocar credencials és part de la migració, no un extra.
Consell general: quan heretis un .travis.yml, llegeix-lo com a documentació arqueològica abans de traduir-lo. allow_failures et diu què portava trencat temps, la llista de dist i versions et diu quan es va tocar per última vegada, i after_success et diu quines integracions existien i potser ja no. Aquest context val més que la traducció mecànica i és exactament l'enfocament de la 05-04.
Exercicis
Exercici 1. Tradueix a GitHub Actions aquest .travis.yml, decidint explícitament quins steps han de ser bloquejants i quins no, i justifica cada decisió:
language: python
python: [ "3.10", "3.11" ]
dist: focal
services: [ redis ]
cache: pip
install:
- pip install -r requirements.txt -r requirements-dev.txt
before_script:
- flake8 --version
script:
- flake8 .
- mypy src/
- pytest --cov=src
after_success:
- coveralls
after_failure:
- cat logs/errors.log
deploy:
provider: pypi
user: __token__
password: { secure: "Ab3d...==" }
on: { tags: true, python: "3.11" }Exercici 2. Un repositori públic de la teva empresa va fer servir Travis entre el 2018 i el 2022 amb secure: per a un token de desplegament d'AWS amb permisos amplis, un token d'npm i una clau SSH xifrada amb encrypt-file. Ningú l'ha tocat des del 2022. Escriu el pla de resposta: què assumeixes, què fas primer, en quin ordre i com verifiques que has acabat.
Exercici 3. La Marta pregunta: "Quant ens costaria canviar de GitHub Actions a una altra eina si demà canvien les seves condicions?". Dissenya l'exercici d'avaluació sobre el ci.yml de Reservalia: com es mesura la portabilitat actual, quins canvis la millorarien, quant costen i fins on val la pena arribar.
Solucions
Solució 1.
name: CI
on:
push: { branches: [main] }
pull_request:
release: { types: [published] }
permissions: { contents: read }
concurrency: { group: ci-${{ github.ref }}, cancel-in-progress: true }
jobs:
verificar:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
python: ["3.10", "3.11"]
services:
redis:
image: redis:7-alpine
options: --health-cmd "redis-cli ping" --health-interval 5s --health-retries 10
ports: ['6379:6379']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: pip
cache-dependency-path: 'requirements*.txt'
- run: pip install -r requirements.txt -r requirements-dev.txt
- run: flake8 . # bloquejant
- run: mypy src/ # bloquejant
- run: pytest --cov=src --cov-report=xml # bloquejant
- name: Pujar cobertura
if: always() # NO bloquejant
continue-on-error: true
uses: coverallsapp/github-action@v2
- name: Diagnòstic en cas de fallada
if: failure() # equivalent a after_failure
run: cat logs/errors.log || true
publicar:
needs: [verificar]
if: github.event_name == 'release'
runs-on: ubuntu-22.04
permissions: { contents: read, id-token: write }
environment: pypi
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- run: pip install build && python -m build
- uses: pypa/gh-action-pypi-publish@release/v1 # publicació de confiança via OIDCDecisions i la seva justificació. flake8, mypy i pytest són bloquejants: són la porta de qualitat de la 02-05 i la seva fallada ha d'impedir la fusió. La pujada a Coveralls és no bloquejant amb continue-on-error perquè una fallada d'un servei extern no diu res sobre el codi —és exactament la semàntica d'after_success, però ara explícita en comptes d'implícita al nom de la fase—. L'abocament de log fa servir if: failure(), reproduint after_failure.
Dos canvis deliberats respecte de l'original. on: tags passa a on: release per no publicar per una etiqueta accidental. I el password: secure: desapareix: se substitueix per publicació de confiança via OIDC amb id-token: write, que elimina el token de llarga vida completament. És la millora que més redueix el risc de tota la migració, i és l'aplicació directa de la lliçó de l'apartat 7: la millor resposta a "els secrets emmagatzemats es poden filtrar" és no emmagatzemar secrets.
Una nota sobre cache: pip amb cache-dependency-path: sense aquella segona línia, la clau no inclou requirements-dev.txt i la memòria cau no s'invalida en canviar les dependències de desenvolupament. És el mateix error de clau incompleta de la 04-02.
Solució 2.
El que s'assumeix, i és la decisió més important: que els tres secrets estan compromesos. El repositori és públic, va estar actiu durant el 2021 i feia servir exactament el mecanisme afectat. No cal buscar evidència d'explotació per actuar: l'absència d'evidència en logs de fa anys no és evidència d'absència, i el cost de rotar és molt inferior al d'equivocar-se en l'altra direcció.
Ordre d'actuació, per impacte potencial:
- Token d'AWS amb permisos amplis (primer, avui). Desactivar la clau —desactivar abans que esborrar, per poder investigar—, revisar CloudTrail del període per si hi ha activitat des d'adreces o regions inesperades, i substituir-la. La substitució no és "una altra clau": és un rol assumit per OIDC des de l'eina actual (03-02), amb permisos mínims. Si hi ha recursos que només aquella clau podia tocar, verificar-ne la integritat.
- Token d'npm. Revocar, revisar les versions publicades del paquet per si n'hi ha alguna que l'equip no reconegui —una publicació maliciosa és el pitjor escenari, perquè el dany va als consumidors—, i reemplaçar per publicació de confiança via OIDC.
- Clau SSH d'
encrypt-file. Revocar la pública a tots els destins on estigui autoritzada (servidors,deploy keysde repositoris), generar-ne una de nova, i comprovar elsauthorized_keysdels servidors accessibles per si hi ha claus afegides que ningú reconegui. - Neteja del repositori. Esborrar
.travis.yml, el fitxer.encde la clau xifrada i la insígnia. Compte: esborrar no elimina l'historial, i el fitxer xifrat continua sent accessible; per això la revocació és la mesura real i l'esborrat és només higiene. - Desactivar el projecte a Travis perquè no hi quedin credencials emmagatzemades.
Com es verifica que has acabat: inventari escrit dels tres secrets amb el seu estat —revocat, substituït, verificat—, comprovació que cap sistema en producció no en depenia (si alguna cosa es trenca en revocar, apareix aquí, que és per això que es fa en horari laborable i no un divendres), i una nota al registre de decisions amb la data i el motiu. I una acció de fons que evita el pròxim episodi: crear l'inventari de secrets de tota l'organització amb propietari i procediment de rotació, perquè aquest exercici ha demostrat que no existia.
Solució 3. L'exercici es fa en tres passos i produeix un número, no una opinió.
Pas 1: classificar cada línia del ci.yml en tres categories.
| Categoria | Què és | Cost de migrar | Exemples a Reservalia |
|---|---|---|---|
| Portable | Lògica que ja és en scripts o ordres estàndard | Zero | npm ci, npm test, docker build, ./scripts/desplegar.sh |
| Orquestració | Estructura del pipeline: jobs, needs, matriu, disparadors, memòria cau |
Reescriptura mecànica | on:, needs:, strategy: matrix, actions/cache |
| Lligat | Depèn de l'eina o del seu ecosistema | Reconstrucció real | actions/* de tercers, environment: amb revisors, OIDC cap a AWS, preparar-node, reusable-build-publicar.yml, GITHUB_TOKEN |
Pas 2: mesurar. Comptar línies de cada categoria als cinc workflows i estimar hores. Un resultat típic per a un repositori com Reservalia: l'orquestració es reescriu en 2-4 dies; el que és lligat és el car —cada acció de tercers cal substituir-la o reimplementar-la, la federació OIDC es refà contra el nou emissor, i les composite actions i els reusable workflows es tradueixen al mecanisme equivalent—. Si el 40 % del pipeline és a la categoria "lligat", la migració és de setmanes.
Pas 3: millores i el seu cost.
| Millora | Cost | Efecte en portabilitat | Val la pena? |
|---|---|---|---|
Moure la lògica dels run llargs a scripts/*.sh i npm scripts |
2-3 dies | Alt: converteix "lligat" en "portable" | Sí, i es paga sola encara que no migris mai: executable en local, revisable, provable |
| Substituir accions de tercers per ordres de la CLI equivalent | 1-2 dies | Mitjà-alt | Sí on l'acció només embolcalla una CLI; no on aporta lògica real |
Definir un make ci, make test, make build com a interfície única |
1 dia | Alt | Sí: dóna un contracte estable independent de l'eina |
| Abstreure disparadors i entorns rere una capa pròpia | Setmanes | Baix | No: costa més que la migració que evita |
Fins on val la pena arribar, que és la pregunta real de la Marta: fins al punt en què la lògica sigui portable i l'orquestració sigui prima, i ni un pas més. És a dir, els tres primers punts sí; el quart no. La raó és de cost esperat: la probabilitat de migrar en els pròxims dos anys és baixa, però els tres primers punts tenen benefici immediat amb independència de la migració —reproductibilitat local, pipeline més llegible, lògica revisable i provable, menys "verd fals" per diferències entre local i CI—, mentre que el quart només rendeix en un escenari improbable.
La resposta curta per a la Marta: "Avui, unes tres o quatre setmanes. Amb dos o tres dies de feina que a més ens van bé per altres motius, ho baixem a una setmana. I no intentarem baixar-ho a zero, perquè això costa més que la migració que evitaria."
Conclusió
Travis CI va inventar la manera com avui s'escriu CI —un fitxer al repositori, convenció sobre configuració, matriu de versions— i va demostrar que el CI podia ser un dret de qualsevol projecte i no un privilegi de qui tingués un administrador de Jenkins. El seu model de fases fixes era perfecte per a "construeix i prova" i estructuralment insuficient per al lliurament continu: sense graf, sense pas d'artefactes entre jobs, sense execució selectiva i amb la promoció per digest compromesa. Traduir el pipeline de Reservalia a Travis ha estat l'exercici més útil del mòdul fins ara, no perquè el resultat serveixi, sinó perquè el que no hi cap explica per què els successors es van dissenyar amb el graf al centre.
I les tres lliçons que sobreviuen a l'eina: el model de negoci del teu proveïdor pot canviar i no ho decideixes tu; un secret confiat a un tercer és un secret la seguretat del qual no controles, d'on se segueix que les credencials efímeres federades no són una moda sinó una reducció real de dany; i el cost de canviar d'eina es decideix anys abans de canviar-la, segons quanta lògica vas deixar dins del YAML.
La lliçó següent canvia de categoria. Docker i Kubernetes no són eines de CI: són el substrat sobre el qual corren els pipelines moderns i el destí on despleguen. Veurem Docker en els seus dos papers —entorn d'execució del pipeline i format de l'artefacte que es desplega—, baixarem al que el curs no havia cobert (BuildKit, memòries cau de capes al registre, construcció multiplataforma, imatges mínimes, construcció sense dimoni privilegiat), i després a Kubernetes com a destí: els objectes mínims, les sondes, el rollback amb kubectl rollout undo, Helm davant de Kustomize, i per què a Kubernetes triomfa GitOps i què canvia respecte del cd.yml de push que fa servir Reservalia.
Curs de CI/CD: Integració i Desplegament Continu
Mòdul 1: Introducció al CI/CD
- Conceptes Bàsics de CI/CD
- Beneficis del CI/CD
- Eines Populars de CI/CD
- El Projecte del Curs: l'Aplicació que Automatitzarem
- Mètriques DORA: Com es Mesura el Lliurament de Programari
Mòdul 2: Integració Contínua (CI)
- Introducció a la Integració Contínua
- Configuració d'un Entorn de CI
- Automatització de la Construcció
- Proves Automatitzades
- Qualitat de Codi i Anàlisi Estàtica
- Artefactes, Versionat i Promoció
- Integració amb el Control de Versions
Mòdul 3: Desplegament Continu (CD)
- Introducció al Desplegament Continu
- Automatització del Desplegament
- Infraestructura com a Codi i Entorns Reproduïbles
- Estratègies de Desplegament
- Feature Flags, Rollback i Recuperació davant Errors
- Monitoratge i Retroalimentació
Mòdul 4: Pràctiques Avançades de CI/CD
- Pipelines de CI/CD
- Gestió de Dependències
- Seguretat en CI/CD
- Escalabilitat i Rendiment
- Pipeline as Code: Plantilles, Reutilització i Proves del Pipeline
- Bases de Dades al Pipeline: Migracions Segures
Mòdul 5: Implementació de CI/CD en Projectes Reals
- Cas d'Estudi: Projecte Web
- Cas d'Estudi: Aplicació Mòbil
- Cas d'Estudi: Microserveis
- Cas d'Estudi: Modernitzar un Projecte Legacy
Mòdul 6: Eines i Tecnologies
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker i Kubernetes
- GitHub Actions a Fons
- Comparativa i Criteris per Triar Eina
Mòdul 7: Exercicis Pràctics
- Exercici 1: Configuració d'un Pipeline Bàsic
- Exercici 2: Integració de Proves Automatitzades
- Exercici 3: Desplegament en un Entorn de Producció
- Exercici 4: Monitoratge i Retroalimentació
- Exercici 5: Enfortir el Pipeline amb Seguretat i Secrets
- Projecte Final: Pipeline Complet d'Extrem a Extrem
