Jenkins competeix per control i GitLab per amplitud. CircleCI competeix per una sola cosa: que el pipeline sigui ràpid. És un SaaS especialitzat —no allotja el teu codi, no gestiona les teves incidències, no és el teu registre de contenidors— que assumeix que el repositori viu en un altre lloc i es concentra a executar treballs de pressa i amb bones eines per saber per què no ho fan. Aquesta especialització es nota en tres peces que cap altra eina del mòdul no té tan afinades: una memòria cau explícita on tu decideixes la clau, quan es desa i com es degrada; els orbs, que són paquets de configuració versionats i publicables; i el repartiment de proves per temps històrics, que és la resposta més madura al problema de sharding que arrosseguem des de la 02-04 i que la 04-04 només va poder resoldre a mà. En aquesta lliçó traduïm el pipeline de Reservalia per tercera vegada, distingim amb precisió espai de treball, memòria cau i artefactes —tres conceptes que aquí són tres mecanismes diferents i explícits—, i avaluem el preu d'aquesta velocitat: dependència d'un SaaS, un model de crèdit que cal entendre abans de signar, i un ecosistema més petit.
Contingut
- La proposta: un SaaS especialitzat en velocitat
- Model d'execució: jobs, workflows i executors
- El pipeline de Reservalia en
.circleci/config.yml - Memòria cau explícita: claus,
restore-keysi degradació - Espai de treball, memòria cau i artefactes: tres coses diferents
- Paral·lelització i test splitting per temps històrics
- Reutilització:
commands,parametersi orbs - Contexts, secrets i aprovacions
- Recursos, classes de màquina i el model de crèdits
- Depurar: SSH into build i execució local
- Límits i contrapartides
- Quan triar CircleCI
- Errors Comuns i Consells
- Exercicis
- Conclusió
- La proposta: un SaaS especialitzat en velocitat
CircleCI va néixer el 2011, a la mateixa onada que Travis, amb una diferència d'enfocament que va resultar decisiva: mentre Travis apostava per la simplicitat extrema per a projectes de codi obert, CircleCI va apostar per equips que paguen i pel rendiment del pipeline com a producte. D'aquí ve la seva forma actual.
El que implica l'especialització, per a bé i per a mal:
| Conseqüència | |
|---|---|
| No allotja el codi | Es connecta a GitHub, GitLab o Bitbucket. La teva identitat i els teus permisos continuen vivint allà, i CircleCI demana accés al repositori |
| No és un registre ni un gestor d'incidències | Publica a ECR, Docker Hub o on vulguis: res regalat, però res que t'ancori |
| Tota la inversió és a l'execució | Classes de recurs fines, arrencada ràpida, memòria cau controlable, repartiment per temps, bones vistes d'on se'n va el temps |
| Si desapareix la connexió amb el SaaS, no hi ha CI | El pla de control és seu, fins i tot amb runners propis |
I una virtut cultural que convé assenyalar: CircleCI ha estat durant anys de les eines que millor exposen on se'n va el temps —temps per job, per step, cues, taxes d'encert de memòria cau, distribució de durades de test—. Això importa perquè la 04-04 va establir que optimitzar sense mesurar és endevinar; aquí el mesurament ve de sèrie.
- Model d'execució: jobs, workflows i executors
El fitxer és únic: .circleci/config.yml, versionat amb el codi. La seva estructura té tres nivells.
flowchart TD
W["workflow<br/>orquestra i ordena"] --> J1["job: preparar"]
J1 --> J2["job: qualitat"]
J1 --> J3["job: test<br/>parallelism: 4"]
J1 --> J4["job: build"]
J2 --> J5["job: publicar"]
J3 --> J5
J4 --> J5
J5 --> A{"hold<br/>type: approval"}
A --> J6["job: desplegar-produccio"]
subgraph EX["executor: on corre cada job"]
E1["docker"]
E2["machine"]
E3["macos"]
E4["arm"]
end
| CircleCI | Equivalent al curs | Matís |
|---|---|---|
| Workflow | El graf de jobs | Pot haver-n'hi diversos en un mateix fitxer, amb disparadors diferents |
| Job | Job | Unitat que corre en un executor; retorna èxit o fallada |
| Step | Step | run, checkout, save_cache, store_artifacts… |
| Executor | Tipus de runner | docker, machine, macos, windows, i variants ARM/GPU |
| Command | Composite action | Seqüència de steps reutilitzable, amb paràmetres |
| Orb | Paquet de plantilles | Publicat i versionat en un registre públic o privat |
| Context | Grup de secrets compartits | Àmbit d'organització, no de projecte |
| Workspace | Pas de fitxers entre jobs | Diferent de memòria cau i d'artefactes |
Dos trets característics del model. Primer, el graf és explícit des del principi: no hi ha concepte d'"etapa" que actuï com a barrera, només requires. CircleCI mai no va haver d'evolucionar de fases a graf perquè va néixer amb graf. Segon, els executors són de primera classe: es declaren a dalt, amb nom, i els jobs els referencien; canviar tota la flota de docker a machine és editar un bloc.
- El pipeline de Reservalia en
.circleci/config.yml
.circleci/config.ymlTercera traducció del mateix pipeline.
version: 2.1
orbs: # 1
aws-cli: circleci/[email protected]
node: circleci/[email protected]
executors: # 2
node-base:
docker:
- image: cimg/node:22.11 # imatges "convenience" preparades per CircleCI
auth: { username: $DOCKERHUB_USER, password: $DOCKERHUB_TOKEN }
- image: cimg/postgres:16.2 # 3 · contenidors secundaris del mateix job
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: test
POSTGRES_DB: reservalia_test
resource_class: medium # 4
working_directory: ~/reservalia
docker-builder:
machine:
image: ubuntu-2404:current # VM completa: hi ha dimoni Docker de debò
docker_layer_caching: true # 5
commands: # 6
preparar-node:
description: Checkout, restauració de memòria cau i instal·lació reproduïble
parameters:
profunditat:
type: integer
default: 1
steps:
- checkout
- restore_cache: # 7
keys:
- npm-v2-{{ checksum "package-lock.json" }}
- npm-v2- # fallback parcial
- run:
name: Instal·lar dependències
command: npm ci --prefer-offline --no-audit
jobs:
preparar:
executor: node-base
steps:
- preparar-node
- save_cache: # 8 · només aquest job escriu la memòria cau
key: npm-v2-{{ checksum "package-lock.json" }}
paths: [ ~/.npm ]
- persist_to_workspace: # 9
root: ~/reservalia
paths: [ ".", "!node_modules" ]
qualitat:
executor: node-base
steps:
- attach_workspace: { at: ~/reservalia }
- run: npx prettier --check .
- run: npm run lint
- run: npm run typecheck
test:
executor: node-base
parallelism: 4 # 10
steps:
- attach_workspace: { at: ~/reservalia }
- run:
name: Esperar PostgreSQL
command: dockerize -wait tcp://localhost:5432 -timeout 1m
- run: npm run migrate
- run:
name: Executar la partició de tests corresponent
command: |
FITXERS=$(circleci tests glob "apps/**/*.test.ts" \
| circleci tests split --split-by=timings) # 11 · repartiment per temps reals
npm test -- --runTestsByPath $FITXERS \
--reporters=default --reporters=jest-junit
- store_test_results: { path: informes } # 12 · alimenta l'històric de temps
- store_artifacts: { path: cobertura } # 13
build:
executor: docker-builder
steps:
- attach_workspace: { at: ~/reservalia }
- run:
name: Construir imatge
command: |
docker buildx build \
--file apps/api/Dockerfile \
--cache-from type=registry,ref=$ECR/$IMATGE:cache \
--cache-to type=registry,ref=$ECR/$IMATGE:cache,mode=max \
--tag $IMATGE:$CIRCLE_SHA1 --load .
- run:
name: Escanejar imatge
command: trivy image --severity HIGH,CRITICAL --exit-code 1 $IMATGE:$CIRCLE_SHA1
publicar:
executor: docker-builder
steps:
- attach_workspace: { at: ~/reservalia }
- aws-cli/setup: # 14 · OIDC, sense claus de llarga vida
role_arn: $AWS_ROLE_ARN
region: eu-west-1
- run:
name: Publicar a ECR per digest
command: |
aws ecr get-login-password --region eu-west-1 \
| docker login --username AWS --password-stdin "$ECR"
docker tag $IMATGE:$CIRCLE_SHA1 $ECR/$IMATGE:$CIRCLE_SHA1
docker push $ECR/$IMATGE:$CIRCLE_SHA1
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $ECR/$IMATGE:$CIRCLE_SHA1)
echo "$DIGEST" | tee digest.txt
- store_artifacts: { path: digest.txt }
desplegar:
executor: node-base
parameters:
entorn: { type: enum, enum: [staging, produccio] } # 15
steps:
- attach_workspace: { at: ~/reservalia }
- aws-cli/setup: { role_arn: $AWS_ROLE_ARN, region: eu-west-1 }
- run: ./scripts/desplegar.sh << parameters.entorn >>
workflows:
ci:
jobs:
- preparar
- qualitat: { requires: [preparar] } # 16
- test: { requires: [preparar] }
- build: { requires: [preparar] }
- publicar:
requires: [qualitat, test, build]
context: [aws-ci] # 17
filters: { branches: { only: main } }
- desplegar:
name: desplegar-staging
entorn: staging
requires: [publicar]
context: [aws-staging]
filters: { branches: { only: main } }
- aprovar-produccio: # 18
type: approval
requires: [desplegar-staging]
filters: { branches: { only: main } }
- desplegar:
name: desplegar-produccio
entorn: produccio
requires: [aprovar-produccio]
context: [aws-produccio]
filters: { branches: { only: main } }- Els orbs s'importen a dalt i s'ancoren a versió.
circleci/[email protected]és un paquet publicat; el seu contingut són commands, jobs i executors ja escrits. Ancorar a versió exacta és aquí la mateixa regla que fixar accions per SHA de la 04-03: un orb és codi de tercers que corre amb els teus secrets. - Els executors amb nom eviten repetir la configuració d'entorn a cada job. És el tret que deixa el fitxer més net.
- Els contenidors secundaris són l'equivalent de
servicesa GitLab o GitHub: comparteixen xarxa amb el primari, així que PostgreSQL és alocalhost:5432. Detall important: CircleCI no espera que estiguin llestos, d'aquí el pas ambdockerize -wait. És una causa habitual de proves flaky en començar. resource_classtria la mida de la màquina (small,medium,large,xlarge…). És la palanca directa de la 04-04: pujar de classe escurça el job i multiplica el consum de crèdits per minut. Es decideix mesurant, no per intuïció.docker_layer_cachingconserva les capes Docker entre execucions a l'executormachine. Accelera molt els builds d'imatge i consumeix crèdits addicionals; amb una memòria cau de capes al registre (--cache-from, 06-05) de vegades no compensa. Cal mesurar les dues opcions.commandsés la composite action de la 04-05: passos reutilitzables amb paràmetres, dins del mateix fitxer o publicats en un orb.restore_cacheamb llista de claus: prova la primera; si no existeix, la segona com a prefix. És elrestore-keysde la 04-02, i aquí és completament explícit.save_cachenomés en un job. Les claus de memòria cau a CircleCI són immutables: un cop escrita una clau, no se sobreescriu. Això obliga a versionar la clau (npm-v2-) quan la vols invalidar, i evita el problema de diversos jobs sobreescrivint-se.persist_to_workspacedesa fitxers per als jobs següents d'aquest mateix workflow. És el que a Jenkins erastashi a GitHub Actions es fa amb artefactes.parallelism: 4llança quatre contenidors idèntics del job, ambCIRCLE_NODE_INDEXiCIRCLE_NODE_TOTAL.- La peça estrella:
circleci tests split --split-by=timingsreparteix els fitxers entre els quatre contenidors fent servir els temps reals d'execucions anteriors, no el nombre de fitxers. Apartat 6. store_test_resultsno és cosmètic: és el que alimenta l'històric de temps que fa servir el repartiment anterior, a més de la vista de tests fallats i flaky. Sense ell,--split-by=timingsdegrada a repartiment per nom.store_artifactspuja fitxers descarregables des de la interfície, que és un concepte diferent de l'espai de treball (apartat 5).- Identitat federada per OIDC: CircleCI emet un token per job i AWS el canvia per credencials temporals, exactament el disseny de la 03-02. La condició de confiança a AWS es lliga al projecte i opcionalment al context.
- Jobs parametritzats invocats diverses vegades amb
namediferent: un sol jobdesplegarserveix per a staging i producció, amb contexts diferents. És reutilització sense duplicar definició. requiresés tot el mecanisme d'ordre. No hi ha etapes.contextaporta els secrets, i s'assigna per invocació al workflow, no dins del job. Aquesta separació és una de les millors idees de CircleCI: el mateix job corre amb credencials de staging o de producció segons qui l'invoqui.type: approvalés un job especial sense steps: un botó. La seva semàntica és la porta de la 03-01; qui el pot prémer es controla amb les restriccions del context i del projecte.
- Memòria cau explícita: claus,
restore-keys i degradació
restore-keys i degradacióDavant de la memòria cau "automàtica" de setup-node a GitHub Actions o del cache: declaratiu de GitLab, aquí tot és manual. Es paga en verbositat i es guanya en control.
- restore_cache:
keys:
# 1 · coincidència exacta: mateix lockfile, mateixa versió de Node, mateix SO
- deps-v3-{{ arch }}-{{ checksum "package-lock.json" }}
# 2 · fallback: qualsevol memòria cau d'aquesta arquitectura, encara que el lockfile difereixi
- deps-v3-{{ arch }}-
# 3 · últim recurs
- deps-v3-
- run: npm ci --prefer-offline
- save_cache:
key: deps-v3-{{ arch }}-{{ checksum "package-lock.json" }}
paths: [ ~/.npm ]
when: on_success # no desis una memòria cau generada per un build trencatTres regles que eviten les fallades habituals:
- La clau ha d'incloure tot allò que invalida la memòria cau: el lockfile, l'arquitectura i, si tens diverses versions de runtime, la versió. Una memòria cau de Node 20 restaurada en un job de Node 22 produeix errors de mòduls natius que costen hores de diagnòstic.
- El fallback parcial és el que fa útil la memòria cau. Sense ell, qualsevol canvi al lockfile obliga a descarregar-ho tot des de zero. Amb ell, es restaura la memòria cau anterior i
npm cinomés baixa el que ha canviat. És el mecanisme delrestore-keysde la 04-02. - El prefix versionat (
v3) és el botó d'invalidar. Com que les claus són immutables, l'única manera de forçar una memòria cau neta quan es corromp és pujar el número. Tenir-lo des del principi estalvia un mal moment.
I un advertiment de seguretat que aplica a totes les eines: la memòria cau és un canal d'escriptura entre branques. Un PR d'un col·laborador que executi save_cache pot enverinar una memòria cau que després restaura un build de main. Posa en memòria cau només artefactes derivats de dependències verificades pel lockfile, mai binaris construïts que després s'executen amb privilegis (04-03).
- Espai de treball, memòria cau i artefactes: tres coses diferents
CircleCI és l'eina que fa més explícita aquesta distinció, i per això és el millor lloc per fixar-la.
| Memòria cau | Espai de treball | Artefactes | |
|---|---|---|---|
| Per a què | Accelerar | Passar fitxers entre jobs del mateix workflow | Desar sortides per a persones o sistemes externs |
| Abast | Entre workflows, entre branques | Un workflow | Persistent, descarregable des de la interfície |
| Com | save_cache / restore_cache |
persist_to_workspace / attach_workspace |
store_artifacts |
| Clau | Explícita, immutable | Rutes; s'acumula per job | Ruta i nom |
| Si desapareix | Més lent | El pipeline falla | Es perd l'evidència |
| Contingut típic | ~/.npm, ~/.gradle |
dist/, fonts compilades, digest.txt |
Informes de cobertura, captures de Playwright, SBOM |
L'error clàssic: fer servir memòria cau per passar el dist/ entre build i deploy. Funciona gairebé sempre i falla el dia que la memòria cau no hi és o quan porta la d'una altra branca, i llavors es desplega un build antic sense que res avisi. Un desplegament amb un artefacte equivocat és pitjor que una fallada, perquè no es detecta. Regla, la mateixa de la 06-02: si perdre-ho trenca la correcció del resultat, no és memòria cau.
Detall pràctic de l'espai de treball: s'acumula. Si build persisteix dist/ i publicar persisteix digest.txt, un job posterior que faci attach_workspace veu tots dos. I com que cada persist_to_workspace afegeix, convé persistir el mínim: persistir node_modules sencer és lent i gairebé mai necessari si la memòria cau està ben posada.
- Paral·lelització i test splitting per temps històrics
Aquest és l'argument tècnic més fort de CircleCI i la solució més madura al problema que la 02-04 va plantejar i la 04-04 va deixar a mitges.
El sharding ingenu reparteix per nombre de fitxers. Amb els tests de Reservalia:
| Contenidor | Fitxers | Temps real |
|---|---|---|
| 1 | 34 | 2 min 10 s |
| 2 | 34 | 1 min 40 s |
| 3 | 34 | 7 min 50 s ← porta els tests d'integració amb base de dades |
| 4 | 34 | 2 min 05 s |
El job triga 7:50, no 3:30. Es paga per quatre contenidors i se n'aprofita un. Amb --split-by=timings, CircleCI fa servir els temps per fitxer d'execucions anteriors —que coneix perquè store_test_results els puja— i reparteix per igualar durada:
| Contenidor | Fitxers | Temps real |
|---|---|---|
| 1 | 51 | 3 min 25 s |
| 2 | 44 | 3 min 30 s |
| 3 | 12 | 3 min 20 s |
| 4 | 29 | 3 min 35 s |
- run:
command: |
# glob → llista de fitxers; split → només els que toquen a aquest contenidor
FITXERS=$(circleci tests glob "apps/**/*.test.ts" | circleci tests split --split-by=timings)
npm test -- --runTestsByPath $FITXERS --reporters=jest-junit
- store_test_results: { path: informes } # imprescindible: sense això no hi ha històricMatisos que cal conèixer:
- La primera execució no té dades i reparteix per nom de fitxer; a partir de la segona, millora. Si afegeixes molts tests nous de cop, el repartiment triga una o dues execucions a reequilibrar-se.
- Existeixen altres estratègies:
--split-by=filesize(útil sense històric) i--timings-type=classnameper a llenguatges on la unitat no és el fitxer. - No arregla un test lent: reparteix millor, però un únic test de cinc minuts continua sent el terra del job. La 04-04 ja avisava que paral·lelitzar no substitueix arreglar.
- Interacció amb les proves flaky: una prova inestable falla en un contenidor i tomba el job sencer; el repartiment no canvia això. La quarantena de la 02-04 continua sent necessària.
Comparat amb les altres eines: a GitHub Actions i GitLab, el repartiment equilibrat per temps s'ha de construir (desant temps com a artefacte i escrivint el repartiment, o amb eines de tercers). Aquí és una ordre. És una diferència de dies de feina, i és real.
- Reutilització:
commands, parameters i orbs
commands, parameters i orbsTres nivells, del més local al més compartit:
# 1. Command: passos reutilitzables dins del projecte (≈ composite action)
commands:
notificar:
parameters:
canal: { type: string, default: "#reservalia-ci" }
estat: { type: enum, enum: [ok, fallada] }
steps:
- run:
when: always
command: ./scripts/notificar.sh "<< parameters.canal >>" "<< parameters.estat >>"
# 2. Job parametritzat: el mateix job invocat amb dades diferents
jobs:
desplegar:
parameters:
entorn: { type: enum, enum: [staging, produccio] }
espera: { type: integer, default: 60 }
steps:
- run: ./scripts/desplegar.sh << parameters.entorn >> << parameters.espera >>
# 3. Orb: paquet versionat, publicat i reutilitzable entre projectes
orbs:
reservalia: reservalia/[email protected]
workflows:
ci:
jobs:
- reservalia/build-publicar:
dockerfile: apps/api/Dockerfile
repositori: reservalia/apiUn orb és un paquet amb tres tipus de contingut —commands, jobs i executors— publicat amb versionat semàntic. Per publicar el teu:
# src/@orb.yml de l'orb reservalia/plataforma
version: 2.1
description: Peces comunes dels pipelines de Reservalia
commands:
preparar-node:
parameters:
version: { type: string, default: "22.11" }
steps:
- checkout
- restore_cache: { keys: ["npm-v2-{{ checksum \"package-lock.json\" }}", "npm-v2-"] }
- run: npm ci --prefer-offline# Publicació: primer una versió de desenvolupament, després la promoció semàntica
circleci orb pack src/ > orb.yml
circleci orb validate orb.yml
circleci orb publish orb.yml reservalia/plataforma@dev:proves # mutable, per provar
circleci orb publish promote reservalia/plataforma@dev:proves patch # immutable: 2.3.1Això és exactament la idea de la 04-05 —repositori central de plantilles— però amb dos avantatges de disseny: el versionat semàntic és obligatori i les versions publicades són immutables, així que el problema de "algú ha canviat main de la llibreria i ha trencat tothom" que vam veure a Jenkins i als include de GitLab no pot passar per accident. La contrapartida continua sent la mateixa: un orb públic de tercers és codi que corre amb els teus secrets, així que les regles de la 04-03 s'apliquen igual —revisar què fa, ancorar a versió exacta, preferir orbs certificats o propis per al que toca credencials—.
Els orbs poden ser públics (catàleg obert, codi visible) o privats dins de la teva organització, que és el que faries servir per a reservalia/plataforma.
- Contexts, secrets i aprovacions
Hi ha dos llocs on viuen els secrets, i la diferència importa:
| Variables de projecte | Context | |
|---|---|---|
| Àmbit | Un projecte | Tota l'organització |
| S'assigna | Sempre, a tots els jobs del projecte | Per invocació de job al workflow |
| Restricció d'accés | Per projecte | Per grup de seguretat: només certs equips |
| Ús típic | Configuració del propi projecte | Credencials compartides i, sobretot, les de producció |
workflows:
cd:
jobs:
- desplegar:
name: desplegar-produccio
context: [aws-produccio, notificacions] # només aquest job les veu
requires: [aprovar-produccio]La pràctica correcta: les credencials de producció viuen en un context restringit a un grup de seguretat, i només s'adjunten al job de desplegament a producció. Cap job de test no les té, així que un PR no les pot exfiltrar encara que modifiqui el config.yml —sempre que el context estigui restringit, que és el que cal verificar—. És el mateix raonament que les variables protegides de la 06-02 i el mínim privilegi de la 04-03, amb un mecanisme diferent.
Sobre les aprovacions: type: approval és un job buit que bloqueja el graf fins que algú prem. És més simple que els Environments de GitHub o els entorns protegits de GitLab: no hi ha historial de desplegaments per entorn ni rollback des de la interfície, perquè CircleCI no modela l'entorn com a objecte. Si vols saber quina versió hi ha en producció, t'ho construeixes tu. És la contrapartida directa de no ser una plataforma integrada.
- Recursos, classes de màquina i el model de crèdits
jobs:
test:
docker: [ { image: cimg/node:22.11 } ]
resource_class: large # més CPU i memòria per minut, i més crèdits per minutCircleCI factura per crèdits, i aquesta és la part del model que cal entendre abans de signar. La idea: cada minut d'execució consumeix crèdits, i quants depèn de la classe de recurs. Una classe gran no costa el mateix per minut que una de petita; els executors macos i les màquines amb GPU són en un altre ordre de magnitud (és exactament el problema de cost que la 05-02 va trobar amb els runners macOS). Altres funcionalitats, com ara docker_layer_caching, també consumeixen. A més hi ha un component per usuari actiu.
No dono xifres: canvien, depenen del pla i de la regió. El que no canvia és la manera de raonar-hi:
- El cost és minuts × classe × concurrència. Pujar de
mediumalargenomés compensa si el temps baixa més que el multiplicador. Es mesura amb una execució de cadascuna, no s'estima. - La paral·lelització multiplica el cost pel nombre de contenidors encara que el temps de paret baixi. Quatre contenidors durant 3 minuts consumeixen més que un durant 8. Es paga velocitat amb diners, que és la decisió explícita que la 04-04 demanava prendre a consciència.
- Un pipeline que s'executa dues vegades per push —branca i PR— duplica la factura sense donar informació nova. Els
filterssón per a això. - La memòria cau mal configurada es paga a cada execució de cada branca.
I un advertiment que val per a tots els SaaS per minut: el cost creix amb l'equip i amb l'activitat, no amb el valor. Un equip que triplica els seus commits triplica la factura. Convé tenir alarmes de consum i revisar mensualment els jobs més cars, amb la mateixa disciplina amb què es revisa el temps de pipeline.
- Depurar: SSH into build i execució local
Dues eines que resolen el problema de "falla en CI i no a la meva màquina", que és on més temps es perd.
SSH into build: rellançar un job amb SSH habilitat i connectar-se a la màquina mentre corre.
# Després de prémer "Rerun job with SSH" a la interfície:
ssh -p 54782 [email protected]
# Ja a dins: l'espai de treball està tal com el va deixar el job
cd ~/reservalia && npm test -- apps/api/reserves.test.ts
env | grep -i databaseÉs la manera més ràpida de diagnosticar una fallada dependent de l'entorn. Dos avisos seriosos: la sessió manté la màquina reservada i consumint crèdits fins que la tanques o expira; i l'entorn conté els secrets del job, així que qui pot obrir sessió SSH és una decisió de seguretat, no de comoditat (04-03). En un job amb credencials de producció, restringeix aquesta capacitat.
Execució local amb la CLI, per iterar sense gastar execucions remotes:
circleci config validate # errors de sintaxi abans de fer push
circleci config process .circleci/config.yml # expandeix orbs i paràmetres: veure el YAML real
circleci local execute --job qualitat # executa un job en Docker localcircleci config process mereix atenció: mostra el fitxer després d'expandir orbs, commands i paràmetres, i és la millor manera d'entendre què fa realment un orb de tercers abans de donar-li els teus secrets. circleci local execute té límits —no admet workflows complets, ni espais de treball entre jobs, ni contexts—, així que serveix per a jobs solts, no per validar el pipeline sencer. Tot i així és una de les millors respostes del mòdul a la pregunta "com provo el pipeline?" de la 04-05.
- Límits i contrapartides
Amb la mateixa franquesa que es va aplicar a Jenkins i GitLab:
- Acoblament a un SaaS. El pla de control és de CircleCI: si el servei no està disponible, no hi ha CI, ni tan sols amb runners propis. I hi ha una lliçó històrica concreta: el 2023 CircleCI va comunicar un incident de seguretat que va obligar a rotar tots els secrets emmagatzemats al servei. És el mateix risc estructural que la 06-04 explicarà amb Travis: confiar secrets a un tercer és una decisió amb conseqüències, i tenir un procediment de rotació massiva assajat no és paranoia.
- No és una plataforma. No hi ha repositori, ni registre, ni entorns, ni incidències. S'integra amb el que tinguis, i tot el que a GitLab venia regalat aquí s'ha de muntar.
- Ecosistema més petit. El catàleg d'orbs és notablement menor que el d'accions de GitHub. Per al que és comú hi ha orb; per al que és rar, l'escrius.
- El cost creix amb l'equip, i els executors cars (macOS, GPU) desequilibren una factura de pressa.
- Runners autoallotjats: existeixen, i resolen el cas de "el treball ha de córrer a la meva xarxa o al meu maquinari". Però el pla de control continua sent SaaS, així que no resolen la restricció de compliment estricta que a la 06-01 portava a Jenkins. Convé no confondre "el treball corre a la meva màquina" amb "tot el sistema està sota el meu control".
- Mercat. El pes relatiu de CircleCI ha disminuït des que GitHub Actions es va integrar al lloc on ja era el codi de gairebé tothom. Això importa per contractar, per trobar respostes i per a la longevitat de l'eina.
- Quan triar CircleCI
Sí, quan:
- La velocitat del pipeline és un problema mesurat i car, amb un conjunt de proves gran i lent. El repartiment per temps i les classes de recurs donen millores reals sense escriure infraestructura pròpia.
- Vols un CI potent sense casar-te amb la plataforma del repositori, o tens projectes en més d'un proveïdor de Git.
- Necessites macOS, ARM o GPU de manera còmoda i sense operar màquines: l'oferta d'executors és àmplia (rellevant per al cas mòbil de la 05-02).
- Valores eines de diagnòstic de primera —insights, SSH into build, validació local— i tens qui les faci servir.
- Ja el tens i funciona. Migrar costa, i la 06-07 posa números a aquest càlcul.
No, quan:
- El teu codi és a GitHub i no tens un problema de velocitat: GitHub Actions ve integrat i sense una segona integració de permisos per mantenir.
- Necessites control total o compliment estricte: el pla de control SaaS no ho permet (per a això hi ha Jenkins, 06-01).
- Vols una plataforma integrada: això és GitLab (06-02).
- El pressupost és una restricció dura i l'equip creix: el model per crèdits i per usuari escala amb l'activitat.
Errors Comuns i Consells
No cridar store_test_results. Sense ell, --split-by=timings no té històric i el repartiment degrada a alfabètic, que és justament el desequilibri que es volia evitar. És l'error que més vegades converteix una bona idea en una decepció.
Claus de memòria cau sense arquitectura ni versió de runtime. Restaurar una memòria cau d'una altra arquitectura produeix fallades de mòduls natius difícils de llegir. Inclou-hi {{ arch }} i la versió.
Sense fallback a restore_cache. Amb una sola clau exacta, qualsevol canvi del lockfile obliga a descarregar-ho tot.
Fer servir memòria cau per passar artefactes entre jobs. És el que provoca desplegar un build vell sense que res falli. Per a això hi ha l'espai de treball.
No esperar els contenidors secundaris. PostgreSQL triga a acceptar connexions; sense dockerize -wait tindràs proves flaky que depenen de la càrrega de la màquina.
Pujar de resource_class sense mesurar. Duplicar la classe no sempre redueix el temps (un build limitat per E/S o per un test seqüencial no millora) i sí que duplica el consum.
parallelism alt per defecte. Multiplica el cost linealment. El punt òptim es busca mesurant temps total davant de crèdits, i sol estar més baix del que diu la intuïció.
Deixar sessions SSH obertes. Reserven la màquina i consumeixen. Tanca-les.
Orbs sense ancorar o d'origen desconegut. S'executen amb els teus secrets. Ancora a versió exacta, revisa amb circleci config process i prefereix orbs certificats o propis per al que toca credencials.
Consell transversal: encara que facis servir commands i orbs, mantén la lògica real en scripts del repositori (./scripts/desplegar.sh) i deixa el YAML com a orquestrador. És el que fa que l'exercici 3 d'aquesta lliçó sigui d'hores i no de setmanes, i és la lliçó de portabilitat que la 06-07 desenvoluparà.
Exercicis
Exercici 1. El job test de Reservalia fa servir parallelism: 6 i triga 9 minuts. En mirar els contenidors: cinc acaben entre 1:30 i 2:10, i un triga 8:50. Al config.yml apareix circleci tests glob "apps/**/*.test.ts" | circleci tests split i no hi ha cap store_test_results. Diagnostica, corregeix i calcula què passa amb el temps i amb el consum. Convindria baixar el parallelism?
Exercici 2. Reservalia té sis repositoris a CircleCI i el bloc de preparació i publicació repetit a tots. Dissenya l'orb reservalia/plataforma: què conté, com es versiona, com es prova abans de publicar, com es desplega als sis repositoris i quina política s'aplica als orbs de tercers que ja fan servir.
Exercici 3. En Diego porta un full de càlcul: el consum de CircleCI ha pujat un 240 % en quatre mesos sense que l'equip hagi crescut. Les dades: el 68 % del consum és del job e2e amb resource_class: xlarge i parallelism: 8; s'executen dos pipelines per push (branca i PR); docker_layer_caching està activat en quatre jobs; i hi ha 14 branques amb pipelines nocturns programats que ningú mira. Escriu el pla de reducció, ordenat per relació entre estalvi i risc.
Solucions
Solució 1.
Diagnòstic. circleci tests split sense --split-by=timings reparteix per nom de fitxer, i sense store_test_results no hi ha històric encara que es demanés. El contenidor lent concentra els tests que triguen —probablement els d'integració amb base de dades—. El job triga el que el pitjor contenidor: 8:50. Es paguen sis contenidors durant gairebé nou minuts i se n'aprofita un.
Correcció:
test:
executor: node-base
parallelism: 4
steps:
- attach_workspace: { at: ~/reservalia }
- run: dockerize -wait tcp://localhost:5432 -timeout 1m
- run:
command: |
FITXERS=$(circleci tests glob "apps/**/*.test.ts" \
| circleci tests split --split-by=timings)
npm test -- --runTestsByPath $FITXERS --reporters=jest-junit
- store_test_results: { path: informes } # imprescindible
- store_artifacts: { path: cobertura }Efecte. Suma de feina real ≈ 5×1:50 + 8:50 ≈ 18 min. Repartida entre 4 contenidors, ~4:30 cadascun més arrencada: el job passa de 8:50 a uns 5 minuts. El consum baixa a més per dues vies: menys contenidors (4 en comptes de 6) i menys minuts per contenidor.
Baixar el parallelism? Sí, i el raonament importa més que el número. Amb 18 minuts de feina real:
parallelism |
Temps aproximat | Contenidor·minut |
|---|---|---|
| 1 | 18:00 | 18 |
| 2 | 9:00 | 18 |
| 4 | 4:30 | 18 + arrencades |
| 6 | 3:00 | 18 + més arrencades |
| 12 | 1:30 | 18 + arrencada dominant |
La feina total és gairebé constant, així que el cost per execució amb prou feines canvia amb el paral·lelisme —fins que l'arrencada del contenidor deixa de ser menyspreable davant de la feina assignada—. Per això la resposta és "4 està bé i 6 no era el problema": el problema era el repartiment, no el número. La regla pràctica: puja el paral·lelisme mentre cada contenidor continuï fent bastanta més feina de la que costa arrencar-lo, i arregla el repartiment abans de tocar el número.
Solució 2.
Contingut de l'orb:
# src/@orb.yml
version: 2.1
description: Peces comunes de CI/CD de Reservalia
executors:
node:
parameters: { version: { type: string, default: "22.11" } }
docker: [ { image: cimg/node:<< parameters.version >> } ]
resource_class: medium
commands:
preparar:
parameters:
versio-cache: { type: string, default: "v2" }
steps:
- checkout
- restore_cache:
keys:
- npm-<< parameters.versio-cache >>-{{ arch }}-{{ checksum "package-lock.json" }}
- npm-<< parameters.versio-cache >>-{{ arch }}-
- run: npm ci --prefer-offline --no-audit
jobs:
build-publicar:
parameters:
dockerfile: { type: string }
repositori: { type: string }
publicar: { type: boolean, default: false }
machine: { image: ubuntu-2404:current }
steps:
- preparar
- run: ./scripts/build-imatge.sh << parameters.dockerfile >> << parameters.repositori >> << parameters.publicar >>Observeu que el job crida un script del repositori, no incrusta vint línies de shell al YAML: això manté la lògica portable i fa que l'orb sigui un orquestrador prim.
Versionat i publicació: versionat semàntic obligatori; els canvis incompatibles pugen major. El flux és publish a @dev:<etiqueta> (mutable, per provar) i publish promote a una versió estable (immutable). Els repositoris ancoren a versió exacta @2.3.0, no a @2, perquè cap adopció no sigui automàtica.
Proves abans de publicar: el mateix repositori de l'orb té un pipeline amb circleci orb validate, circleci config process sobre un config.yml d'exemple per comprovar que expandeix al que s'espera, i un projecte d'integració (reservalia-orb-sandbox) que consumeix @dev:proves i exerceix tots els commands a cada commit. És el canari de la 04-05.
Desplegament als sis repositoris: progressiu. Es publica 2.4.0, s'adopta en un repositori de baix risc durant una setmana, i només llavors s'obre un PR als altres cinc. Mai els sis alhora, i mai per adopció automàtica: el risc de radi gran és el mateix que a Jenkins i GitLab.
Política sobre orbs de tercers: llista permesa revisada per l'equip de plataforma; ancorats a versió exacta; cap orb de tercers en jobs que tenen contexts de producció llevat dels certificats i revisats; i circleci config process com a pas obligatori de revisió en afegir-ne un de nou, per veure quines ordres executa realment. És la política de dependències de la 04-02 aplicada al pipeline.
Solució 3. Ordenat per estalvi dividit per risc, que és l'ordre correcte per a aquest tipus de feina:
| # | Acció | Estalvi estimat | Risc | Esforç |
|---|---|---|---|---|
| 1 | filters per no executar pipeline de branca i de PR alhora |
~50 % de tot | Nul | 1 h |
| 2 | Apagar els 14 pipelines nocturns que ningú mira | Directe i mesurable | Baix: si ningú els mira, no donen senyal | 1 h |
| 3 | Arreglar el repartiment d'e2e (timings) i baixar el parallelism de 8 a 4 |
Alt sobre el 68 % | Baix | 0,5 dia |
| 4 | Baixar e2e d'xlarge a large i mesurar |
Mitjà | Baix si es mesura abans/després | 0,5 dia |
| 5 | Treure docker_layer_caching on ja hi ha memòria cau de registre |
Mitjà | Baix: es compara el temps amb i sense | 0,5 dia |
| 6 | Executar e2e complet només a main i un subconjunt als PR |
Alt | Mitjà: menys senyal abans de fusionar | 2 dies |
Notes de criteri. El punt 1 és el més rendible del pla i el més invisible: no es manifesta com a lentitud, només com a factura, i per això porta mesos allà. Els punts 3 i 4 s'han de fer en aquest ordre i mesurant entre l'un i l'altre, perquè si es canvien alhora no se sap quin ha causat què; i xlarge pot estar justificat si l'e2e fa servir navegadors reals, així que es comprova, no s'assumeix. El punt 6 és l'únic que canvia el contracte de qualitat: redueix el senyal abans de fusionar i per tant pot pujar el CFR, així que es decideix amb la Marta, s'escriu quin subconjunt s'executa i per què, i es vigilen les mètriques DORA durant un mes. Si el CFR puja del 3,8 % actual, es reverteix.
I l'observació de mètode que cal donar a en Diego: el 240 % no va venir d'un canvi, va venir de quatre decisions petites que ningú va revisar. La correcció estructural no és aquesta llista, és posar una alarma de consum i una revisió mensual dels jobs més cars, amb la mateixa disciplina amb què es revisa el temps de pipeline a la 04-04. Sense això, en quatre mesos hi ha un altre 240 %.
Conclusió
CircleCI és l'eina del mòdul que millor respon a una pregunta concreta: com fer que un pipeline gran sigui ràpid sense construir la infraestructura tu. Les seves tres peces distintives són d'aprenentatge transferible encara que no la facis servir mai: la memòria cau explícita amb claus i fallback t'obliga a entendre què invalida què; la separació nítida entre espai de treball, memòria cau i artefactes aclareix una confusió que en altres eines es pot arrossegar durant anys; i el repartiment de proves per temps històrics és la millor solució disponible al problema de sharding, i saber que existeix canvia el que demanes a les altres.
El que es paga: és un SaaS i el seu pla de control no és teu —amb el precedent de la rotació massiva de secrets del 2023 com a recordatori—, no és una plataforma i tot el que GitLab regalava aquí ho muntes, l'ecosistema és més petit, i el model per crèdits creix amb l'activitat de l'equip, cosa que exigeix una vigilància de la despesa que no cal deixar per quan arribi la factura.
La lliçó següent canvia de registre. Travis CI va ser el que va inventar bona part del que avui donem per evident —el fitxer de CI al repositori, la matriu de versions, el CI gratuït per a codi obert— i avui és, sobretot, alguna cosa que et trobaràs heretada i una lliçó d'història amb moralitat: sobre models de negoci que canvien sota els teus peus, sobre confiar secrets a un tercer, i sobre per què convé que el teu pipeline no depengui massa de l'eina que l'executa. Veurem el seu model de fases fixes, el pipeline de Reservalia forçat a cabre-hi, i com migrar un .travis.yml al que facis servir avui.
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
