Les quatre lliçons anteriors comparaven eines de CI/CD entre elles. Aquesta no, perquè Docker i Kubernetes no són eines de CI/CD: en són el substrat. Docker apareix dues vegades en qualsevol pipeline modern i amb papers completament diferents —és l'entorn on corre el pipeline i és el format de l'artefacte que es desplega—, i confondre aquests dos papers produeix imatges de producció d'1,2 GB que contenen el compilador, els tests i les credencials del build. Kubernetes, per la seva banda, és el destí més comú avui i canvia la forma del CD: el model de push que Reservalia fa servir contra ECS té un equivalent natural a Kubernetes, però la comunitat es va moure majoritàriament cap a GitOps, que inverteix qui inicia el desplegament. El curs ha fet servir tots dos des del mòdul 2 sense baixar al detall; aquesta lliçó hi baixa. I acaba amb la pregunta que gairebé mai no es fa en veu alta: quan NO necessites Kubernetes.
Contingut
- Els dos papers de Docker en un pipeline
- BuildKit: memòries cau de muntatge, multietapa i secrets
- Memòria cau de capes al registre i builds multiplataforma
- Imatges mínimes, no-root,
HEALTHCHECKi etiquetes OCI - Per què el digest mana sobre el tag
- Construir imatges dins del pipeline: DinD, socket i constructors sense dimoni
- Kubernetes com a destí: els objectes mínims
- Sondes i el seu paper al rolling update
- Rollback:
kubectl rollout statusiundo - Parametritzar per entorn: Helm i Kustomize
- Push davant de pull: per què a Kubernetes triomfa GitOps
- Reservalia a Kubernetes, contrastat amb ECS
- Kubernetes com a plataforma per als propis executors
- Quan NO necessites Kubernetes
- Errors Comuns i Consells
- Exercicis
- Conclusió
- Els dos papers de Docker en un pipeline
flowchart LR
subgraph P["Paper A · entorn del pipeline"]
R1["Job qualitat<br/>contenidor node:22"]
R2["Job test<br/>contenidor node:22 + postgres"]
R3["Job build<br/>contenidor amb buildx"]
end
subgraph A["Paper B · format de lartefacte"]
IM["Imatge de runtime<br/>distroless, no-root, 90 MB"]
REG["Registre ECR<br/>identificada per digest"]
DEST["ECS / Kubernetes"]
end
R3 -->|"docker build --push"| IM --> REG --> DEST
| Paper A: entorn del pipeline | Paper B: artefacte desplegable | |
|---|---|---|
| Què és | La imatge on s'executen els steps | La imatge que es desplega a producció |
| Qui la tria | L'image: del job, el container: o l'executor |
El teu Dockerfile |
| Què conté | Compilador, gestor de paquets, eines, CLIs | Només el necessari per executar |
| Mida raonable | 500 MB - 1,5 GB, tant li fa | Com més petita, millor |
| Cicle de vida | Mor amb el job | Viu mesos a producció |
| Criteri de seguretat | Aïllament del job | Superfície d'atac mínima |
| Objectiu | Reproductibilitat del build | Reproductibilitat del desplegament + seguretat |
El paper A és el que dona reproductibilitat al pipeline: un job que corre a node:22.11-bookworm es comporta igual avui que d'aquí a sis mesos, independentment de què tingui instal·lat la màquina que l'executa. És la resposta a l'agent contaminat de la 06-01, i per això totes les eines modernes el fan servir per defecte.
El paper B és l'artefacte immutable de la 02-06. La confusió entre tots dos produeix l'antipatró més comú de tot el tema: fer servir la mateixa imatge per construir i per executar. El resultat és una imatge de producció que inclou el compilador de TypeScript, devDependencies, l'historial de Git i, amb sort, cap fitxer de credencials del build. La separació es fa amb multietapa, que el curs va introduir a la 02-03 i que aquí portem al detall.
- BuildKit: memòries cau de muntatge, multietapa i secrets
BuildKit és el motor de construcció modern (per defecte en versions recents de Docker i a buildx). Aporta tres coses que canvien de veritat un pipeline: construcció paral·lela del graf d'etapes, memòries cau de muntatge i secrets que no queden a la imatge.
# syntax=docker/dockerfile:1.7 # 1 · habilita la sintaxi de BuildKit
# apps/api/Dockerfile — Reservalia
# ---------- etapa de dependències ----------
FROM node:22.11-bookworm AS deps
WORKDIR /app
COPY package.json package-lock.json ./
COPY apps/api/package.json apps/api/
COPY packages/compartit/package.json packages/compartit/
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
npm ci --prefer-offline # 2 · memòria cau persistent entre builds
# ---------- etapa de construcció ----------
FROM deps AS build
COPY . .
RUN --mount=type=cache,target=/app/.tsbuildinfo \
npm run build --workspace apps/api
RUN npm ci --omit=dev --prefer-offline # 3 · deixa només dependències de producció
# ---------- etapa de runtime ----------
FROM gcr.io/distroless/nodejs22-debian12:nonroot AS runtime # 4
WORKDIR /app
COPY --from=build --chown=nonroot:nonroot /app/node_modules ./node_modules
COPY --from=build --chown=nonroot:nonroot /app/apps/api/dist ./dist
USER nonroot # 5
EXPOSE 3000
ENV NODE_ENV=production
LABEL org.opencontainers.image.source="https://github.com/reservalia/reservalia" \
org.opencontainers.image.revision="${GIT_SHA}" \
org.opencontainers.image.licenses="UNLICENSED" # 6
ENTRYPOINT ["/nodejs/bin/node", "dist/servidor.js"]- La línia
# syntax=no és un comentari: li diu a BuildKit quina versió del frontend ha de fer servir, i és el que habilita--mount. Sense ella, aquestes línies fallen. --mount=type=cacheés la millora més rendible i la menys coneguda. La memòria cau d'npm es munta durant elRUNi no queda a la imatge, però persisteix entre builds al constructor. Diferència respecte del truc clàssic de "copiar noméspackage.jsonprimer per aprofitar la memòria cau de capes": aquest funciona encara que el lockfile canviï, perquè no depèn d'invalidar una capa sinó de tenir els tarballs ja descarregats.sharing=lockedevita corrupció amb builds concurrents.npm ci --omit=devdesprés de construir és el que evita portardevDependenciesal runtime. És un estalvi habitual de centenars de megabytes.- Imatge de runtime diferent i mínima: apartat 4.
USER nonroot: apartat 4.- Etiquetes OCI: apartat 4.
I els secrets en temps de construcció, que és on més gent s'equivoca:
# MALAMENT: el token queda a l'historial de la imatge per sempre
ARG NPM_TOKEN
RUN echo "//registre.example/:_authToken=${NPM_TOKEN}" > .npmrc && npm ci && rm .npmrc
# ↑ esborrar el fitxer després NO l'elimina: la capa anterior el conté
# BÉ: muntatge de secret, no persisteix a cap capa
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --prefer-offlineLa regla és la de la 04-03 amb un mecanisme concret: ARG i ENV amb secrets són visibles amb docker history per a qualsevol que tingui la imatge. Esborrar el fitxer en un RUN posterior no serveix: les capes són immutables i acumulatives. --mount=type=secret és l'única forma correcta.
- Memòria cau de capes al registre i builds multiplataforma
En un executor efímer no hi ha memòria cau local de capes: cada build comença de zero. La solució és guardar la memòria cau al registre, que és el que el ci.yml de Reservalia ja feia i aquí expliquem.
docker buildx build \
--file apps/api/Dockerfile \
--cache-from type=registry,ref=$ECR/reservalia/api:cache \
--cache-to type=registry,ref=$ECR/reservalia/api:cache,mode=max \
--tag $ECR/reservalia/api:$GIT_SHA \
--push .| Mode de memòria cau | Què guarda | Quan |
|---|---|---|
mode=min (per defecte) |
Només les capes de la imatge final | Poc útil amb multietapa: perd les etapes intermèdies |
mode=max |
Capes de totes les etapes, incloses deps i build |
El que vols gairebé sempre; ocupa més al registre |
type=gha |
El magatzem de memòria cau de GitHub Actions | Còmode a Actions; subjecte als seus límits de mida i desallotjament |
type=inline |
La memòria cau va dins de la mateixa imatge | Simple; només mode=min a la pràctica |
Dos avisos. La memòria cau al registre també cal netejar-la: és una etiqueta més que creix, i amb mode=max creix bastant. I una memòria cau de capes enverinada és un vector real: si algú amb accés d'escriptura al registre substitueix la memòria cau, els teus builds poden incorporar capes alienes. Els permisos d'escriptura sobre el repositori de memòria cau han de ser els mateixos que sobre les imatges (04-03).
Multiplataforma amb buildx, rellevant des que els executors ARM són més barats i els portàtils de desenvolupament són ARM:
docker buildx create --name multi --driver docker-container --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag $ECR/reservalia/api:$GIT_SHA \
--push .Això produeix una llista de manifestos: una única etiqueta que apunta a dues imatges, i el destí es descarrega la que correspon a la seva arquitectura. Contrapartides honestes: si l'executor és amd64, la variant arm64 es construeix per emulació amb QEMU i pot trigar entre tres i deu vegades més; l'alternativa és construir cada arquitectura en un executor natiu i unir els manifestos amb docker buildx imagetools create, que és més ràpid i més complex. I si la teva aplicació té dependències amb binaris natius, cal provar de veritat a les dues arquitectures: compilar no és funcionar.
- Imatges mínimes, no-root,
HEALTHCHECK i etiquetes OCI
HEALTHCHECK i etiquetes OCI| Base | Mida típica | Té shell | Superfície | Quan |
|---|---|---|---|---|
node:22 (Debian completa) |
~1,1 GB | Sí | Alta | Només per construir |
node:22-slim |
~200 MB | Sí | Mitjana | Runtime acceptable, fàcil de depurar |
node:22-alpine |
~130 MB | Sí (ash) |
Baixa | Runtime petit; compte amb musl davant de glibc |
gcr.io/distroless/nodejs22 |
~110 MB | No | Molt baixa | Runtime de producció |
scratch |
0 | No | Mínima | Binaris estàtics (Go, Rust) |
La diferència important no és la mida, és què hi ha a dins. Una imatge distroless no té shell, ni gestor de paquets, ni curl, ni utilitats: si algú aconsegueix execució dins del contenidor, no té amb què treballar. També redueix dràsticament el soroll dels escàners de vulnerabilitats: la majoria dels CVE que apareixen en una imatge Debian completa són en paquets que la teva aplicació no fa servir mai, i aquest soroll és el que fa que els equips deixin de mirar els informes de la 04-03.
Contrapartida real i cal dir-la: depurar en distroless és incòmode. No pots fer kubectl exec -it ... -- sh perquè no hi ha sh. Les respostes: contenidors efímers de depuració (kubectl debug --image=busybox --target=api) que s'adjunten al Pod sense modificar la imatge, i bona observabilitat (03-06) per no necessitar entrar-hi. Molts equips fan servir -slim a staging i distroless a producció, cosa que és un compromís defensable encara que trenca lleugerament la regla del "mateix artefacte a tots els entorns".
Usuari no-root: per defecte un contenidor corre com a root, i encara que l'aïllament del contenidor limita el dany, un procés root que s'escapi té molt més marge. A Kubernetes això es reforça amb la política del Pod:
securityContext:
runAsNonRoot: true # el Pod no arrenca si la imatge corre com a root
runAsUser: 65532
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true # obliga a declarar volums per al que sigui escrivible
capabilities: { drop: ["ALL"] }HEALTHCHECK al Dockerfile és útil amb Docker o Compose; a Kubernetes l'orquestrador l'ignora, perquè fa servir les seves pròpies sondes (apartat 8). Posar-lo no sobra —documenta com es comprova que el procés està sa— però no hi confiïs com a mecanisme en un clúster.
Etiquetes OCI: són metadades estàndard que fan traçable la imatge. org.opencontainers.image.source connecta la imatge amb el seu repositori i revision amb el commit exacte. La pregunta "de quin commit va sortir el que hi ha a producció?" —primera pregunta de qualsevol incident, segons la 03-05— es respon amb docker inspect en comptes d'amb arqueologia.
- Per què el digest mana sobre el tag
Ja es va establir a la 02-06; aquí el mecanisme:
# Una etiqueta és un punter MUTABLE
docker push reservalia/api:v1.4.0 # apunta a sha256:aaa...
docker push reservalia/api:v1.4.0 # ara apunta a sha256:bbb... i ningú no se n'assabenta
# Un digest és el hash del manifest: IMMUTABLE per construcció
docker pull reservalia/api@sha256:aaa1b2c3...Conseqüències pràctiques que cal interioritzar:
FROM node:22al teuDockerfileno és reproduïble. Aquesta etiqueta apunta a imatges diferents amb el temps. Per a builds veritablement reproduïbles:FROM node:22.11-bookworm@sha256:.... Contrapartida: cal actualitzar-ho, i aquí entra l'automatització de dependències de la 04-02, que també renova digests d'imatges base.- Desplegar per etiqueta introdueix una cursa: entre que el pipeline construeix
:v1.4.0i l'orquestrador se la descarrega, aquesta etiqueta pot apuntar a una altra cosa. Per això elcd.ymlde Reservalia desplega per digest. - La promoció entre entorns és moure un digest, no reconstruir. Staging i producció executen el mateix
sha256:exacte, i això és el que fa que "funcionava a staging" signifiqui alguna cosa. - Els registres permeten etiquetes immutables (ECR: tag immutability), cosa que impedeix sobreescriure una etiqueta existent. Activa-ho: tanca la porta a un error humà amb conseqüències greus.
- Construir imatges dins del pipeline: DinD, socket i constructors sense dimoni
Construir una imatge des d'un job que ja corre en un contenidor és un problema real amb tres solucions i tres perfils de risc diferents. És una de les decisions de seguretat més importants d'un pipeline i gairebé sempre es pren per inèrcia.
| Enfocament | Com funciona | Risc | Rendiment |
|---|---|---|---|
| Docker-in-Docker (DinD) | Un dimoni Docker complet dins del contenidor del job, en mode privilegiat | Alt: --privileged equival a accés al kernel de l'amfitrió; escapar-se del contenidor és factible |
Memòria cau freda cada vegada excepte muntatge de volum |
| Socket de l'amfitrió muntat | Es munta /var/run/docker.sock de l'amfitrió |
Molt alt: accés al dimoni de l'amfitrió = root a l'amfitrió, i visibilitat dels contenidors d'altres jobs | Bo: memòria cau compartida |
| Kaniko | Construeix en espai d'usuari, sense dimoni | Baix | Acceptable; memòria cau al registre |
| Buildah | Constructor sense dimoni, pot córrer rootless | Baix | Bo |
| BuildKit rootless | Dimoni de BuildKit sense privilegis | Baix | Molt bo; admet --mount=type=cache |
El raonament sobre per què això importa: en un executor compartit, un job amb el socket de l'amfitrió muntat pot llistar i manipular els contenidors d'altres jobs, inclosos els que tenen credencials de producció al seu entorn. No cal un atacant extern: n'hi ha prou amb un .gitlab-ci.yml o un flux de treball modificat en un PR. És l'escalada de privilegis més accessible en un CI mal configurat.
# Kaniko en un job de GitLab CI: sense dimoni, sense privilegis
build:
image:
name: gcr.io/kaniko-project/executor:v1.23.2-debug
entrypoint: [""]
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/apps/api/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}/api:${CI_COMMIT_SHA}"
--cache=true
--cache-repo "${CI_REGISTRY_IMAGE}/cache"
--image-name-with-digest-file /tmp/digest.txt # el digest, per promocionar
artifacts:
paths: [ /tmp/digest.txt ]# BuildKit rootless com a contenidor secundari en un Pod d'agent
containers:
- name: buildkit
image: moby/buildkit:v0.16.0-rootless
args: ["--addr", "unix:///run/user/1000/buildkit/buildkitd.sock", "--oci-worker-no-process-sandbox"]
securityContext:
runAsUser: 1000
seccompProfile: { type: Unconfined } # requisit de rootless, no privilegi totalRecomanació pràctica: si l'executor és compartit, no facis servir DinD privilegiat ni muntis el socket de l'amfitrió. Kaniko o BuildKit rootless cobreixen el cas normal. DinD és acceptable en executors efímers d'un sol ús i d'un sol equip, on el radi del compromís és el mateix job. I a GitHub Actions amb executors allotjats el problema no es planteja igual, perquè la màquina és efímera i d'un sol job —cosa que és, per cert, un avantatge de disseny dels executors allotjats que no sempre s'aprecia—.
- Kubernetes com a destí: els objectes mínims
No cal saber Kubernetes sencer per desplegar-hi. Aquests són els objectes que sí que cal entendre:
flowchart TD
D["Deployment<br/>estat desitjat: 4 repliques, imatge X"] --> RS["ReplicaSet<br/>un per versio"]
RS --> P1["Pod"]
RS --> P2["Pod"]
SVC["Service<br/>IP estable + balanceig"] --> P1
SVC --> P2
ING["Ingress<br/>encaminament HTTP extern"] --> SVC
CM["ConfigMap<br/>configuracio no sensible"] -.-> P1
SEC["Secret<br/>credencials"] -.-> P1
| Objecte | Què és | Equivalent mental a ECS |
|---|---|---|
| Pod | Un o diversos contenidors que comparteixen xarxa i cicle de vida | Una tasca |
| ReplicaSet | Manté N Pods idèntics vius | — (intern) |
| Deployment | Declara l'estat desitjat i gestiona el reemplaçament creant ReplicaSets | Servei ECS |
| Service | IP i DNS estables amb balanceig cap als Pods sans | Target group |
| Ingress | Encaminament HTTP extern, TLS, hosts i rutes | ALB amb regles |
| ConfigMap | Configuració no sensible | Variables d'entorn de la definició de tasca |
| Secret | Dades sensibles (codificades en base64, no xifrades per defecte) | Secrets Manager / SSM |
# k8s/deployment.yaml — Reservalia API
apiVersion: apps/v1
kind: Deployment
metadata:
name: reservalia-api
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 1 · no baixar mai de la capacitat actual
maxSurge: 1 # 1 Pod extra durant el reemplaçament
selector:
matchLabels: { app: reservalia-api }
template:
metadata:
labels: { app: reservalia-api }
spec:
securityContext:
runAsNonRoot: true
seccompProfile: { type: RuntimeDefault }
containers:
- name: api
# 2 · per digest, no per etiqueta
image: 123456789012.dkr.ecr.eu-west-1.amazonaws.com/reservalia/api@sha256:aaa1b2c3...
ports: [ { containerPort: 3000 } ]
envFrom:
- configMapRef: { name: reservalia-api-config }
- secretRef: { name: reservalia-api-secrets }
resources:
requests: { cpu: "250m", memory: "256Mi" } # 3
limits: { cpu: "1", memory: "512Mi" }
startupProbe: # 4
httpGet: { path: /salut/viu, port: 3000 }
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet: { path: /salut/llest, port: 3000 }
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet: { path: /salut/viu, port: 3000 }
periodSeconds: 10
failureThreshold: 3
lifecycle:
preStop: # 5
exec: { command: ["sleep", "5"] }
terminationGracePeriodSeconds: 30maxUnavailable: 0ambmaxSurge: 1és l'ajust correcte per no perdre capacitat durant el desplegament: primer es crea el Pod nou, s'espera que estigui llest i només llavors se'n retira un de vell. AmbmaxUnavailable: 1(el valor per defecte reparteix d'una altra manera) pots quedar-te per sota de la capacitat necessària en hora punta.- Imatge per digest, pel que s'ha dit a l'apartat 5. A més, amb digest no cal
imagePullPolicy: Always: l'identificador ja és únic. requestsilimitsno són opcionals. Senserequests, el planificador no sap què reservar i col·loca malament; senselimits, un Pod amb una fuita de memòria pot tombar el node. I un detall que mossega: superar el límit de memòria mata el contenidor (OOMKilled) sense avís, mentre que superar el de CPU només l'alenteix.- Sondes: apartat 8.
preStopamb una pausa curta resol una cursa real: quan un Pod entra en terminació, Kubernetes li enviaSIGTERMalhora que el treu dels endpoints del Service, i aquestes dues coses no estan sincronitzades. Sense la pausa, algunes peticions arriben a un Pod que ja s'està tancant. Cinc segons eliminen la majoria dels 502 durant els desplegaments.
- Sondes i el seu paper al rolling update
Les tres sondes es confonen constantment i els seus efectes són molt diferents:
| Sonda | Pregunta que respon | Si falla | Efecte al rolling update |
|---|---|---|---|
| startupProbe | Ha acabat d'arrencar? | Reinicia el contenidor | Suspèn les altres dues mentre corre: protegeix arrencades lentes |
| readinessProbe | Pot atendre trànsit ara? | El treu del Service, sense reiniciar | Decisiva: el rollout no avança fins que el Pod nou està llest |
| livenessProbe | Continua viu o està penjat? | Reinicia el contenidor | Pot provocar reinicis en bucle si està mal configurada |
La sonda de disponibilitat és la que governa el desplegament i és la que fa que el rolling update de la 03-04 sigui segur: mentre el Pod nou no respongui 200 a /salut/llest, Kubernetes no retira cap Pod vell. Si l'aplicació arrenca trencada, el rollout es queda aturat amb la versió anterior servint: la fallada es conté sola.
Dos errors de configuració amb conseqüències greus:
La sonda de vida que comprova dependències. Si /salut/viu consulta la base de dades, una caiguda de la base de dades fa fallar la sonda a tots els Pods, Kubernetes els reinicia tots alhora, i a un incident de base de dades li sumes una caiguda total de l'aplicació amb reinicis en bucle. Regla: vida = "el procés respon"; disponibilitat = "puc atendre peticions, incloses les meves dependències".
Sonda de vida sense sonda d'arrencada en aplicacions lentes. Si l'aplicació triga 40 s a arrencar i la sonda de vida comença als 10 s amb llindar de 3 fallades, el contenidor es reinicia abans d'acabar d'arrencar, per sempre. La startupProbe existeix exactament per a això.
- Rollback:
kubectl rollout status i undo
kubectl rollout status i undo# Desplegar: canviar la imatge del Deployment (per digest)
kubectl set image deployment/reservalia-api \
api=$ECR/reservalia/api@sha256:aaa1b2c3... --record
# Esperar i FALLAR si no convergeix: això és el que fa que el job de CD sigui honest
kubectl rollout status deployment/reservalia-api --timeout=5m
# Historial i tornada enrere
kubectl rollout history deployment/reservalia-api
kubectl rollout undo deployment/reservalia-api # a la revisió anterior
kubectl rollout undo deployment/reservalia-api --to-revision=7kubectl rollout status amb --timeout és la línia que converteix un desplegament en una verificació: si els Pods nous no arriben a estar llestos, l'ordre retorna error i el job de CD falla, en comptes de donar verd i deixar un rollout encallat que ningú no mira. És exactament el que la 03-02 demanava del desplegament idempotent i verificat.
I el rollback aquí és més ràpid que a ECS: kubectl rollout undo restaura el ReplicaSet anterior, les imatges del qual ja estan descarregades als nodes, així que en segons hi ha Pods vells servint. Els 4 minuts de rollback per digest de Reservalia (03-05) baixarien bastant.
Dos matisos honestos. undo reverteix la plantilla del Pod, no l'estat del món: si el desplegament va incloure una migració de base de dades, revertir la imatge no reverteix la migració, i allà continua manant expand and contract (04-06). I --record està en desús; en un flux GitOps l'historial real és a Git, que és millor lloc que les anotacions de l'objecte.
- Parametritzar per entorn: Helm i Kustomize
El mateix Deployment ha d'existir a dev, staging i producció amb diferent nombre de rèpliques, diferents recursos i diferent configuració. Dos enfocaments dominants.
Helm — plantilles amb variables:
# helm/reservalia-api/templates/deployment.yaml
spec:
replicas: {{ .Values.repliques }}
template:
spec:
containers:
- name: api
image: "{{ .Values.imatge.repositori }}@{{ .Values.imatge.digest }}"
resources:
{{- toYaml .Values.recursos | nindent 12 }}# helm/reservalia-api/values-produccio.yaml
repliques: 6
recursos:
requests: { cpu: "500m", memory: "512Mi" }
limits: { cpu: "2", memory: "1Gi" }helm upgrade --install reservalia-api ./helm/reservalia-api \
--namespace produccio \
--values helm/reservalia-api/values-produccio.yaml \
--set imatge.digest="sha256:aaa1b2c3..." \
--atomic \ # si falla, reverteix automàticament a l'estat anterior
--wait \ # espera que els recursos estiguin llestos
--timeout 10m--atomic --wait és la combinació que converteix helm upgrade en un desplegament amb rollback automàtic incorporat: si els Pods no arriben a estar llestos dins del termini, Helm desfà el canvi. És la línia més valuosa de l'ordre.
Kustomize — superposicions sobre una base, sense plantilles:
# k8s/overlays/produccio/kustomization.yaml
resources: [ ../../base ]
namespace: produccio
replicas:
- name: reservalia-api
count: 6
images:
- name: reservalia/api
digest: sha256:aaa1b2c3...
patches:
- path: recursos.yaml
target: { kind: Deployment, name: reservalia-api }
configMapGenerator:
- name: reservalia-api-config
literals: [ "LOG_LEVEL=info", "ENTORN=produccio" ]| Helm | Kustomize | |
|---|---|---|
| Mecanisme | Plantilles Go + valors | Pedaçat declaratiu de YAML |
| Els fitxers base són… | Plantilles: no són YAML vàlid | YAML vàlid i aplicable tal com està |
| Corba d'aprenentatge | Mitjana-alta (funcions, nindent, condicionals) |
Baixa |
| Distribuir a tercers | Excel·lent: repositoris de charts, versionat | Pobra |
| Instal·lar programari de tercers | Estàndard de facto | Poc pràctic |
| Gestió d'estat | Guarda releases i historial; helm rollback |
Cap: l'estat és el que hi ha al clúster |
| Llegibilitat en créixer | Empitjora: lògica a les plantilles | Bona, fins que hi ha overlays d'overlays |
Integrat a kubectl |
No | Sí (-k) |
A la pràctica, molts equips fan servir tots dos: Helm per instal·lar programari de tercers (ingress, monitoratge, operadors) i Kustomize per a les seves pròpies aplicacions. És una combinació defensable i molt comuna. L'antipatró que cal evitar és un chart propi amb tantes condicionals que cal llegir plantilles Go per saber què es desplega.
- Push davant de pull: per què a Kubernetes triomfa GitOps
El cd.yml de Reservalia fa servir push: el pipeline té credencials de l'entorn i executa el desplegament.
flowchart LR
subgraph PUSH["Model push · el cd.yml de Reservalia"]
CI1["Pipeline"] -->|"credencials de prod"| K1["Cluster / ECS"]
end
subgraph PULL["Model pull · GitOps"]
CI2["Pipeline"] -->|"commit: nou digest"| G["Repositori de desplegaments"]
AG["Agent dins del cluster<br/>Argo CD / Flux"] -->|"llegeix cada 3 min"| G
AG -->|"aplica des de dins"| K2["Cluster"]
end
| Push | Pull (GitOps) | |
|---|---|---|
| Qui inicia | El pipeline | Un agent dins del clúster |
| Credencials del clúster | Al CI | No surten mai del clúster |
| Estat desitjat | Implícit a l'últim desplegament | Explícit a Git |
| Deriva de configuració | Invisible | Detectada i corregida |
| Rollback | Tornar a executar amb el digest anterior | git revert |
| Auditoria | Logs del pipeline | Historial de Git |
| Multiclúster | Un job per clúster | Un agent per clúster, mateixa font |
| Complexitat | Baixa | Mitjana: un altre component per operar |
| Latència del desplegament | Immediata | Segons a minuts (o immediata amb webhook) |
Les dues raons per les quals GitOps va guanyar a Kubernetes, i cap no és moda:
1. Les credencials d'administrador d'un clúster són massa poderoses per deixar-les al CI. kubectl apply requereix permisos amplis; si el CI els té, comprometre el CI és comprometre el clúster. Amb GitOps el pipeline només necessita permís per fer commit en un repositori, que és un privilegi molt menor.
2. Kubernetes és declaratiu, i això encaixa de manera natural amb Git. L'estat desitjat és un conjunt de manifestos; posar-los a Git i que un agent els reconciliï contínuament és l'extensió òbvia. I porta una capacitat que el push no té: detecció de deriva. Si algú fa kubectl edit a les tres de la matinada durant un incident, l'agent ho detecta i ho reverteix o ho assenyala. El "per què producció no és el que diu el repositori?" deixa d'existir.
# El pipeline a GitOps: no desplega, escriu l'estat desitjat
actualitzar-manifestos:
needs: [publicar]
script:
- git clone https://github.com/reservalia/desplegaments.git && cd desplegaments
- |
cd overlays/produccio
kustomize edit set image reservalia/api@${DIGEST}
- git commit -am "api: ${DIGEST} (des de ${CI_COMMIT_SHA})"
- git push
# Aquí acaba el pipeline. Argo CD detecta el commit i aplica.# L'Application d'Argo CD, que viu al clúster
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: reservalia-api-produccio }
spec:
source:
repoURL: https://github.com/reservalia/desplegaments.git
path: overlays/produccio
targetRevision: main
destination: { server: https://kubernetes.default.svc, namespace: produccio }
syncPolicy:
automated: { prune: true, selfHeal: true } # selfHeal: corregeix la deriva
retry: { limit: 3 }Contrapartides honestes de GitOps: hi ha un component més per operar i actualitzar; el desplegament deixa de ser immediat llevat que configuris un webhook; el rastre d'un canvi travessa dos repositoris, cosa que complica respondre "quin commit de l'aplicació hi ha a producció?" si no s'automatitza bé; i no resol les estratègies de desplegament per si sol —per a canari o blue-green calen Argo Rollouts o Flagger, que és una altra peça més—. La 05-03 ja ho va introduir en el context de microserveis; aquí en queda el perquè.
- Reservalia a Kubernetes, contrastat amb ECS
Exercici de contrast, no de migració: Reservalia funciona bé a ECS i la 06-07 dirà per què això importa.
| Aspecte | ECS Fargate (avui) | Kubernetes (hipotètic) |
|---|---|---|
| Definició del servei | Task definition (JSON) + Service | Deployment + Service + Ingress |
| Qui opera el pla de control | AWS, invisible | AWS (EKS) o tu; visible en qualsevol cas |
| Rolling update | Natiu del servei ECS | Natiu del Deployment |
| Canari al 10 % | Pesos al listener de l'ALB | Argo Rollouts / Flagger / malles de serveis |
| Rollback | Tornar a desplegar el digest anterior: ~4 min | kubectl rollout undo: segons |
| Configuració | SSM Parameter Store a la task definition | ConfigMap + Secret (o operador de secrets externs) |
| Autoescalat | Application Auto Scaling | HPA (+ Karpenter o Cluster Autoscaler per als nodes) |
| Cost d'operació | Baix | Mitjà-alt: actualitzacions del clúster, complements, CRDs |
| Coneixement necessari a l'equip | Baix | Alt |
| Portabilitat entre núvols | Nul·la | Alta (amb matisos: els complements solen ser específics) |
El que Reservalia guanyaria: rollback gairebé instantani, portabilitat, un ecosistema enorme d'operadors, millors eines de desplegament progressiu, i GitOps amb la reducció de privilegis del CI que comporta.
El que pagaria: un clúster que cal actualitzar diverses vegades l'any, complements que cal mantenir (ingress, gestor de certificats, escalat, observabilitat), l'operació d'Argo CD, i una corba d'aprenentatge real per a un equip de tres persones. En Diego preguntaria quant costa i tindria raó: amb 340 negocis i ~9.000 cites al mes, la resposta honesta és que el rollback de 4 minuts que ja tenen no és el coll d'ampolla de res.
Quan canviaria la resposta: si apareguessin els cinc serveis de la 05-03 amb equips independents, si hi hagués un requisit multinúvol, o si l'equip de plataforma creixés prou perquè operar el clúster no surti del temps de producte.
- Kubernetes com a plataforma per als propis executors
Un ús que de vegades s'oblida: el clúster pot allotjar els agents de CI, no només l'aplicació. Ja va aparèixer amb el plugin de Kubernetes de Jenkins (06-01) i amb l'executor kubernetes de GitLab (06-02); a GitHub Actions és ARC (Actions Runner Controller), que la 06-06 detalla.
# Un agent efímer: es crea en començar el job, es destrueix en acabar
apiVersion: v1
kind: Pod
spec:
restartPolicy: Never
containers:
- name: runner
image: node:22-bookworm
resources:
requests: { cpu: "1", memory: "2Gi" }
limits: { cpu: "2", memory: "4Gi" }
- name: buildkit
image: moby/buildkit:v0.16.0-rootless
securityContext: { runAsUser: 1000 }Què aporta: entorn net per job —adeu a l'agent contaminat—, autoescalat de franc (ho fa el clúster), aprofitament de capacitat ociosa, i aïllament per espai de noms entre equips.
Què costa, amb honestedat: un Pod triga desenes de segons a arrencar i descarregar imatges, cosa que empitjora el temps de cua si no es cuida (imatges precarregades als nodes, o un petit grup de Pods calents); la memòria cau ja no persisteix per definició, així que cal recolzar-se en memòries cau remotes; i continua sent un clúster per operar. No executis mai agents de CI al mateix clúster que producció sense aïllament fort: un job és codi arbitrari, i compartir nodes amb producció converteix qualsevol escapada en un incident greu.
- Quan NO necessites Kubernetes
Aquest apartat és tan important com els anteriors i s'omet massa vegades.
Probablement no el necessites si:
- Tens menys de cinc serveis. Kubernetes resol la coordinació de molts serveis; amb pocs, aporta sobretot complexitat. Reservalia n'és l'exemple.
- El teu equip no té algú que sàpiga operar-lo. Un clúster mal operat és pitjor que ECS o que màquines virtuals ben operades. I "sap fer servir
kubectl" no és el mateix que "sap operar un clúster": actualitzar versions, gestionar CRDs, diagnosticar xarxa i emmagatzematge, revisar polítiques de seguretat. - La teva càrrega és previsible i modesta. L'autoescalat fi de Kubernetes brilla amb pics; amb càrrega estable, un servei gestionat fa el mateix amb menys peces.
- Ja tens un servei gestionat que funciona (ECS, Cloud Run, App Runner, App Service). Migrar costa mesos i cal poder anomenar quin problema concret resol.
- La raó principal és "portabilitat entre núvols". És real però sol ser teòrica: el clúster és portable, però la base de dades gestionada, el balancejador, l'emmagatzematge d'objectes, la gestió d'identitats i els complements específics no ho són. La portabilitat que s'aconsegueix és parcial i es paga cara.
Sí que el necessites quan: tens molts serveis i diversos equips que necessiten autonomia (05-03); necessites desplegament progressiu sofisticat i capacitats que només existeixen en aquest ecosistema; la teva càrrega és molt variable i l'empaquetatge eficient de contenidors estalvia diners de veritat; tens requisits d'on-premise o multinúvol reals i pressupostats; o ja tens la plataforma i l'equip, cas en què el cost marginal d'una aplicació més és baix.
La regla, que és la mateixa que va tancar el mòdul 5: la complexitat es justifica amb un problema concret que resol, no amb el fet que altres la facin servir.
Errors Comuns i Consells
Fer servir la mateixa imatge per construir i per executar. Produeix imatges enormes amb compiladors i credencials. Multietapa sempre.
Secrets amb ARG o ENV. Queden a l'historial de la imatge encara que esborris el fitxer després. --mount=type=secret.
FROM node:22 sense fixar. El build no és reproduïble. Fixa la versió i, en entorns sensibles, el digest, amb automatització que ho actualitzi.
mode=min a la memòria cau de registre amb multietapa. Perd les etapes intermèdies, que són justament les cares. mode=max.
Muntar el socket de Docker de l'amfitrió en jobs de CI. Equival a donar root de l'amfitrió a qualsevol PR. Kaniko o BuildKit rootless.
Sonda de vida que comprova la base de dades. Converteix un incident de base de dades en una caiguda total amb reinicis en bucle. Vida = procés; disponibilitat = dependències.
Sense startupProbe en aplicacions lentes. Bucle de reinicis permanent que sembla un bug de l'aplicació.
Sense requests ni limits. Planificació dolenta, i una fuita de memòria tomba el node. Recorda que superar el límit de memòria mata el contenidor sense avís.
Desplegar per etiqueta en comptes de per digest. Introdueix una cursa i trenca la garantia del "mateix artefacte a tots els entorns".
kubectl apply sense kubectl rollout status --timeout. El job dona verd amb el rollout encallat. Verd fals (04-04) en la seva versió més cara.
Secrets de Kubernetes tractats com a xifrats. Estan en base64, no xifrats. Activa el xifratge en repòs d'etcd i fes servir un operador de secrets externs.
Adoptar Kubernetes per defecte. És la decisió d'arquitectura més cara que es pren amb menys anàlisi.
Exercicis
Exercici 1. La imatge d'apps/api pesa 1,3 GB, triga 6 minuts a construir-se al CI, l'escàner reporta 180 CVE i l'equip va descobrir un token d'npm visible amb docker history. Reescriu el Dockerfile explicant què corregeix cada canvi i quina millora esperes en mida, temps i troballes.
Exercici 2. Reservalia desplega a Kubernetes amb kubectl set image des del cd.yml, amb un kubeconfig d'administrador guardat com a secret del CI. Dissenya la migració a GitOps amb Argo CD: estructura de repositoris, què fa el pipeline després, quins permisos queden al CI, com es fa el rollback, i què es perd respecte del model actual.
Exercici 3. Una startup de quatre persones amb un monòlit Node i una base de dades PostgreSQL, ~200 usuaris i creixement lent, proposa migrar a Kubernetes "per estar preparats per escalar". Escriu la resposta tècnica: quines preguntes cal fer abans, quins costos reals té, quines alternatives hi ha i sota quines condicions canviaria la recomanació.
Solucions
Solució 1.
# syntax=docker/dockerfile:1.7
FROM node:22.11-bookworm AS deps
WORKDIR /app
COPY package.json package-lock.json ./
COPY apps/api/package.json apps/api/
COPY packages/compartit/package.json packages/compartit/
# El token va muntat: NO queda a cap capa
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
--mount=type=cache,target=/root/.npm,sharing=locked \
npm ci --prefer-offline
FROM deps AS build
COPY . .
RUN npm run build --workspace apps/api
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
npm ci --omit=dev --prefer-offline
FROM gcr.io/distroless/nodejs22-debian12:nonroot AS runtime
WORKDIR /app
COPY --from=build --chown=nonroot:nonroot /app/node_modules ./node_modules
COPY --from=build --chown=nonroot:nonroot /app/apps/api/dist ./dist
USER nonroot
ENV NODE_ENV=production
EXPOSE 3000
LABEL org.opencontainers.image.source="https://github.com/reservalia/reservalia" \
org.opencontainers.image.revision="${GIT_SHA}"
ENTRYPOINT ["/nodejs/bin/node", "dist/servidor.js"]| Canvi | Què corregeix | Efecte esperat |
|---|---|---|
Etapa runtime distroless separada |
La imatge final portava compilador, devDependencies i fonts |
1,3 GB → ~120 MB |
npm ci --omit=dev abans de copiar |
devDependencies a producció |
Part de l'estalvi anterior |
--mount=type=secret per al .npmrc |
Token visible a docker history |
Desapareix de la imatge |
--mount=type=cache per a ~/.npm |
Descàrrega completa a cada build | 6 min → 2-3 min amb memòria cau calenta |
COPY dels manifestos abans que el codi |
Qualsevol canvi invalidava la instal·lació | Capa de dependències reutilitzada entre commits |
Base distroless + USER nonroot |
180 CVE, la majoria en paquets del sistema sense fer servir | ~180 → 5-15, i aquests sí que són accionables |
| Etiquetes OCI | Sense traçabilitat imatge → commit | docker inspect respon "d'on ha sortit això?" |
Acció imprescindible que no és al Dockerfile: rotar el token d'npm. Va estar a l'historial de totes les imatges publicades i qualsevol amb accés al registre el va poder llegir. Canviar el Dockerfile evita repetir-ho; no repara el que ha passat. És exactament la lliçó de la 06-04, i la resposta de fons és la mateixa: si el token pot substituir-se per publicació federada, millor que rotar-lo.
Advertiment sobre distroless: no hi ha shell, així que l'equip necessita kubectl debug i bona observabilitat abans d'adoptar-lo, o el primer incident serà desagradable.
Solució 2.
Estructura de repositoris — dos, deliberadament separats:
reservalia/reservalia → codi de l'aplicació, CI, Dockerfile
reservalia/desplegaments → estat desitjat del clúster
base/
deployment.yaml service.yaml ingress.yaml kustomization.yaml
overlays/
staging/ kustomization.yaml recursos.yaml
produccio/ kustomization.yaml recursos.yamlSe separen perquè tenen cicles de vida i permisos diferents: a desplegaments es poden exigir revisors de l'equip de plataforma per a overlays/produccio sense bloquejar el desenvolupament diari.
Què fa el pipeline després:
promocionar:
needs: [publicar]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
with:
repository: reservalia/desplegaments
token: ${{ secrets.TOKEN_DESPLEGAMENTS }} # únic permís que queda
- run: |
cd overlays/staging
kustomize edit set image reservalia/api=$ECR/reservalia/api@${{ needs.publicar.outputs.digest }}
git commit -am "staging: api ${{ github.sha }}" && git pushProducció es promociona amb un PR des d'overlays/staging a overlays/produccio —revisat i aprovat—, que substitueix l'aprovació de l'Environment i deixa el mateix rastre d'auditoria, però a Git.
Permisos que queden al CI: només un token amb permís d'escriptura sobre el repositori desplegaments. El kubeconfig d'administrador desapareix del CI, que és el guany principal: comprometre el CI ja no equival a comprometre el clúster, només a poder proposar un canvi que a més passa per revisió a producció.
Rollback: git revert del commit a desplegaments i Argo CD reconcilia en l'interval de sondeig (o immediatament amb webhook). Avantatge afegit: el rollback queda registrat com un commit amb autor i motiu, en comptes de com un job rellançat que ningú no recorda. Per a emergències es conserva kubectl rollout undo com a sortida manual, amb selfHeal desactivat temporalment si cal —i amb la disciplina que després cal arreglar Git, o l'agent revertirà el teu arranjament—.
Què es perd: la immediatesa (segons a minuts, llevat de webhook); la simplicitat d'un sol repositori, i amb ella la resposta directa a "quin commit hi ha a producció?", que ara exigeix mirar dos historials i per tant convé automatitzar amb una anotació al manifest; un component més per operar i actualitzar; i una corba d'aprenentatge per a l'equip. El compte surt a favor sobretot pel punt de les credencials, no per elegància.
Solució 3.
Preguntes abans de respondre res:
- Quin problema concret teniu avui? Si la resposta és "cap, és per si de cas", no hi ha cas. Si és "els desplegaments ens fan por" o "no sabem escalar en pics", aquests problemes tenen solucions més barates.
- Quina és la càrrega real i la seva variabilitat? Amb 200 usuaris i creixement lent, l'autoescalat no aporta res.
- Qui opera el clúster i què deixa de fer mentrestant? Amb quatre persones, és entre mitja i una persona equivalent.
- Quin és l'objectiu de disponibilitat? Si no hi ha cap SLO escrit, no hi ha manera de justificar complexitat per disponibilitat.
- Què hi ha avui i què falla exactament?
Costos reals, que gairebé mai no es comptabilitzen:
| Cost | Detall |
|---|---|
| Pla de control | Cost fix mensual (EKS/GKE/AKS) encara que no desplegueu res |
| Nodes infrautilitzats | Un clúster mínim amb redundància són diverses màquines sempre enceses |
| Complements | Ingress, certificats, monitoratge, escalat: tots s'han d'instal·lar i actualitzar |
| Actualitzacions | Diverses versions l'any, amb APIs que es retiren i manifestos que cal tocar |
| Aprenentatge | Setmanes fins a operar amb soltesa; mesos fins a diagnosticar bé |
| Cost d'oportunitat | El més gran: el que aquelles setmanes no es van dedicar al producte |
Alternatives, ordenades pel que resolen:
| Si el problema és… | Solució proporcionada |
|---|---|
| Desplegaments manuals i amb por | CI/CD sobre el que ja hi ha: el mòdul 3 sencer |
| Entorns no reproduïbles | Contenidors + IaC (03-03), sense orquestrador complex |
| Necessitat d'escalar de tant en tant | Servei gestionat de contenidors: ECS Fargate, Cloud Run, App Runner |
| Caigudes en desplegar | Rolling update amb health checks, que aquests serveis ja porten |
| "Volem aprendre Kubernetes" | Formació deliberada, no la producció de l'empresa |
Recomanació: no migrar ara. Conteniritzar l'aplicació —això sí, perquè dona reproductibilitat i manté la porta oberta: una aplicació en contenidor amb IaC es mou a Kubernetes més endavant en setmanes, no en mesos— i desplegar en un servei gestionat. Escriure la decisió amb la data i amb les condicions que la revisarien, perquè no es replantegi cada trimestre.
Condicions sota les quals canviaria: arribar a cinc o més serveis amb equips diferents; un requisit contractual d'on-premise o multinúvol; una càrrega amb pics de més d'un ordre de magnitud on l'empaquetatge estalviï diners mesurables; contractar algú amb experiència real d'operació; o necessitar capacitats de l'ecosistema (operadors, desplegament progressiu avançat) que el servei gestionat no ofereixi.
I l'argument que sol tancar la conversa: "estar preparats per escalar" és la justificació més cara de l'enginyeria. Amb 200 usuaris, el risc real no és no poder escalar —això s'arregla quan passa, i amb diners—, és gastar tres mesos de l'equip en infraestructura mentre el producte no avança. La preparació efectiva és conteniritzar i tenir infraestructura com a codi; l'orquestrador es tria quan el problema existeixi.
Conclusió
Docker i Kubernetes no competeixen amb les eines de les lliçons anteriors: hi són a sota i al davant. Docker apareix en dos papers que cal mantenir separats —entorn reproduïble del pipeline i artefacte immutable que es desplega—, i la separació es materialitza amb multietapa, imatges de runtime mínimes, usuari no-root i secrets muntats que no deixen rastre a l'historial. BuildKit hi afegeix el que de veritat accelera un pipeline en executors efímers: memòries cau de muntatge que sobreviuen als canvis de lockfile i memòria cau de capes al registre amb mode=max. I la decisió de com construir imatges dins d'un contenidor —DinD privilegiat, socket de l'amfitrió, o constructors sense dimoni— és una decisió de seguretat de primer ordre que se sol prendre per inèrcia.
A Kubernetes, el que cal dominar per desplegar és un conjunt petit: Deployment, Service, Ingress, ConfigMap, Secret, i sobretot les sondes, perquè la de disponibilitat és la que fa que el rolling update de la 03-04 sigui segur i la de vida mal configurada és la que converteix un incident en una caiguda total. kubectl rollout status --timeout és la línia que impedeix el verd fals, i undo dona el rollback més ràpid del curs. Per parametritzar per entorn, Helm i Kustomize resolen el mateix de manera diferent i conviuen bé. I GitOps va guanyar per una raó concreta i no per moda: treu les credencials del clúster fora del CI i converteix l'estat desitjat en una cosa explícita, versionada i amb detecció de deriva.
L'apartat que convé recordar més temps és l'últim: Kubernetes és la decisió d'arquitectura més cara que es pren amb menys anàlisi. Reservalia està millor a ECS avui, i saber justificar això és tan valuós com saber escriure un Deployment.
L'última eina del recorregut és la que l'alumne fa cinc mòduls que fa servir. GitHub Actions a fons tanca els buits que van quedar: contextos i expressions, tots els disparadors que faltaven i la diferència crítica entre pull_request i pull_request_target, els permisos del GITHUB_TOKEN i OIDC, els tres tipus d'action amb el codi complet d'una escrita en JavaScript, matrius dinàmiques, executors autoallotjats amb autoescalat, els límits reals del servei i com depurar-lo. Després, la 06-07 posarà les sis eines a la mateixa taula i donarà els criteris per triar.
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
