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

  1. El problema: instàncies efímeres amb IPs dinàmiques
  2. Registre de serveis: qui anota que una instància existeix
  3. Descobriment de la banda del client
  4. Descobriment de la banda del servidor
  5. Comparativa dels dos models
  6. Eines: Consul i Eureka
  7. Descobriment natiu de Kubernetes: Service, Endpoints i DNS
  8. Balanceig de càrrega: algorismes i capes
  9. On passa el balanceig en cada model
  10. Health checks: liveness davant de readiness
  11. La decisió de TechCorp

  1. 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).

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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 amb PUT; 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.consul resol 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:

npm install consul
// 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.

  1. Descobriment natiu de Kubernetes: Service, Endpoints i DNS

Kubernetes 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 Service selecciona 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
  1. El pod de Comandes resol el nom servei-cataleg a CoreDNS, el DNS intern del clúster. El nom complet és servei-cataleg.<namespace>.svc.cluster.local, però des del mateix namespace n'hi ha prou amb el nom curt.
  2. CoreDNS retorna la ClusterIP del Service, que no canvia mai mentre el Service existeixi.
  3. Comandes obre una connexió a aquella IP virtual i al port 3001.
  4. A cada node, kube-proxy manté regles de xarxa (iptables o IPVS) que intercepten el trànsit cap a les ClusterIP.
  5. 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 contenidor

Un 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.

  1. 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.

  1. 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) : entre serveis interns i del gateway cap als serveis
Ingress / gateway Traefik, NGINX Ingress Capa 7: per ruta, capçalera, pes : 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.

  1. 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) , 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:

{ "estat": "no llest", "dependencies": { "mongodb": "fallada: timeout", "rabbitmq": "ok" } }

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.

  1. La decisió de TechCorp

  • Descobriment: el natiu de Kubernetes. Un Service per microservei amb nom estable servei-cataleg, servei-comandes, servei-pagaments, servei-clients, servei-notificacions, servei-inventari, bff-mobil, més rabbitmq i 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/live i /health/ready a 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 Service es 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. SIGTERMaturant = true → esperar uns segons → tancar.
  • Health checks cars. Un SELECT COUNT(*) FROM comandes cada 5 s per rèplica és una càrrega inventada. SELECT 1 o ping.
  • Exposar /health pel 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 500 a tot.
  • RabbitMQ: sí, amb matís. Comandes publica via outbox (02-05): POST /comandes desa comanda i esdeveniment a PostgreSQL i el relay publica després. Estrictament, podria acceptar comandes sense RabbitMQ; però també consumeix comandes.saga i sense ell la saga no avança. TechCorp l'inclou: una comanda acceptada que no pot avançar és pitjor experiència que un 503 breu. (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 respondre 503 DEPENDENCIA_NO_DISPONIBLE nomé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

Mòdul 2: Disseny de Microserveis

Mòdul 3: Comunicació entre Microserveis

Mòdul 4: Implementació de Microserveis

Mòdul 5: Desplegament i Orquestració

Mòdul 6: Monitoratge i Manteniment

Mòdul 7: Seguretat en Microserveis

Mòdul 8: Casos d'Estudi i Exemples Pràctics

© Copyright 2026. Tots els drets reservats