A les quatre lliçons anteriors hem escrit, sense justificar-ho, adreces com http://servei-cataleg:3001, servei-inventari:50051 o amqp://rabbitmq:5672. Han funcionat com a noms estables als exemples, però en un sistema real cadascun d'aquests noms amaga un problema: darrere de "servei-cataleg" hi haurà dues rèpliques un dimarts al matí i vuit el Black Friday, cadascuna en un contenidor amb una IP que s'assigna en arrencar i es destrueix en morir. A quina d'aquestes IPs crida Comandes quan necessita GET /productes?ids=? Qui sap quines són vives ara mateix? Com es reparteix la feina entre elles? Aquesta lliçó respon a aquestes tres preguntes.
Veurem per què no es poden cablejar adreces (les fal·làcies de 01-02 tornen), el registre de serveis (qui anota que una instància existeix: ella mateixa o un tercer), el descobriment de la banda del client davant del de la banda del servidor, les eines clàssiques (Consul, Eureka) amb un fragment real de registre amb health check, el descobriment natiu de Kubernetes per DNS que TechCorp adoptarà, els algorismes i capes del balanceig de càrrega i on passa en cada model, els health checks (liveness davant de readiness, amb tots dos endpoints en Express) com a part del contracte d'un servei, i la decisió de TechCorp: DNS de Kubernetes + noms estables servei-* com a configuració. Desplegar de debò a Kubernetes és de 05-02, el service mesh de 05-05 i la resiliència (què fer quan la instància triada falla) de 06-03.
Contingut
- El problema: instàncies efímeres amb IPs dinàmiques
- Registre de serveis: qui anota que una instància existeix
- Descobriment de la banda del client
- Descobriment de la banda del servidor
- Comparativa dels dos models
- Eines: Consul i Eureka
- Descobriment natiu de Kubernetes:
Service,Endpointsi DNS - Balanceig de càrrega: algorismes i capes
- On passa el balanceig en cada model
- Health checks: liveness davant de readiness
- La decisió de TechCorp
- El problema: instàncies efímeres amb IPs dinàmiques
Al monòlit, la web cridava techcorp-shop:3000 i aquell nom apuntava a una màquina fixa amb una IP fixa. En microserveis sobre contenidors, la situació canvia en tres sentits:
- Moltes instàncies per servei. Catàleg té N rèpliques idèntiques i N canvia amb la càrrega (els pics ×20 de 01-05) o amb un desplegament (05-04).
- Adreces efímeres. Cada contenidor rep una IP del rang intern en arrencar (
10.244.3.17) i aquella IP desapareix amb ell. Reiniciar és canviar d'adreça. - Fallades parcials. Una rèplica pot ser viva però sense connexió a la seva base de dades, o arrencant i encara sense poder atendre, o a punt d'aturar-se.
Cablejar adreces (CATALEG_URL=http://10.244.3.17:3001) falla per les fal·làcies de la computació distribuïda de 01-02: "la topologia no canvia" (canvia cada minut), "la xarxa és fiable" (no ho és), "hi ha un sol administrador" (Kubernetes reprograma contenidors sense preguntar). I una llista d'IPs a la configuració es desactualitza abans de desplegar-se.
Necessitem tres coses: un lloc on es registri quines instàncies existeixen i on (registre de serveis), un mecanisme perquè qui crida trobi una instància sana en el moment de cridar (descobriment), i una manera de repartir les crides entre les instàncies disponibles (balanceig de càrrega).
- Registre de serveis: qui anota que una instància existeix
El registre és una base de dades de "servei → llista d'instàncies (IP, port, estat)". Hi ha dues maneres d'omplir-lo:
| Model | Qui registra | Com es detecta la caiguda | Avantatges | Inconvenients |
|---|---|---|---|---|
| Self-registration (autoregistre) | La mateixa instància, en arrencar, crida el registre ("soc servei-cataleg, soc a 10.244.3.17:3001") i en aturar-se es dona de baixa |
La instància envia batecs (heartbeats) periòdics; si deixen d'arribar durant un temps, el registre la marca com a caiguda | Senzill d'entendre; no requereix infraestructura que "miri" el servei | Cada servei necessita codi de registre (acoblament a l'eina); si el procés mor de cop no es dona de baixa i cal esperar el timeout de batecs |
| Third-party registration (registre per tercers) | Un component extern (l'orquestrador, un agent) observa quines instàncies arrenquen i moren i actualitza el registre | El tercer fa comprovacions de salut a la instància | El servei no sap que existeix un registre; sense codi d'infraestructura al negoci | Requereix que aquell tercer existeixi i estigui integrat amb la plataforma |
Consul i Eureka es fan servir tradicionalment en mode self-registration (amb clients al servei) o amb un agent local; Kubernetes és third-party pur: el mateix orquestrador sap quins contenidors hi ha perquè els ha creat ell.
- Descobriment de la banda del client
En el descobriment de la banda del client (client-side discovery), és qui crida qui consulta el registre, obté la llista d'instàncies sanes, en tria una (aplicant un algorisme de balanceig) i la crida directament.
sequenceDiagram
participant P as servei-comandes
participant R as Registre (Consul/Eureka)
participant C1 as cataleg 10.244.3.17 (3001)
participant C2 as cataleg 10.244.5.42 (3001)
C1->>R: registre + heartbeats
C2->>R: registre + heartbeats
P->>R: instàncies de servei-cataleg?
R-->>P: [10.244.3.17:3001, 10.244.5.42:3001]
Note over P: en tria una (round robin) i desa la llista en memòria cau uns segons
P->>C2: GET /productes?ids=p-501,p-777
C2-->>P: 200 OK
Conseqüències: el client necessita una llibreria que parli amb el registre i balancegi (Netflix Ribbon amb Eureka en va ser l'exemple clàssic); aquesta llibreria ha d'existir per a cada llenguatge del sistema; el balanceig pot ser molt intel·ligent (el client coneix les seves latències); i no hi ha cap salt de xarxa extra. Però cada servei porta a dins lògica d'infraestructura, i actualitzar aquesta lògica és redesplegar tots els serveis.
- Descobriment de la banda del servidor
En el descobriment de la banda del servidor (server-side discovery), qui crida ho fa sempre a una adreça estable (un balancejador, un nom DNS virtual, un proxy) i és aquella peça intermèdia qui consulta el registre i reenvia la petició a una instància sana.
sequenceDiagram
participant P as servei-comandes
participant LB as Adreça estable (Service / balancejador)
participant R as Registre (Endpoints)
participant C1 as cataleg 10.244.3.17 (3001)
participant C2 as cataleg 10.244.5.42 (3001)
Note over R: l'orquestrador actualitza la llista en crear/destruir instàncies
P->>LB: GET http://servei-cataleg:3001/productes?ids=...
LB->>R: instàncies sanes de servei-cataleg
R-->>LB: [10.244.3.17, 10.244.5.42]
LB->>C1: reenviament
C1-->>LB: 200 OK
LB-->>P: 200 OK
Conseqüències: el client és trivial (un fetch a un nom fix, com els de 03-01); la lògica de descobriment viu a la plataforma i s'actualitza sense tocar serveis; funciona igual per a Node.js, Go o Java. A canvi, hi ha una peça més pel camí (que ha de ser altament disponible) i, en alguns casos, un salt de xarxa extra.
- Comparativa dels dos models
| Criteri | Banda del client | Banda del servidor |
|---|---|---|
| Qui consulta el registre | El servei que crida | Un intermediari (balancejador, proxy, DNS+kube-proxy) |
| Codi al servei | Llibreria de descobriment i balanceig | Cap: crida un nom |
| Políglota | Una llibreria per llenguatge | Independent del llenguatge |
| Salt de xarxa addicional | No | De vegades (depèn de la implementació) |
| Intel·ligència del balanceig | Alta (el client coneix latències, pot fer hedging) | Mitjana (l'intermediari veu tot el trànsit, no l'experiència de cada client) |
| Punt de fallada | El registre (però el client desa en memòria cau) | L'intermediari (s'ha de replicar) |
| Exemples | Eureka + Ribbon, Consul + llibreria client | AWS ELB/ALB, Kubernetes Service, NGINX/Traefik, service mesh |
| Encaix per a TechCorp | Afegiria codi d'infraestructura a sis serveis Node.js | Kubernetes ho dona de sèrie |
La tendència de la indústria és clara: amb orquestradors i service meshes, el descobriment de la banda del servidor ha guanyat perquè treu la infraestructura del codi de negoci. És coherent amb "smart endpoints, dumb pipes" de 02-01: el servei pensa en comandes, no en IPs.
- Eines: Consul i Eureka
Tot i que TechCorp farà servir el mecanisme de Kubernetes, convé conèixer les dues eines clàssiques perquè apareixen en molts sistemes existents i perquè expliquen d'on venen els conceptes.
- Netflix Eureka. Registre REST creat per Netflix per a AWS. Les instàncies es registren amb
POST /eureka/apps/{app}i envien un batec cada 30 s ambPUT; si en falten tres, es marca com a caiguda. Els clients descarreguen el registre complet i el desen en memòria cau. Molt lligat a l'ecosistema Spring Cloud (Java); en Node.js existeixen clients, però és estrany triar-lo avui fora de Java. - HashiCorp Consul. Registre + magatzem clau-valor + health checks + DNS. Cada node executa un agent local; els serveis es registren contra el seu agent (per API HTTP o per fitxer), i Consul executa les comprovacions de salut (HTTP, TCP, script) des de l'agent. Exposa el registre per API i per DNS (
servei-cataleg.service.consulresol a les instàncies sanes), cosa que ja és una forma de descobriment de la banda del servidor. És l'opció habitual fora de Kubernetes (màquines virtuals, Nomad) i continua sent rellevant.
Fragment amb el client consul de Node.js registrant servei-cataleg amb un health check HTTP:
// infraestructura/registreConsul.js (només es faria servir si TechCorp NO fos a Kubernetes)
const Consul = require('consul');
const os = require('node:os');
async function registrarEnConsul({ nom = 'servei-cataleg', port = 3001 }) {
// 1. Client contra l'agent local (cada node té el seu al 8500)
const consul = new Consul({ host: process.env.CONSUL_HOST ?? '127.0.0.1', port: 8500 });
// 2. Id únic per instància: nom + host + port. Dues rèpliques → dos ids
const instanciaId = `${nom}-${os.hostname()}-${port}`;
const adreca = process.env.IP_INSTANCIA ?? obtenirIpLocal();
// 3. Registre amb health check: Consul cridarà GET /health/ready cada 10 s;
// si falla 30 s seguits, la instància es dona de baixa sola (DeregisterCriticalServiceAfter)
await consul.agent.service.register({
id: instanciaId,
name: nom,
address: adreca,
port: port,
tags: ['http', 'v1'],
check: {
http: `http://${adreca}:${port}/health/ready`,
interval: '10s',
timeout: '2s',
deregistercriticalserviceafter: '30s'
}
});
console.log(`Registrat a Consul com a ${instanciaId}`);
// 4. Baixa ordenada en aturar: sense això, la instància figura "crítica" fins que expiri el check
const donarDeBaixa = async () => { await consul.agent.service.deregister(instanciaId); process.exit(0); };
process.on('SIGTERM', donarDeBaixa);
process.on('SIGINT', donarDeBaixa);
}
// Client: obtenir instàncies sanes i triar-ne una (descobriment de la banda del client)
async function resoldreInstancia(consul, nom) {
const [instancies] = await consul.health.service({ service: nom, passing: true }); // només les sanes
if (instancies.length === 0) throw new Error(`Sense instàncies sanes de ${nom}`);
const triada = instancies[Math.floor(Math.random() * instancies.length)]; // balanceig aleatori simple
return `http://${triada.Service.Address}:${triada.Service.Port}`;
}Fixa't en tot el que aquest codi afegeix al servei de Catàleg que no té res a veure amb productes: ids d'instància, IPs, batecs, baixes. És el cost del self-registration i del descobriment de la banda del client. Amb Kubernetes, res d'això no existeix al codi del servei.
- Descobriment natiu de Kubernetes:
Service, Endpoints i DNS
Service, Endpoints i DNSKubernetes porta registre, descobriment i balanceig integrats. Tres objectes basten per entendre-ho (el YAML complet de desplegament és de 05-02; aquí només el mecanisme):
- Pod. La unitat que executa un contenidor (una rèplica de Catàleg). Té una IP efímera del clúster (
10.244.3.17). - Service. Un objecte amb un nom estable (
servei-cataleg) i una IP virtual estable (ClusterIP, p. ex.10.96.12.5) que selecciona pods per etiquetes (app: servei-cataleg). Viu encara que els pods morin i neixin. - Endpoints (o EndpointSlice en versions actuals). La llista, mantinguda automàticament per Kubernetes, de les IPs dels pods que el
Serviceselecciona i que estan llestos (readiness, apartat 10). És el registre, omplert per un tercer (l'orquestrador): third-party registration sense escriure ni una línia.
El flux quan Comandes fa fetch('http://servei-cataleg:3001/productes?ids=...'):
flowchart LR
P[pod servei-comandes] -- "1. resoldre servei-cataleg" --> DNS[CoreDNS del clúster]
DNS -- "2. 10.96.12.5 (ClusterIP)" --> P
P -- "3. TCP a 10.96.12.5:3001" --> KP[kube-proxy / iptables del node]
KP -- "4. consulta Endpoints" --> EP[(Endpoints de servei-cataleg: 10.244.3.17, 10.244.5.42, ...)]
KP -- "5. reescriu el destí cap a un pod" --> C2[pod cataleg 10.244.5.42:3001]
C2 --> P
- El pod de Comandes resol el nom
servei-catalega CoreDNS, el DNS intern del clúster. El nom complet ésservei-cataleg.<namespace>.svc.cluster.local, però des del mateix namespace n'hi ha prou amb el nom curt. - CoreDNS retorna la ClusterIP del
Service, que no canvia mai mentre elServiceexisteixi. - Comandes obre una connexió a aquella IP virtual i al port 3001.
- A cada node, kube-proxy manté regles de xarxa (iptables o IPVS) que intercepten el trànsit cap a les ClusterIP.
- Aquestes regles trien un dels pods de la llista d'Endpoints i reescriuen el destí cap a la seva IP real. Si un pod deixa d'estar llest, surt d'Endpoints i deixa de rebre trànsit en segons.
L'important per al desenvolupador: el codi de Comandes no sap res de tot això. Crida un nom i un port fixos, exactament el CATALEG_URL de 03-01. Registre, salut i balanceig són de la plataforma. És descobriment de la banda del servidor i registre per tercers, gratis.
Fragment del Service de Catàleg (només per veure d'on surten el nom i el port; el Deployment que crea els pods és de 05-02):
apiVersion: v1
kind: Service
metadata:
name: servei-cataleg # → nom DNS estable
spec:
selector:
app: servei-cataleg # els pods amb aquesta etiqueta formen els Endpoints
ports:
- port: 3001 # port del Service (el que fan servir els que criden)
targetPort: 3001 # port del contenidorUn matís per a RabbitMQ i altres connexions llargues: el balanceig de kube-proxy passa en establir la connexió TCP. Una connexió AMQP oberta durant hores es queda amb el mateix pod; per a HTTP amb keep-alive passa una cosa semblant (totes les peticions d'una connexió van al mateix pod). Per a HTTP funciona bé perquè les connexions es renoven; per repartir de debò per petició cal balanceig de capa 7 (apartat 8), que és una de les coses que aporta el service mesh de 05-05.
- Balanceig de càrrega: algorismes i capes
Algorismes de repartiment entre N instàncies:
| Algorisme | Com reparteix | Quan convé | Limitació |
|---|---|---|---|
| Round robin | Una a cada instància, per torns | Instàncies iguals i peticions semblants (el cas de Catàleg) | Ignora que una instància estigui més carregada |
| Round robin ponderat | Torns proporcionals a un pes | Instàncies amb capacitat diferent; desplegaments canary (05-04): 95 % a la versió vella, 5 % a la nova | Cal mantenir els pesos |
| Least connections (menys connexions) | A la instància amb menys connexions actives | Peticions de durada molt variable (informes llargs al costat de consultes curtes) | Requereix que el balancejador conegui les connexions actives |
| Hash consistent | Una funció de la petició (IP, id de client, id de comanda) decideix sempre la mateixa instància | Memòries cau locals per instància; afinitat de sessió; repartir consumidors per clau | Repartiment desigual si les claus ho són; reassignacions en canviar N |
| Aleatori | A l'atzar | Amb moltes instàncies s'aproxima al round robin sense estat | Ratxes |
| Latència / càrrega | A la instància que respon més ràpid | Instàncies heterogènies o en zones diferents | Necessita mesurar; risc de "ramat" cap a la més ràpida |
Capes en què es balanceja:
| Capa 4 (transport, TCP/UDP) | Capa 7 (aplicació, HTTP) | |
|---|---|---|
| Què veu | IP i port; decideix per connexió | Mètode, ruta, capçaleres, cos; decideix per petició |
| Velocitat | Molt alta (no parseja) | Alta, però parseja HTTP |
| Capacitats | Repartiment simple, sense entendre la petició | Encaminar per ruta (/api/comandes → Comandes), per capçalera (canary), reintents, timeouts per petició, terminació TLS |
| Exemples | kube-proxy, HAProxy en mode TCP, balancejadors de xarxa al núvol | NGINX, Traefik, Envoy (Istio), balancejadors d'aplicació al núvol, el gateway de 03-04 |
kube-proxy és capa 4: reparteix connexions, no peticions. El gateway de 03-04 és capa 7: encamina per ruta i capçalera. Un service mesh posa un proxy de capa 7 (Envoy) al costat de cada pod i dona balanceig per petició, reintents i mètriques entre serveis interns sense tocar el codi: per això apareix a 05-05 com a evolució natural.
- On passa el balanceig en cada model
| Model | Qui balanceja | Algorismes típics | A TechCorp |
|---|---|---|---|
| Descobriment de la banda del client | La llibreria del client (Ribbon, client Consul) | Round robin, aleatori, per latència | No (evitem codi d'infraestructura) |
| Balancejador dedicat | NGINX/HAProxy/ALB davant de les instàncies | Tots; capa 4 o 7 | El balancejador extern davant de les rèpliques del gateway 8080 |
Kubernetes Service |
kube-proxy a cada node | Aleatori/round robin per connexió (capa 4) | Sí: entre serveis interns i del gateway cap als serveis |
| Ingress / gateway | Traefik, NGINX Ingress | Capa 7: per ruta, capçalera, pes | Sí: el gateway de 03-04 com a Ingress (05-02) |
| Service mesh | Sidecar Envoy al costat de cada pod | Capa 7 per petició, least request, hash, amb reintents i mTLS | Avançament: 05-05 |
Per a les crides internes (Comandes → Catàleg) TechCorp comença amb kube-proxy: suficient per al volum actual i sense cost. Quan necessiti balanceig per petició, reintents declaratius o mètriques per ruta entre serveis, adoptarà Istio.
- Health checks: liveness davant de readiness
Res de tot l'anterior no serveix si el registre no sap quines instàncies estan sanes. Un contenidor amb el procés viu no és el mateix que un servei capaç d'atendre. Per això hi ha dues comprovacions diferents, i confondre-les causa incidents:
| Liveness ("soc viu?") | Readiness ("estic llest per atendre?") | |
|---|---|---|
| Pregunta | El procés funciona o està penjat/bloquejat sense remei? | Puc atendre peticions ara? (dependències connectades, memòria cau carregada, no aturant-me) |
| Què comprova | El mínim: que el bucle d'esdeveniments respon | Connexió a la BD, al broker, configuració carregada, estat d'arrencada/aturada |
| Si falla | L'orquestrador reinicia el contenidor | L'orquestrador el treu del balanceig (fora d'Endpoints) però no el reinicia; quan torna a passar la prova, el reincorpora |
| Ha de dependre de tercers | No (si la BD cau i liveness falla, Kubernetes reiniciaria tots els pods en bucle, empitjorant-ho tot) | Sí, precisament per no rebre trànsit que no pot atendre |
| Endpoint a TechCorp | GET /health/live |
GET /health/ready |
Exemple de tots dos endpoints en Express (Catàleg, amb MongoDB i RabbitMQ com a dependències):
// rutes/salut.js (patró comú a tots els serveis; s'empaqueta a @techcorp/comu-http)
const express = require('express');
function crearRutesSalut({ comprovacions = {} } = {}) {
const enrutador = express.Router();
let aturant = false;
// Liveness: si Express pot executar això, el procés és viu. Res més.
enrutador.get('/health/live', (_req, res) => {
res.status(200).json({ estat: 'viu' });
});
// Readiness: cada dependència es comprova amb un timeout curt; una que falli → 503
enrutador.get('/health/ready', async (_req, res) => {
if (aturant) {
// Durant l'aturada ordenada, diem "no llest" perquè el balancejador deixi d'enviar-nos trànsit
return res.status(503).json({ estat: 'aturant' });
}
const resultats = {};
let totBe = true;
for (const [nom, comprovar] of Object.entries(comprovacions)) {
try {
await Promise.race([
comprovar(),
new Promise((_, rebutjar) => setTimeout(() => rebutjar(new Error('timeout')), 1000))
]);
resultats[nom] = 'ok';
} catch (err) {
resultats[nom] = `fallada: ${err.message}`;
totBe = false;
}
}
res.status(totBe ? 200 : 503).json({ estat: totBe ? 'llest' : 'no llest', dependencies: resultats });
});
// En rebre SIGTERM, deixem d'estar "ready" uns segons abans de tancar (aturada ordenada)
process.on('SIGTERM', () => { aturant = true; });
return enrutador;
}
module.exports = { crearRutesSalut };I el seu ús a Catàleg:
app.use(crearRutesSalut({
comprovacions: {
mongodb: () => clientMongo.db().command({ ping: 1 }), // respon MongoDB?
rabbitmq: () => Promise.resolve(canalAmqp && !canalAmqp.closed ? true : Promise.reject(new Error('canal tancat')))
}
}));Resposta de GET /health/ready amb MongoDB caigut:
Amb codi 503. Kubernetes (05-02) cridarà aquests endpoints periòdicament (livenessProbe, readinessProbe), Traefik ja els feia servir al healthCheck de 03-04, i Consul al check de l'apartat 6. Per això diem que les comprovacions de salut són part del contracte d'un servei: igual que POST /comandes respon 202, tot servei de TechCorp respon a /health/live i /health/ready amb la semàntica de la taula; l'equip de Plataforma ho dona per fet en configurar la plataforma. I dos matisos més: /health/* no requereix autenticació (el crida la infraestructura) però no s'exposa pel gateway; i no ha de fer feina pesada (una consulta SELECT 1, un ping), perquè s'executa cada pocs segons per cada rèplica.
- La decisió de TechCorp
- Descobriment: el natiu de Kubernetes. Un
Serviceper microservei amb nom estableservei-cataleg,servei-comandes,servei-pagaments,servei-clients,servei-notificacions,servei-inventari,bff-mobil, mésrabbitmqi les bases de dades. Registre per tercers (Kubernetes manté els Endpoints), descobriment de la banda del servidor (DNS + kube-proxy). Cap servei no porta codi de registre. - Balanceig: el del
Service(kube-proxy, capa 4) entre serveis interns; capa 7 al gateway/Ingress (Traefik) per al trànsit extern; service mesh com a pas següent quan faci falta (05-05). - Noms estables com a configuració: cada servei rep les adreces de les seves dependències per variables d'entorn el valor de les quals és el nom DNS del
Service. A 04-03 es veurà com es gestionen (ConfigMaps, fitxers.env); el contracte des d'avui és aquest:
| Variable | Valor al clúster | Qui la fa servir |
|---|---|---|
CATALEG_URL |
http://servei-cataleg:3001 |
Comandes, BFF mòbil, gateway |
CLIENTS_URL |
http://servei-clients:3004 |
Comandes, gateway |
COMANDES_URL |
http://servei-comandes:3002 |
BFF mòbil, gateway |
INVENTARI_GRPC |
servei-inventari:50051 |
Comandes (si s'activa gRPC, 03-03) |
RABBITMQ_URL |
amqp://rabbitmq:5672 |
Tots els que publiquen o consumeixen |
COMANDES_DB_URL |
postgres://...@postgres-comandes:5432/comandes |
Només Comandes (BD per servei, 02-04) |
- Salut com a contracte:
/health/livei/health/readya tots els serveis, amb la semàntica de l'apartat 10. - En desenvolupament local (Docker Compose, 05-01) els mateixos noms funcionen perquè Compose també dona DNS per nom de servei: el codi no canvia entre el portàtil i el clúster, només el valor de les variables si fes falta.
Errors Comuns i Consells
- Cablejar IPs a la configuració "només en desenvolupament". Acaben a producció. Noms des del primer dia.
- Desar la resolució DNS en memòria cau per sempre al client. Node.js no desa DNS en memòria cau per defecte, però algunes llibreries o pools de connexions sí que conserven la IP; si el
Servicees recrea amb una altra ClusterIP, el client continua apuntant a l'antiga. Renovar connexions i no fixar IPs. - Liveness que comprova la base de dades. Cau la BD → tots els pods "no vius" → Kubernetes els reinicia en bucle → quan la BD torna, ningú no està arrencat. Liveness només mira el procés.
- Readiness que no comprova res. El pod entra al balanceig abans de connectar amb RabbitMQ i falla les primeres peticions de cada desplegament.
- No passar a "no llest" en aturar. El pod rep peticions fins a l'últim mil·lisegon i les talla a mitges.
SIGTERM→aturant = true→ esperar uns segons → tancar. - Health checks cars. Un
SELECT COUNT(*) FROM comandescada 5 s per rèplica és una càrrega inventada.SELECT 1oping. - Exposar
/healthpel gateway. És informació interna i una superfície d'atac més. Només dins del clúster. - Confiar que kube-proxy reparteix per petició. Reparteix connexions. Amb keep-alive, un client molt actiu pot carregar sempre el mateix pod. Si el repartiment importa, capa 7 (mesh).
- Afegir un client Consul "per si de cas" quan ja ets a Kubernetes. És duplicar el registre i omplir el servei de codi d'infraestructura.
Exercicis
Exercici 1. El servei de Comandes té tres dependències: PostgreSQL (comandes), RabbitMQ i el servei de Catàleg (síncron, via CATALEG_URL). Decideix, justificant cada cas, quines han de formar part de /health/ready de Comandes i quines no, i escriu la crida a crearRutesSalut resultant. Pista: pensa què passaria amb les rèpliques de Comandes si Catàleg caigués i fos a la comprovació.
Exercici 2. Explica pas a pas què passa a Kubernetes quan una rèplica de Catàleg comença a fallar la seva readinessProbe perquè MongoDB va lent, i quina diferència hi hauria si en lloc de la readiness fallés la liveness. Indica en quin moment Comandes deixa de rebre errors i per què el codi de Comandes no canvia en cap cas.
Exercici 3. L'equip de Plataforma proposa que les rèpliques de Notificacions facin servir hash consistent per clientId perquè tots els correus d'un mateix client els enviï la mateixa rèplica i així reutilitzar una connexió SMTP per client. Notificacions consumeix esdeveniments de RabbitMQ (03-02), no rep HTTP. Raona si el balanceig per hash té sentit aquí, quin mecanisme real de RabbitMQ (o de Kubernetes) ho permetria o no, i quina alternativa recomanaries.
Solucions
Solució 1.
- PostgreSQL de Comandes: sí. Sense la seva base de dades, Comandes no pot atendre cap petició (ni crear ni consultar comandes). Millor treure'l del balanceig que retornar
500a tot. - RabbitMQ: sí, amb matís. Comandes publica via outbox (02-05):
POST /comandesdesa comanda i esdeveniment a PostgreSQL i el relay publica després. Estrictament, podria acceptar comandes sense RabbitMQ; però també consumeixcomandes.sagai sense ell la saga no avança. TechCorp l'inclou: una comanda acceptada que no pot avançar és pitjor experiència que un503breu. (Argumentar el contrari és vàlid si es prioritza acceptar comandes.) - Catàleg: no. És una dependència d'un altre servei. Si Catàleg cau i fos a la readiness, totes les rèpliques de Comandes sortirien del balanceig alhora i
GET /comandes/{id}(que no necessita Catàleg) deixaria de funcionar: una fallada es convertiria en dues. Comandes ha de continuar llest i respondre503 DEPENDENCIA_NO_DISPONIBLEnomés a les operacions que necessiten Catàleg (03-01). Regla: la readiness inclou el que aquest servei necessita per funcionar en absolut, no altres serveis (això és cascada de fallades, tema de 06-03).
app.use(crearRutesSalut({
comprovacions: {
postgres: () => pool.query('SELECT 1'),
rabbitmq: () => (canalAmqp && !canalAmqp.closed) ? Promise.resolve() : Promise.reject(new Error('canal tancat'))
}
}));Solució 2.
Readiness fallant: (1) el kubelet del node crida GET /health/ready cada N segons i rep 503 (la comprovació de MongoDB supera el timeout d'1 s); (2) després de failureThreshold fallades consecutives (per defecte 3), Kubernetes marca el pod com a not ready; (3) el controlador d'Endpoints treu la IP d'aquell pod dels Endpoints de servei-cataleg; (4) kube-proxy actualitza les regles a tots els nodes i les connexions noves van només a les rèpliques llestes; (5) el pod continua viu, continua intentant-ho; quan MongoDB es recupera i /health/ready dona 200 (successThreshold, per defecte 1), torna a Endpoints. Comandes deixa de veure errors al pas 4, uns segons després de la primera fallada, i abans només veia errors a la fracció de peticions que kube-proxy enviava a aquell pod.
Liveness fallant: Kubernetes mata i reinicia el contenidor. Si el problema és MongoDB lent, el reinici no arregla res i, si la liveness comprovés MongoDB, totes les rèpliques es reiniciarien en bucle: per això la liveness no toca dependències.
El codi de Comandes no canvia perquè crida http://servei-cataleg:3001, un nom i una ClusterIP estables; qui hi ha al darrere i si està llest ho gestiona la plataforma. L'únic que Comandes veu són menys errors.
Solució 3.
El balanceig per hash és un concepte de crides entrants (HTTP/gRPC): qui decideix és un balancejador que veu la petició i tria la instància. Notificacions no rep crides: les seves rèpliques competeixen per missatges de la cua notificacions.comandes (03-02), i RabbitMQ lliura cada missatge a la rèplica que tingui lloc al seu prefetch; kube-proxy no hi intervé (les connexions AMQP són de la rèplica cap al broker, no a l'inrevés) i no pot saber res de clientId. Per tant, el hash consistent per clientId no aplica amb la topologia actual.
Alternatives: (a) a RabbitMQ existeix el plugin consistent-hash exchange, que reparteix a diverses cues per hash de la routing key o d'una capçalera; caldria crear una cua per rèplica i perdre la simetria d'"una cua per servei"; complex i fràgil en escalar. (b) Kafka resoldria això de manera natural (partició per clau), però canviar de broker per una optimització SMTP no compensa. (c) La recomanació: no fer-ho. Un pool de connexions SMTP a cada rèplica (reutilitzat entre clients) dona el mateix estalvi sense afinitat, i l'ordre entre correus d'un client el garanteix millor l'esdeveniment (que porta tot el necessari) que la topologia.
Conclusió
El problema era clar: instàncies efímeres amb IPs dinàmiques i algú que crida i necessita trobar-ne una de sana. La solució té tres peces: un registre (omplert per la mateixa instància amb batecs, o per un tercer com l'orquestrador), un mecanisme de descobriment (al client, amb una llibreria, o al servidor, amb una adreça estable que resol la plataforma) i un balanceig (round robin, ponderat, least connections, hash; en capa 4 per connexió o en capa 7 per petició). Hem vist Consul i Eureka com a referència i hem entès el mecanisme natiu de Kubernetes: Service amb nom i IP estables, Endpoints com a registre automàtic, CoreDNS per resoldre servei-cataleg i kube-proxy per repartir; i hem fixat els health checks (/health/live per a "soc viu", /health/ready per a "puc atendre") com a part del contracte de tot servei de TechCorp. La decisió: DNS de Kubernetes, balanceig del Service, noms estables servei-* com a valors de configuració (CATALEG_URL=http://servei-cataleg:3001), i zero codi d'infraestructura als serveis.
Amb això, els serveis saben parlar (REST, esdeveniments, gRPC/GraphQL quan toqui), tenen una porta (el gateway) i es troben entre ells. Queda l'última pregunta del mòdul, i potser la que més incidents evita a llarg termini: quan l'equip de Catàleg canvia la forma de GET /productes o el de Comandes afegeix un camp a comanda.creada, com ho fan sense trencar ningú? Quins canvis són compatibles i quins no, com es versiona una API REST, un esdeveniment, un .proto o un esquema GraphQL, com es retira una versió i com es pacta el contracte abans d'escriure codi. Aquest és el tema de la lliçó següent: contractes i versionat d'APIs.
Curs de Microserveis
Mòdul 1: Introducció als Microserveis
- Conceptes Bàsics de Microserveis
- Avantatges i Desavantatges dels Microserveis
- Comparació amb l'Arquitectura Monolítica
- Quan Adoptar Microserveis: Criteris de Decisió
- El Cas Pràctic del Curs: la Botiga Online de TechCorp
Mòdul 2: Disseny de Microserveis
- Principis de Disseny de Microserveis
- Descomposició d'Aplicacions Monolítiques
- Definició de Bounded Contexts
- Gestió de Dades: una Base de Dades per Servei
- Consistència Distribuïda: Sagues, CQRS i Event Sourcing
Mòdul 3: Comunicació entre Microserveis
- APIs RESTful
- Missatgeria Asíncrona
- Protocols de Comunicació: gRPC, GraphQL
- API Gateway i Backend for Frontend
- Descobriment de Serveis i Balanceig de Càrrega
- Contractes i Versionat d'APIs
Mòdul 4: Implementació de Microserveis
- Elecció de Tecnologies i Eines
- Desenvolupament d'un Microservei Simple
- Gestió de Configuració
- Integració Pràctica: Consumir APIs i Publicar Esdeveniments
- Proves en Microserveis: Unitàries, d'Integració i de Contracte
Mòdul 5: Desplegament i Orquestració
- Contenidors i Docker
- Orquestració amb Kubernetes
- CI/CD per a Microserveis
- Estratègies de Desplegament: Rolling, Blue-Green i Canary
- Service Mesh: Istio i Linkerd
Mòdul 6: Monitoratge i Manteniment
- Monitoratge i Logging
- Traçabilitat Distribuïda amb OpenTelemetry
- Gestió d'Errors i Recuperació
- Escalabilitat i Rendiment
- SLOs, Alertes i Gestió d'Incidents
Mòdul 7: Seguretat en Microserveis
- Autenticació i Autorització
- Seguretat en la Comunicació
- Pràctiques de Seguretat
- Seguretat en Contenidors i Kubernetes
