Jenkins resolia el problema de "vull automatitzar qualsevol cosa" i deixava tota la resta —repositori, registre, entorns, seguretat— en mans de plugins o d'altres productes. GitLab parteix de la posició contrària: un sol producte que conté el repositori, la revisió de codi, el CI/CD, el registre de contenidors, els entorns, l'anàlisi de seguretat, les incidències i la wiki, amb tot connectat de fàbrica. Aquesta aposta arquitectònica —el que en el seu màrqueting anomenen DevOps platform i aquí anomenarem simplement plataforma integrada— és el que cal entendre per avaluar-la bé, perquè explica tant les seves virtuts més grans com els seus costos. A més, GitLab CI és històricament important: el seu .gitlab-ci.yml (2015) va ser dels primers a normalitzar el pipeline as code integrat al repositori, i el seu model de stages amb jobs a dins és el que molts enginyers porten al cap quan pensen "pipeline". En aquesta lliçó veurem aquest model, la seva evolució d'etapes fixes a graf amb needs, el pipeline de Reservalia traduït per segona vegada, la distinció entre cache i artifacts que més confusió genera, els runners i els seus executors, els entorns i les review apps, els mecanismes de reutilització, i què costa operar una instància pròpia.
Contingut
- La plataforma integrada: què és i què implica
- El model d'execució: stages, jobs i el pas al graf
- El pipeline de Reservalia en
.gitlab-ci.yml cachedavant d'artifacts: la distinció que més confon- Paral·lelització:
paralleliparallel: matrix - Runners i executors
- Variables, secrets i protecció
- Entorns, desplegament manual i review apps
- Reutilització:
extends,include, components i pipelines pare-fill - El registre de contenidors integrat
- Auto DevOps, amb criteri
- SaaS davant d'autoallotjat, i el cost real
- Quan GitLab CI/CD és l'elecció correcta
- Errors Comuns i Consells
- Exercicis
- Conclusió
- La plataforma integrada: què és i què implica
En una organització típica amb GitHub, la cadena d'eines es compon: GitHub per al codi, GitHub Actions per al CI, ECR o Docker Hub per al registre, Snyk o Dependabot per a les dependències, Jira per a les incidències, alguna cosa a part per als entorns. A GitLab, tot això són pestanyes del mateix projecte.
El que es guanya, i no és retòrica:
- Zero integracions per mantenir entre peces. No hi ha tokens creuats entre el CI i el registre, ni webhooks que es trenquen, ni "el bot de Jira ha perdut permisos". El job de CI empeny al registre del mateix projecte amb una variable que ja existeix.
- Traçabilitat d'extrem a extrem. De la incidència al merge request, al pipeline, a l'artefacte, a l'entorn on està desplegat. La pregunta "quina versió hi ha en producció i quines incidències inclou?" té resposta a la interfície sense construir res.
- Un sol model de permisos. Qui pot fusionar, pot desplegar a staging però no a producció, i això es configura una sola vegada.
- Funcionalitats que només existeixen perquè les peces estan juntes: els informes de seguretat que apareixen dins del merge request comparant la branca amb
main, els entorns que mostren quin commit està desplegat, les review apps amb el seu enllaç al MR.
El que es paga, amb la mateixa franquesa:
- Acoblament fort. Sortir de GitLab significa sortir de tot alhora: repositori, CI, registre i historial d'entorns. És el cost de canvi més alt del mòdul, i la 06-07 el quantifica.
- Les funcionalitats interessants estan esglaonades per nivell de subscripció. Bona part del que s'explica en articles i conferències —aprovacions múltiples, algunes anàlisis de seguretat, certs controls de compliment— no és al nivell gratuït. No convé dissenyar una arquitectura sobre una funcionalitat i descobrir després en quin nivell viu.
- Si te l'autoallotges, operes una plataforma sencera, no un servidor de CI: base de dades, emmagatzematge d'objectes, Redis, Gitaly, el registre, els runners. Apartat 12.
Una nota de vocabulari útil: a GitLab, merge request (MR) és el que a GitHub és pull request. El concepte és el mateix que la 02-07 va descriure.
- El model d'execució: stages, jobs i el pas al graf
El model original de GitLab CI és d'etapes seqüencials amb jobs paral·lels a dins:
flowchart LR
subgraph S1["stage: preparar"]
A["preparar"]
end
subgraph S2["stage: verificar"]
B["qualitat"]
C["test 1/4"]
D["test 2/4"]
E["test 3/4"]
F["test 4/4"]
end
subgraph S3["stage: construir"]
G["build"]
H["seguretat"]
end
subgraph S4["stage: publicar"]
I["publicar"]
end
S1 --> S2 --> S3 --> S4
La regla clàssica: tots els jobs d'una etapa s'executen en paral·lel, i l'etapa següent no comença fins que l'anterior acaba sencera. És un model fàcil de raonar i amb un defecte clar: si test 4/4 triga vuit minuts i els altres dos, build espera vuit minuts encara que només depengués de preparar. És exactament el problema de la barrera d'etapa que la 04-01 va descriure.
La correcció va arribar amb needs, que converteix el pipeline en un DAG: un job amb needs arrenca tan bon punt acaben les seves dependències concretes, sense esperar la seva etapa. Amb needs, les stages passen de ser barreres a ser sobretot agrupació visual.
Model per stages |
Model per needs (DAG) |
|
|---|---|---|
| Arrencada d'un job | Quan acaba tota l'etapa anterior | Quan acaben les seves dependències |
| Facilitat de lectura | Molt alta | Mitjana: cal seguir el graf |
| Temps total | Suma dels màxims per etapa | Camí crític real |
| Risc | Esperes innecessàries | Grafs enredats si ningú els revisa |
Vocabulari de GitLab traduït:
| GitLab | Equivalent al curs |
|---|---|
| Pipeline | Execució de workflow |
| Stage | Etapa (agrupació) |
| Job | Job |
Script (una línia de script:) |
Step |
| Runner | Runner / agent |
| Executor | Com materialitza el runner l'entorn (shell, docker, kubernetes) |
| Artifacts | Artefactes entre jobs i descarregables |
| Environment | Entorn de desplegament amb historial |
- El pipeline de Reservalia en
.gitlab-ci.yml
.gitlab-ci.ymlSegona traducció del mateix pipeline: instal·lar amb memòria cau → lint i test en paral·lel → build d'imatge → publicar per digest.
# .gitlab-ci.yml — Reservalia · CI
stages: [preparar, verificar, construir, publicar, desplegar] # 1
default: # 2
image: node:22-bookworm
interruptible: true # cancel·la si arriba un push nou
retry:
max: 2
when: [runner_system_failure, stuck_or_timeout_failure] # 3 · reintentar només fallades d'infraestructura
variables:
NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.npm" # 4 · memòria cau dins de l'espai de treball
IMATGE: "$CI_REGISTRY_IMAGE/api"
FF_USE_FASTZIP: "true"
workflow: # 5
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- when: never # res més dispara pipeline
# ---------------------------------------------------------------- plantilles
.node: # 6 · plantilla reutilitzable
cache:
key:
files: [package-lock.json] # 7 · clau per lockfile
paths: [.npm/]
policy: pull # només llegeix; el job "preparar" l'escriu
before_script:
- npm ci --prefer-offline --no-audit
# ---------------------------------------------------------------- jobs
preparar:
stage: preparar
extends: .node
cache:
key:
files: [package-lock.json]
paths: [.npm/]
policy: pull-push # 8 · aquest sí que actualitza la memòria cau
script:
- echo "Dependències instal·lades i memòria cau poblada"
qualitat:
stage: verificar
extends: .node
needs: [preparar] # 9 · DAG
script:
- npx prettier --check .
- npm run lint
- npm run typecheck
test:
stage: verificar
extends: .node
needs: [preparar]
parallel: 4 # 10 · sharding
services: # 11 · PostgreSQL com a servei
- name: postgres:16-alpine
alias: db
variables:
POSTGRES_DB: reservalia_test
POSTGRES_PASSWORD: test
DATABASE_URL: "postgres://postgres:test@db:5432/reservalia_test"
script:
- npm run migrate
- npm test -- --shard=$((CI_NODE_INDEX))/$CI_NODE_TOTAL
artifacts: # 12
when: always
expire_in: 1 week
reports:
junit: informes/junit-*.xml
coverage_report:
coverage_format: cobertura
path: cobertura/cobertura.xml
build-web:
stage: construir
extends: .node
needs: [preparar]
script:
- npm run build --workspace apps/web
artifacts:
paths: [apps/web/dist/] # 13 · això sí que és un artefacte
expire_in: 1 day
build-api:
stage: construir
needs: [preparar]
image: docker:27
services: [docker:27-dind] # 14 · Docker-in-Docker
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- |
docker buildx build \
--file apps/api/Dockerfile \
--cache-from type=registry,ref=$IMATGE:cache \
--cache-to type=registry,ref=$IMATGE:cache,mode=max \
--tag $IMATGE:$CI_COMMIT_SHA \
--push .
- docker buildx imagetools inspect $IMATGE:$CI_COMMIT_SHA
--format '{{.Manifest.Digest}}' > digest.env.tmp
- echo "DIGEST=$(cat digest.env.tmp)" > build.env
artifacts:
reports:
dotenv: build.env # 15 · outputs entre jobs
seguretat:
stage: construir
extends: .node
needs: [preparar]
script:
- npm audit --audit-level=high
- gitleaks detect --no-git --exit-code 1
allow_failure: false
publicar:
stage: publicar
needs: [qualitat, test, build-api, build-web, seguretat] # 16 · fan-in
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # només a la branca per defecte
image: docker:27
services: [docker:27-dind]
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker buildx imagetools create --tag $IMATGE:estable $IMATGE@$DIGEST # promoció per digest
desplegar-staging:
stage: desplegar
needs: [publicar]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
environment: # 17
name: staging
url: https://staging.reservalia.example
script:
- ./scripts/desplegar.sh staging "$IMATGE@$DIGEST"
desplegar-produccio:
stage: desplegar
needs: [desplegar-staging]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual # 18 · aprovació
allow_failure: false
environment:
name: produccio
url: https://app.reservalia.example
script:
- ./scripts/desplegar.sh produccio "$IMATGE@$DIGEST"stagesdeclara l'ordre. Un job sensestagecau atestper defecte, cosa que sorprèn; declara'l sempre.defaultaplica a tots els jobs el que abans es repetia.interruptible: trueamb l'opció d'autocancel·lació del projecte és elcancel-in-progressde la 04-04.retryselectiu. Reintentar qualsevol fallada emmascara les proves flaky de la 02-04 i converteix el pipeline en un generador de verd fals; reintentar només fallades d'infraestructura del runner és legítim. Aquesta distinció és la diferència entre resiliència i autoengany.- La memòria cau ha de ser dins de
$CI_PROJECT_DIR: GitLab només pot posar en memòria cau rutes de l'espai de treball. Per això es redirigeix la memòria cau d'npm allà en comptes de deixar-la a~/.npm. És l'error número u de memòria cau a GitLab. workflow:rulesdecideix si es crea pipeline. Sense això, un push a una branca amb MR obert genera dos pipelines (un de branca i un altre de MR): duplica la despesa i confon el senyal.- Una plantilla és un job que comença per punt: GitLab no l'executa i serveix per a
extends. cache:key:filesgenera la clau a partir del hash del lockfile: canvia el lockfile, canvia la memòria cau. És elhashFilesde la 04-02 amb un altre nom.policyés la peça que gairebé ningú fa servir i que estalvia molt de temps:pullnomés descarrega,pull-pushdescarrega i puja al final. Amb sis jobs pujant la mateixa memòria cau idèntica es malgasten minuts; aquí només l'escriupreparar.needsconverteix el pipeline en graf. Ambneeds: []un job arrenca immediatament, ignorant la seva etapa.parallel: 4crea quatre instàncies del job ambCI_NODE_INDEX(1..N) iCI_NODE_TOTAL. És el sharding de la 02-04; el repartiment per temps històrics que veuràs a la 06-03 no ve de sèrie.servicesaixeca contenidors auxiliars accessibles pel seualias, igual que el PostgreSQL de la 02-02.artifacts:reportssón artefactes amb semàntica: GitLab els interpreta i els mostra al MR (resultats de test, cobertura, troballes de seguretat).when: alwaysés imprescindible: els informes de test importen sobretot quan el job falla.- Distinció de fons, que desenvolupa l'apartat 4:
dist/és un artefacte —sortida del pipeline, es passa a altres jobs—;.npm/és memòria cau —accelera, i perdre-la no trenca res—. - Docker-in-Docker és el patró habitual per construir imatges a GitLab, i té implicacions de seguretat que la 06-05 desenvolupa: el servei
dindrequereix runners privilegiats. artifacts:reports:dotenvés el mecanisme d'outputs entre jobs: les variables del fitxer queden disponibles als jobs que depenen d'aquest. És l'equivalent delneeds.<job>.outputsde GitHub Actions.- El fan-in de la 04-01:
publicarespera les cinc senyals. environmentregistra el desplegament: GitLab desa quin commit hi ha astaging, des de quan, i ofereix el botó de tornar a desplegar una versió anterior.when: manualamballow_failure: falseés la porta de la 03-01: el pipeline s'atura esperant que una persona premi el botó, i qui el pot prémer ho determina la protecció de l'entorn.
Comparat amb el ci.yml original, la traducció és gairebé un a un. Les diferències reals: el sharding és un número en comptes d'una matriu, els outputs entre jobs passen per un fitxer dotenv en comptes de per outputs, la memòria cau exigeix configurar rutes i política a mà, i el registre de contenidors i els entorns són part del mateix producte en lloc de serveis externs amb credencials pròpies.
cache davant d'artifacts: la distinció que més confon
cache davant d'artifacts: la distinció que més confonTots dos desen fitxers i tots dos els restauren en un altre job. No són el mateix i confondre'ls produeix pipelines lents o incorrectes.
cache |
artifacts |
|
|---|---|---|
| Propòsit | Accelerar (dependències, compilacions intermèdies) | Transportar resultats entre jobs i fer-los descarregables |
| Si desapareix | El job és més lent, però funciona | El pipeline falla o el resultat es perd |
| On es desa | Al runner (o en emmagatzematge compartit, si es configura) | Sempre al servidor de GitLab |
| Abast | Compartida entre pipelines i branques, segons key |
De l'execució concreta del pipeline |
| Es recupera | Per key, best-effort, sense garanties |
Automàticament des dels jobs que el necessiten |
| Caducitat | Política del runner | expire_in, explícit |
| Contingut típic | .npm/, ~/.gradle, vendor/ |
dist/, .war, informes JUnit, SBOM |
Regla mental que resol el 100 % dels casos: si esborrar això trenca el pipeline, és artefacte; si només el fa més lent, és memòria cau.
Dos mecanismes relacionats:
# Descarregar NOMÉS els artefactes que aquest job necessita
publicar:
needs:
- job: build-web
artifacts: true # descarrega dist/
- job: seguretat
artifacts: false # només la dependència d'ordre, sense transferir fitxersSense això, un job descarrega per defecte els artefactes de tots els jobs d'etapes anteriors, i en un pipeline gran això són centenars de megabytes moguts sense raó: una de les causes de pipeline lent més freqüents i menys diagnosticades. dependencies: [] és la manera antiga de dir "no em descarreguis res".
I expire_in no és opcional: els artefactes consumeixen emmagatzematge facturable en SaaS i disc en autoallotjat. La política de retenció de la 02-06 aquí és una línia per job.
- Paral·lelització:
parallel i parallel: matrix
parallel i parallel: matrix# Sharding simple: N còpies idèntiques que es reparteixen per índex
test:
parallel: 4
script: [ "npm test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL" ]
# Matriu: combinacions de variables, com el matrix de la 02-04
test-compatibilitat:
parallel:
matrix:
- NODE: ["20", "22"]
POSTGRES: ["15", "16"] # → 4 jobs
- NODE: ["22"] # els blocs addicionals se sumen
POSTGRES: ["17"]
EXPERIMENTAL: "true"
image: node:$NODE
services: [ "postgres:$POSTGRES-alpine" ]
script: [ "npm test" ]Diferències amb el matrix de GitHub Actions que convé tenir clares: no existeix exclude —es modela afegint blocs en comptes de restar—, i no hi ha fail-fast global: el control és per job amb allow_failure. A canvi, parallel: matrix sí que permet variar image i services, cosa que dóna força joc.
- Runners i executors
Un runner és el procés que executa jobs. Es registra contra una instància i se li assignen jobs segons els seus tags.
| Tipus de runner | Abast | Quan |
|---|---|---|
| Compartit (shared) | Tota la instància | El cas normal en SaaS; minuts consumibles |
| De grup | Tots els projectes d'un grup | Runners propis compartits per un departament |
| Específic de projecte | Un projecte | Maquinari especial, accés a xarxes concretes |
I l'executor determina com materialitza l'entorn:
| Executor | Com executa | Aïllament | Quan fer-lo servir |
|---|---|---|---|
shell |
Ordres a la màquina del runner | Cap | Gairebé mai; hereta el problema de l'agent contaminat de la 06-01 |
docker |
Un contenidor per job, des d'image: |
Bo | El cas normal |
docker+machine |
Crea una VM per job i la destrueix | Molt bo | Autoescalat al núvol |
kubernetes |
Un Pod per job | Molt bo | Si ja tens clúster |
ssh, custom |
Màquines remotes, integracions pròpies | Variable | Maquinari rar |
# config.toml d'un runner autoallotjat amb executor docker
concurrent = 8 # jobs simultanis en aquesta màquina
check_interval = 3
[[runners]]
name = "runner-reservalia-1"
url = "https://gitlab.example.com/"
token = "glrt-..." # token de registre
executor = "docker"
[runners.docker]
image = "node:22-bookworm" # imatge per defecte si el job no la indica
privileged = false # true NOMÉS si cal dind (vegeu 06-05)
volumes = ["/cache", "/certs/client"]
memory = "4g"
cpus = "2"
[runners.cache] # memòria cau compartida entre runners
Type = "s3"
Shared = true
[runners.cache.s3]
ServerAddress = "s3.eu-west-1.amazonaws.com"
BucketName = "reservalia-ci-cache"
AuthenticationType = "iam" # rol d'instància, sense claus de llarga vidaTres punts operatius amb conseqüències:
privileged = trueper a Docker-in-Docker és un forat real: un job amb accés al dimoni privilegiat pot escapar del contenidor i comprometre la màquina, inclosos els secrets d'altres jobs que hi corrin. Alternatives sense privilegis a la 06-05 (Kaniko, Buildah, BuildKit rootless).- La memòria cau amb emmagatzematge compartit canvia el rendiment del tot. Amb memòria cau local al runner i diversos runners, cadascun té la seva còpia i la taxa d'encert s'esfondra. Amb S3 compartit, tots comparteixen.
concurrentés la palanca de cost: dimensionar-lo malament deixa jobs en cua (cosa que es percep com "el CI és lent", encara que el temps d'execució sigui el mateix) o malbarata màquines.
- Variables, secrets i protecció
GitLab té un sol mecanisme —variables de CI/CD— amb atributs que canvien el seu comportament:
| Atribut | Efecte | Quan activar-lo |
|---|---|---|
| Protected | Només s'exposa en pipelines de branques i tags protegits | Sempre, per a qualsevol credencial de staging o producció |
| Masked | El seu valor se substitueix per [MASKED] als logs |
Sempre, per a secrets |
| File | Es materialitza com a fitxer i la variable conté la ruta | Kubeconfig, claus, certificats |
| Environment scope | Valor diferent per entorn (produccio, staging, *) |
Configuració per entorn de la 03-02 |
| Expanded | S'interpolen altres variables a dins | Desactivar en secrets amb $ |
Dos advertiments importants. Masked té restriccions de format —longitud mínima, sense espais ni certs caràcters—; un secret que no compleix simplement no s'emmascara, i GitLab ho diu en un avís que és fàcil passar per alt. I l'emmascarament té la mateixa limitació que a Jenkins: cobreix la coincidència literal, no la transformada. La conclusió de la 04-03 no canvia per eina.
Protected és la protecció de debò, i la que més s'oblida. Sense ella, qualsevol que obri un MR des d'una branca arbitrària pot escriure un .gitlab-ci.yml que imprimeixi les credencials de producció. Amb la variable marcada com a protegida, aquesta variable no existeix en pipelines de branques no protegides. Regla: tota credencial de desplegament, protegida; sense excepcions.
Per a identitat federada, GitLab emet tokens OIDC per job amb id_tokens, exactament el patró sense claus de llarga vida de la 03-02:
desplegar-produccio:
id_tokens:
AWS_TOKEN:
aud: https://gitlab.example.com # audiència que espera el proveïdor d'identitat
script:
- >
export $(aws sts assume-role-with-web-identity
--role-arn "$AWS_ROLE_ARN"
--role-session-name "gitlab-$CI_JOB_ID"
--web-identity-token "$AWS_TOKEN"
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
--output text | awk '{print "AWS_ACCESS_KEY_ID="$1"\nAWS_SECRET_ACCESS_KEY="$2"\nAWS_SESSION_TOKEN="$3}')
- ./scripts/desplegar.sh produccioLa condició de confiança a AWS es defineix sobre atributs del token (projecte, branca, entorn), de manera que només un pipeline de main desplegant a produccio pot assumir el rol. És el mateix disseny de la 03-02 amb un altre emissor.
- Entorns, desplegament manual i review apps
environment: és una de les millors peces de GitLab i no té un equivalent exacte de sèrie a totes les eines: converteix un job en un desplegament registrat, amb historial de quin commit està desplegat, enllaç a la URL, i botons per tornar a desplegar o revertir a un desplegament anterior.
review:
stage: desplegar
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
environment:
name: review/$CI_COMMIT_REF_SLUG # 1 · un entorn per branca
url: https://$CI_COMMIT_REF_SLUG.review.reservalia.example
on_stop: parar-review # 2
auto_stop_in: 3 days # 3
script:
- ./scripts/desplegar-review.sh "$CI_COMMIT_REF_SLUG" "$IMATGE@$DIGEST"
parar-review:
stage: desplegar
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop # 4
variables:
GIT_STRATEGY: none # no cal el codi per destruir
script:
- ./scripts/destruir-review.sh "$CI_COMMIT_REF_SLUG"- Un entorn dinàmic per branca: són les review apps, l'equivalent directe de les previsualitzacions per PR de la 02-07. GitLab publica l'enllaç dins del merge request, així que la Nuria pot provar el canvi sense baixar-se la branca.
on_stopenllaça el job que destrueix l'entorn; s'executa automàticament en tancar o fusionar el MR.auto_stop_inés la línia que evita la factura sorpresa: sense ella, cada branca abandonada deixa un entorn corrent indefinidament. És el problema que la 05-01 assenyalava amb les previsualitzacions òrfenes, aquí resolt amb una línia.action: stopmarca el job com el que apaga l'entorn.
Sobre aprovacions: when: manual atura el job fins que algú el llança, i qui ho pot fer es controla protegint l'entorn (Deployments → Protected environments), que és l'equivalent dels revisors d'Environments de la 03-02. Als nivells de pagament existeixen aprovacions múltiples i separació de funcions —qui aprova no pot ser qui va escriure el canvi—, que és el que solen exigir les auditories.
- Reutilització:
extends, include, components i pipelines pare-fill
extends, include, components i pipelines pare-fillGitLab ofereix més mecanismes de reutilització que cap altra eina del mòdul, i convé saber quin toca.
# a) Anchors YAML: substitució textual, sense coneixement de GitLab
.base: &base
image: node:22
retry: 1
qualitat:
<<: *base
script: [ "npm run lint" ]
# b) extends: el mateix però conscient de l'estructura (fusiona mapes en profunditat)
.node:
image: node:22
cache: { key: { files: [package-lock.json] }, paths: [.npm/] }
qualitat:
extends: .node # preferible als anchors: fusiona bé i admet herència múltiple
script: [ "npm run lint" ]
# c) include: portar YAML de fora
include:
- local: '/ci/plantilles/node.yml' # mateix repositori
- project: 'reservalia/plantilles-ci' # un altre projecte de la instància
ref: 'v3.2.0' # ancorat a tag, no a main!
file: '/plantilles/build-publicar.yml'
- remote: 'https://exemple.com/plantilla.yml' # URL: sense control de versió → evitar
- template: 'Security/SAST.gitlab-ci.yml' # plantilla oficial de GitLab
- component: gitlab.com/reservalia/components/[email protected] # component amb inputs tipats
inputs:
dockerfile: apps/api/Dockerfile
publicar: trueextends davant d'anchors: els anchors són YAML pur, es resolen abans que GitLab entengui res i no funcionen entre fitxers inclosos; extends fusiona mapes en profunditat, admet cadenes i sí que travessa include. Fes servir extends.
Els components (CI/CD components) són l'evolució més recent i la més semblant al que la 04-05 defensava: unitats amb inputs tipats i versió pròpia, publicades en un catàleg, en comptes de YAML inclòs a cegues. Un include: template no valida res; un component falla en analitzar el fitxer si li passes un booleà on espera una cadena.
I la regla de la 04-03 aplica igual aquí: include d'un altre projecte ancorat a main significa que un commit aliè canvia el teu pipeline sense que tu hagis fusionat res. Ancora a tag o a SHA.
Pipelines pare-fill per a monorepos, que és el problema d'execució selectiva de la 04-04:
# .gitlab-ci.yml del pare
generar-fills:
stage: preparar
script:
- node scripts/generar-pipeline.js > fill.yml # decideix què construir segons què ha canviat
artifacts: { paths: [fill.yml] }
executar-api:
stage: verificar
rules:
- changes: [ "apps/api/**/*", "packages/compartit/**/*" ] # només si ha canviat això
trigger:
include:
- artifact: fill.yml # pipeline generat dinàmicament
job: generar-fills
strategy: depend # el pare espera i reflecteix el resultatI trigger:project per disparar el pipeline d'un altre projecte —el cas multiprojecte de la 05-03—, amb strategy: depend si el pare ha d'esperar el fill.
| Mecanisme | Què reutilitza | Quan |
|---|---|---|
| Anchors YAML | Fragments, mateix fitxer | Gairebé mai: fes servir extends |
extends |
Configuració de job | Repetició dins d'un projecte |
include: local |
Fitxers del repositori | Partir un .gitlab-ci.yml gran |
include: project |
Plantilles entre projectes | Estandarditzar a l'organització |
| Component | Unitat versionada amb inputs | El recomanable avui per a plantilles compartides |
| Pipeline fill | Un pipeline sencer | Monorepo, pipelines dinàmics |
trigger: project |
Pipeline d'un altre projecte | Multiprojecte, microserveis |
- El registre de contenidors integrat
Cada projecte de GitLab porta el seu propi registre de contenidors, i això elimina el tràmit de credencials que al ci.yml de Reservalia ocupa un job sencer:
publicar:
image: docker:27
services: [docker:27-dind]
script:
# Aquestes tres variables existeixen sense configurar-les: les injecta GitLab per job
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA" -f apps/api/Dockerfile .
- docker push "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA"CI_REGISTRY_PASSWORD és un token efímer amb permís només sobre el registre d'aquell projecte i amb la vida del job. És mínim privilegi (04-03) sense haver configurat res, i és un bon exemple del que regala la integració.
El registre inclou polítiques de neteja per edat i per patró d'etiqueta, que és la retenció de la 02-06 com a formulari. Sense elles, l'emmagatzematge creix sense fre: un pipeline que publica per commit genera desenes d'imatges al dia. Configura-les el primer dia i conserva sempre el que està referenciat per un entorn actiu, perquè esborrar la imatge que està desplegada converteix el rollback per digest de la 03-05 en impossible.
També hi ha registre de paquets (npm, Maven, PyPI…) i registre de dependències proxy, que resol el cas dels registres privats de la 04-02 sense muntar Nexus o Artifactory a part.
- Auto DevOps, amb criteri
Auto DevOps és un pipeline complet preconfigurat: detecta el llenguatge, construeix amb buildpacks, executa tests, anàlisi SAST, dependències, llicències, contenidor, DAST, i desplega a Kubernetes amb revisió, staging i producció amb canari. S'activa amb una casella.
Valoració honesta: és una demostració excel·lent i una base de producció discutible. A favor: per a un projecte nou i estàndard, en deu minuts tens un pipeline amb més portes de qualitat que moltes empreses després d'un any. En contra: el pipeline és una caixa negra gran que cal entendre per modificar, assumeix Kubernetes amb una topologia concreta, la construcció per buildpacks produeix imatges que no controles —i la 06-05 explicarà per què voldries controlar-les—, i tan bon punt necessites alguna cosa particular acabes sobreescrivint tantes variables que hauria sortit més barat escriure el pipeline.
Ús recomanat: com a catàleg d'idees i com a punt de partida a desactivar aviat. Activa'l, mira quins jobs genera, copia els que et serveixin al teu propi .gitlab-ci.yml i desactiva'l. El que sí que val la pena adoptar per separat són les plantilles de seguretat (include: template: Security/...), que són peces soltes, comprensibles i ajustables.
- SaaS davant d'autoallotjat, i el cost real
| GitLab.com (SaaS) | Autoallotjat | |
|---|---|---|
| Qui opera | GitLab | Tu |
| Model de cost | Per usuari i mes, amb minuts de còmput inclosos i consumibles addicionals | Llicència per usuari (segons nivell) més infraestructura més persones |
| Actualitzacions | Contínues | Teves, amb finestra i assaig |
| Dades | Fora de la teva xarxa | On tu decideixis |
| Runners | Compartits, o els teus | Els teus |
| Punt de fallada | El proveïdor | La teva instància |
Autoallotjar GitLab no és autoallotjar un servidor de CI: és operar una plataforma amb PostgreSQL, Redis, Gitaly (el servei de Git), emmagatzematge d'objectes, el registre i els runners. Les tasques recurrents reals: actualitzacions —GitLab publica a ritme alt i saltar-se versions intermèdies no sempre està suportat—, còpies de seguretat amb restauració assajada, creixement de l'emmagatzematge (els artefactes i el registre són els que es disparen), i dimensionament de runners. A la pràctica és com a mínim mitja persona a temps parcial en una organització mitjana, i no és un cost que desaparegui amb el temps.
Sobre les xifres: els preus per usuari, els minuts inclosos i quina funcionalitat és a quin nivell canvien amb freqüència i per regió. El que importa aquí és el model: pagues per usuari, no per projecte; el còmput es factura per minut amb multiplicadors segons el tipus de màquina; l'emmagatzematge d'artefactes i registre també compta. Consulta les tarifes vigents abans de decidir i, sobretot, verifica a quin nivell és la funcionalitat concreta sobre la qual vols construir abans de dissenyar res.
- Quan GitLab CI/CD és l'elecció correcta
Sí, amb força claredat, quan:
- El teu codi ja és a GitLab. És el factor que decideix la majoria dels casos i la 06-07 el posa el primer per una raó: fer servir un altre CI contra un repositori de GitLab significa sincronitzar identitats, webhooks i permisos per obtenir menys.
- Vols una plataforma en comptes d'una cadena d'eines, i valores no mantenir integracions ni conciliar permisos entre cinc productes.
- Necessites autoallotjar-ho tot per normativa, i vols una experiència integrada que Jenkins no dóna. És probablement el millor punt del quadrant autoallotjat.
- Els entorns i les review apps t'importen: el model d'
environmentamb historial,on_stopiauto_stop_inés madur i estalvia codi propi. - Treballes en monorepo i necessites pipelines dinàmics: pare-fill amb generació de YAML és una solució de primera per al problema de la 04-04.
No, o pensa-t'ho dues vegades, quan:
- El teu codi és a GitHub. Emmirallar repositoris per fer servir GitLab CI és una font permanent de fricció.
- El teu equip és petit i no vol operar res: SaaS ho resol, però el cost per usuari amb molts col·laboradors ocasionals pesa.
- Depens de funcionalitat de nivells alts: comprova el nivell abans de dissenyar, no després.
- Necessites molt macOS o maquinari exòtic: és viable amb runners propis, però l'ecosistema mòbil està més rodat en altres eines (06-03, 05-02).
Errors Comuns i Consells
Posar en memòria cau rutes fora de $CI_PROJECT_DIR. La memòria cau no es desa i no hi ha error visible, només lentitud. Redirigeix NPM_CONFIG_CACHE, GRADLE_USER_HOME o equivalents a l'espai de treball.
Tots els jobs amb policy: pull-push. Pugen una vegada i una altra la mateixa memòria cau. Només el job que la genera necessita escriure-la.
Pipelines duplicats als MR. Un push a una branca amb MR obert crea dos pipelines. workflow:rules ho arregla i estalvia la meitat del còmput.
Confondre cache amb artifacts. Posar dist/ en memòria cau produeix desplegaments amb un build d'una altra branca; posar node_modules a artifacts puja centenars de megabytes per job al servidor. La regla: si perdre-ho trenca el pipeline, és artefacte.
No posar expire_in. L'emmagatzematge creix fins que algú rep la factura o s'omple el disc.
Descarregar artefactes que no necessites. Per defecte arriben els de totes les etapes anteriors. Fes servir needs amb artifacts: false on només cal ordre.
Variables de desplegament sense marcar com a protegides. Qualsevol branca les pot llegir des d'un .gitlab-ci.yml modificat en un MR. És la via més directa d'exfiltració de credencials a GitLab.
Review apps sense on_stop ni auto_stop_in. Entorns orfes acumulant-se i facturant.
retry sense when. Reintentar-ho tot amaga les proves flaky i produeix verd fals (02-04).
include d'un altre projecte apuntant a main. Un canvi aliè modifica el teu pipeline sense revisió. Ancora a tag.
Runners privilegiats per costum. privileged = true només on de debò cal dind, i avalua alternatives sense dimoni (06-05).
Exercicis
Exercici 1. El pipeline de Reservalia a GitLab triga 19 minuts. Observes: l'etapa verificar acaba en 6 min però construir no comença fins al minut 9; cadascun dels sis jobs puja 400 MB de memòria cau; el job publicar descarrega 1,2 GB d'artefactes i només fa servir un fitxer digest; i un push a una branca amb MR obert llança dos pipelines. Escriu les correccions concretes i estima l'efecte de cadascuna.
Exercici 2. La Nuria vol review apps per a apps/web: un entorn per MR amb URL pròpia, comentari automàtic al MR, destrucció en tancar-lo i caducitat automàtica als tres dies. Escriu els jobs complets i explica què protegeix cada línia. Afegeix què fer amb els secrets, sabent que un MR pot venir d'una branca de qualsevol membre de l'equip.
Exercici 3. L'empresa que va comprar Gestor Citas 4 (05-04) té el seu codi a GitLab autoallotjat 14.x, amb pipelines que fan servir only/except, sense needs i amb un runner shell únic que ho executa tot a la màquina del servidor. Escriu el pla de modernització en fases, amb què es guanya a cadascuna i quins riscos hi ha.
Solucions
Solució 1.
| Problema | Causa | Correcció | Efecte estimat |
|---|---|---|---|
construir espera 3 min |
Barrera d'etapa: espera que acabi el job més lent de verificar |
needs: [preparar] a build-web i build-api |
−3 min de camí crític |
| 6 × 400 MB de pujada de memòria cau | Tots amb policy: pull-push |
Només preparar amb pull-push; la resta pull |
−2 GB de trànsit; ~1 min per job |
publicar descarrega 1,2 GB |
Descàrrega implícita d'artefactes d'etapes prèvies | needs explícit amb artifacts: false llevat del dotenv del build |
−1 a 2 min |
| Pipelines duplicats | Falta workflow:rules |
Regles de MR i branca per defecte, when: never a la resta |
−50 % de la despesa de còmput |
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- when: never
.node:
cache:
key: { files: [package-lock.json] }
paths: [.npm/]
policy: pull # només lectura
preparar:
extends: .node
cache:
key: { files: [package-lock.json] }
paths: [.npm/]
policy: pull-push # l'únic que escriu
script: [ "npm ci --prefer-offline" ]
build-api:
needs: [preparar] # no espera l'etapa verificar
# ...
publicar:
needs:
- job: build-api
artifacts: true # només el dotenv amb el digest
- job: build-web
artifacts: true # dist/ sí que cal
- job: qualitat
artifacts: false # només ordre
- job: test
artifacts: false
- job: seguretat
artifacts: falseEstimació total: de 19 min a al voltant de 12-13 al camí crític, i aproximadament la meitat del còmput facturat per l'arranjament dels pipelines duplicats. Aquest últim punt és el més rendible dels quatre i el que més es passa per alt, perquè no es manifesta com a lentitud sinó com a factura: és exactament la lliçó de la 04-04 que optimitzar comença per mesurar on se'n va el temps i els diners, no per endevinar.
Solució 2.
review:
stage: desplegar
needs: [build-web]
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes: [ "apps/web/**/*", "packages/compartit/**/*" ] # només si afecta la web
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.reservalia.example
on_stop: parar-review
auto_stop_in: 3 days
script:
- aws s3 sync apps/web/dist/ "s3://reservalia-review/$CI_COMMIT_REF_SLUG/" --delete
- |
curl -sS --request POST \
--header "PRIVATE-TOKEN: $TOKEN_BOT_MR" \
--data-urlencode "body=Previsualització llesta: $CI_ENVIRONMENT_URL (commit $CI_COMMIT_SHORT_SHA)" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
parar-review:
stage: desplegar
needs: []
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
environment: { name: review/$CI_COMMIT_REF_SLUG, action: stop }
variables: { GIT_STRATEGY: none }
script:
- aws s3 rm "s3://reservalia-review/$CI_COMMIT_REF_SLUG/" --recursiveQuè protegeix cada línia: changes evita aixecar entorns per canvis que no toquen la web; auto_stop_in acota el cost de les branques abandonades; on_stop garanteix neteja en tancar el MR; GIT_STRATEGY: none accelera la destrucció i redueix superfície; needs: [] fa que el job d'aturada no depengui de res i funcioni encara que el pipeline original hagués fallat.
Sobre secrets: l'entorn review/* ha de fer servir credencials pròpies i limitades —un rol que només pot escriure al prefix reservalia-review/ d'un bucket sense dades reals—, mai les de staging o producció. Les de producció van marcades com a protegides i el seu environment scope és produccio, així que ni existeixen en aquest job. TOKEN_BOT_MR és un token de projecte amb permís mínim de comentar. I la regla general que aplica igual que a la 04-03: un entorn de previsualització es construeix assumint que el seu contingut és públic, perquè la seva URL és endevinable i no porta autenticació real.
Solució 3. Quatre fases, cadascuna amb valor propi —l'enfocament de la 05-04—:
Fase 0, inventari i treure risc immediat (1-2 setmanes). El runner shell a la màquina del servidor és el problema urgent: qualsevol job executa ordres com a usuari del runner al mateix amfitrió que la instància, així que un .gitlab-ci.yml en un MR pot llegir la base de dades de GitLab. Es canvia primer, abans que cap millora de rendiment: runner nou amb executor docker en una màquina diferent, i el shell s'apaga. En paral·lel, inventari de variables sense marcar com a protegides —rotar les que hagin pogut ser exposades— i de projectes amb .gitlab-ci.yml.
Fase 1, actualitzar GitLab (2-4 setmanes de calendari). 14.x està molt endarrerit: cal pujar per les versions d'actualització obligatòries, no d'un salt, i assajar la restauració de la còpia en una instància de proves abans de tocar la real. Es guanya: seguretat, components de CI/CD, millores de rules i d'entorns. Risc principal: finestra d'indisponibilitat i migracions de base de dades llargues; es mitiga assajant sobre una còpia i anunciant la finestra.
Fase 2, only/except → rules (1 setmana, projecte a projecte). only/except continua funcionant però no es pot combinar amb rules al mateix job i no expressa condicions compostes. La traducció és mecànica:
| Antic | Modern |
|---|---|
only: [main] |
rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH |
only: [merge_requests] |
rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" |
only: changes: [src/**] |
rules: - changes: [src/**] |
except: [tags] |
rules: - if: $CI_COMMIT_TAG when: never + regla positiva |
Es fa un projecte de prova primer i es compara que el conjunt de pipelines disparats és el mateix abans i després, que és l'única verificació que de debò demostra l'equivalència.
Fase 3, needs i memòria cau compartida (1-2 setmanes). Afegir needs per passar d'etapes a graf, i configurar memòria cau a S3 compartida entre runners. Es guanya temps de pipeline. Va l'última a propòsit: és la fase més visible i la menys important, i posar-la abans que la fase 0 seria optimitzar la velocitat d'un sistema amb un forat de seguretat obert. Aquest ordre —risc, després mantenibilitat, després rendiment— és el mateix que governava els increments de la 05-04.
Conclusió
GitLab CI/CD és el que passa quan el CI es dissenya dins de la plataforma en comptes de al costat. El .gitlab-ci.yml va néixer sent pipeline as code, sense l'herència de la interfície que arrossega Jenkins; el model va evolucionar d'etapes seqüencials a graf amb needs sense trencar el que hi havia; i la integració regala coses que en altres cadenes costen feina: credencials de registre per job, informes de test i seguretat dins del merge request, entorns amb historial i rollback, i review apps amb caducitat en una línia. A canvi es paga acoblament —sortir és sortir de tot—, funcionalitat esglaonada per nivell de subscripció, i una operació considerable si te l'autoallotges.
Del que hem vist, tres coses s'enduen a qualsevol altra eina: la distinció memòria cau davant d'artefacte —si perdre-ho trenca el pipeline, és artefacte— és universal encara que el nom canviï; marcar com a protegides les credencials de desplegament és la defensa concreta contra el MR maliciós, i la seva absència és un forat real en moltes instal·lacions; i ancorar els include a una versió és la mateixa regla que les Shared Libraries de Jenkins i les accions fixades per SHA de la 04-03.
L'eina següent ataca una dimensió diferent. Allà on GitLab competeix per amplitud, CircleCI competeix per profunditat en una sola cosa: la velocitat del pipeline. Porta la memòria cau més explícita i controlable del mòdul, un sistema de reutilització empaquetat —els orbs— i, sobretot, 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. Veurem el pipeline de Reservalia per tercera vegada i què es paga per aquesta velocitat.
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
