Escena Viva ja es configura com cal i ja explica què fa: registres estructurats amb traçabilitat per petició, mètriques a /metriques, sondes /salut/viu i /salut/preparat. Però tot això continua arrencant-se a mà. Algú entra per SSH al servidor, escriu npm start, veu passar les primeres línies de registre i tanca la terminal. I en aquell moment l'aplicació es mor. Aquesta lliçó resol el problema del procés: qui l'arrenca, qui el vigila, qui l'aixeca quan cau i qui el recarrega quan desplegues una versió nova sense tallar ni una compra a mitges. L'eina serà PM2, però l'important són els conceptes: s'apliquen igual si demà fas servir systemd o un orquestrador.
Contingut
- El problema:
npm starten una terminal - Què és un supervisor de processos i quin triar
- PM2: instal·lació i ordres de referència
- El fitxer d'ecosistema d'Escena Viva
- Mode clúster de PM2 enfront del nostre
src/cluster.js reloadenfront derestart: recàrrega sense talls- Reinici automàtic i protecció contra bucles
- Reinici per memòria com a xarxa de seguretat
- Arrencada en engegar la màquina
- Registres de PM2 i la seva rotació
- Variables d'entorn i secrets a PM2
- Desplegament i monitoratge
- El que PM2 no resol
- El problema:
npm start en una terminal
npm start en una terminalQuan executes npm start en una sessió SSH, el procés de Node és fill del teu intèrpret d'ordres, i el teu intèrpret és fill del dimoni SSH. En desconnectar, el nucli envia SIGHUP a la sessió i tot l'arbre s'esfondra. La teva API desapareix. Els apedaçaments clàssics —nohup npm start &, screen, tmux— resolen només aquella part, i deixen cinc problemes sense tocar:
- Si el procés cau per un
uncaughtException(que, recorda, a 11-02 vam decidir que acaba el procés deliberadament), ningú l'aixeca. - Si el servidor es reinicia, l'aplicació no torna sola.
- Els registres van a un fitxer infinit que omplirà el disc.
- No hi ha manera de desplegar sense tall: aturar i arrencar deixa un forat de segons amb errors 502.
- No hi ha visibilitat: quants processos hi ha, quanta memòria fan servir, quantes vegades s'han reiniciat.
Això és exactament el que fa un supervisor.
- Què és un supervisor de processos i quin triar
Un supervisor és un programa la funció única del qual és mantenir altres programes vius: els arrenca, els vigila, els reinicia si acaben, redirigeix la seva sortida i sobreviu al tancament de la teva sessió.
| Supervisor | Què és | A favor | En contra | Quan triar-lo |
|---|---|---|---|---|
| PM2 | Supervisor en Node, específic per a aplicacions Node | Clúster integrat, recàrrega sense talls, CLI còmoda, tauler en terminal | És un procés més que pot fallar; específic de Node | VPS o servidor propi amb una o poques aplicacions Node |
| systemd | L'init del sistema a Linux | Ja està instal·lat, integrat amb l'arrencada, journald, cgroups |
Sense clúster propi (necessites src/cluster.js o plantilles d'unitat), sintaxi àrida |
Servidor Linux on vulguis zero dependències extra |
Docker + restart: always |
El motor de contenidors vigila el contenidor | L'entorn viatja amb l'app; un únic mecanisme per a tot | No reparteix processos dins del contenidor: un per contenidor | Quan ja empaquetes amb Docker (lliçó 11-04) |
| Orquestrador (Kubernetes, ECS, Nomad) | Supervisa contenidors en una flota de màquines | Escalat, sondes, desplegament progressiu, autoreparació | Complexitat i cost operatiu alts | Molts serveis, diversos equips, escala real |
| PaaS (Heroku, Render, Fly) | El proveïdor supervisa per tu | No gestiones res | Menys control, cost per ús | Equips petits que volen anar de pressa (lliçó 11-05) |
Per a Escena Viva sobre un VPS, PM2 és l'elecció raonable: dóna clúster i recàrrega sense talls sense escriure ni una línia de systemd. A 11-04 i 11-05 veurem que, tan bon punt contenidoritzes, el supervisor passa a ser un altre — i aquest solapament és normal, no una errada de disseny.
- PM2: instal·lació i ordres de referència
# Instal.lacio global al servidor (no es dependencia del projecte).
npm install -g pm2
pm2 --versionPM2 s'instal·la global, no com a dependència. Posar-lo a dependencies és un error freqüent: PM2 arrenca la teva aplicació, no en forma part, i ficar-lo a dins complica la imatge Docker de la lliçó següent. A la primera ordre, PM2 arrenca un dimoni de fons (God daemon) que sobreviu a la teva sessió i manté la llista d'aplicacions. Les ordres del CLI parlen amb aquell dimoni.
| Ordre | Què fa |
|---|---|
pm2 start ecosystem.config.js --env production |
Arrenca el que hi ha definit al fitxer d'ecosistema |
pm2 list |
Taula amb estat, CPU, memòria i reinicis de cada aplicació |
pm2 logs escena-viva-api --lines 100 |
Segueix els registres en temps real |
pm2 monit |
Tauler interactiu en terminal: CPU, memòria i registres per procés |
pm2 describe escena-viva-api |
Fitxa completa: rutes, variables, temps actiu, reinicis, PID |
pm2 restart <app> |
Atura i arrenca (hi ha tall de servei) |
pm2 reload <app> |
Recàrrega seqüencial sense talls (mode clúster) |
pm2 stop <app> |
Atura sense eliminar de la llista |
pm2 delete <app> |
Elimina de la llista de PM2 |
pm2 save |
Desa la llista actual per restaurar-la en arrencar la màquina |
pm2 startup |
Genera el servei de systemd que arrenca PM2 en engegar |
pm2 flush |
Buida els fitxers de registre |
pm2 scale escena-viva-api 4 |
Canvia el nombre d'instàncies en calent |
Una nota sobre estil: encara que pm2 start src/servidor.js -i 4 funciona, en un servidor real mai no s'arrenca així. La configuració quedaria només a l'historial de bash de qui la va escriure. Tot va al fitxer d'ecosistema.
- El fitxer d'ecosistema d'Escena Viva
Escena Viva té dos processos per desplegar, i això ve directament del mòdul 10: l'API HTTP i el consumidor de la cua de BullMQ (src/processos/consumidor-entrades.js), que emet els PDF de les entrades i els correus. Són dues aplicacions diferents, amb perfils de recursos diferents, i s'han d'escalar per separat: durant l'estrena del Festival de Jazz pots necessitar més consumidors sense necessitar més API.
// ecosystem.config.js
'use strict';
module.exports = {
apps: [
{
name: 'escena-viva-api',
script: './src/servidor.js',
// 0 = tantes instancies com nuclis disponibles.
instances: 0,
exec_mode: 'cluster',
// --- Arrencada i aturada ordenada ---
wait_ready: true, // Espera process.send('ready'), no el 'listen'.
listen_timeout: 10000, // Si no arriba 'ready' en 10 s, es considera fallit.
kill_timeout: 15000, // Marge despres de SIGINT abans de SIGKILL: drenatge del M6.
// --- Politica de reinici ---
autorestart: true,
max_restarts: 10,
min_uptime: '30s',
exp_backoff_restart_delay: 200,
max_memory_restart: '700M',
// --- Registres: pino ja escriu JSON a stdout (11-02) ---
out_file: '/var/log/escena-viva/api.log',
error_file: '/var/log/escena-viva/api.error.log',
merge_logs: true,
time: false, // NO anteposar marca de temps: trenca el JSON.
// --- Configuracio per entorn (nomes valors NO secrets) ---
env: {
NODE_ENV: 'development',
PORT: 3000,
NIVELL_REGISTRE: 'debug',
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
NIVELL_REGISTRE: 'info',
CONFIAR_EN_PROXY: 'true',
},
},
{
name: 'escena-viva-consumidor',
script: './src/processos/consumidor-entrades.js',
// El consumidor NO escolta en cap port: no hi ha port per compartir.
instances: 2,
exec_mode: 'fork',
wait_ready: false,
kill_timeout: 30000, // Marge mes gran: pot estar generant un PDF.
autorestart: true,
max_restarts: 10,
min_uptime: '60s',
exp_backoff_restart_delay: 500,
max_memory_restart: '900M', // Els worker threads de PDF gasten mes.
out_file: '/var/log/escena-viva/consumidor.log',
error_file: '/var/log/escena-viva/consumidor.error.log',
merge_logs: true,
time: false,
env: {
NODE_ENV: 'development',
CONCURRENCIA_CUA: 2,
},
env_production: {
NODE_ENV: 'production',
CONCURRENCIA_CUA: 5,
},
},
],
};Les decisions que hi ha al darrere:
exec_mode: 'cluster'només per a l'API. El mode clúster existeix per repartir un port entre diversos processos. El consumidor no escolta en cap port: BullMQ reparteix la feina a través de Redis. Fer servir clúster allà no aportaria res, així que va enfork.instances: 0a l'API,2fixes al consumidor. L'API escala amb els nuclis; el consumidor escala amb la càrrega de la cua i amb la memòria que consumeixen els worker threads de generació de PDF (mòdul 10). Són eixos diferents.kill_timeoutdiferent a cadascuna. L'API drena peticions HTTP: 15 segons són de sobres. El consumidor pot estar a mitja generació d'un PDF: 30 segons eviten matar-lo amb feina a mitges.time: false. És subtil i molt important: si PM2 anteposa la seva pròpia marca de temps a cada línia, el JSON de pino deixa de ser JSON vàlid i l'agregador no el pot indexar. Amb registre estructurat, PM2 s'ha de limitar a redirigir.env_productionno conté ni un secret. Hi tornarem a l'apartat 11.
S'arrenca amb:
Sense --env production, PM2 fa servir el bloc env, és a dir, desenvolupament. És una de les maneres més habituals d'acabar amb NODE_ENV=development en un servidor de producció, amb totes les conseqüències que vam veure a 11-01.
- Mode clúster de PM2 enfront del nostre
src/cluster.js
src/cluster.jsAl mòdul 10 vam escriure src/cluster.js a mà: un procés primari que bifurca N treballadors amb node:cluster, els supervisa, els reemplaça si moren i fa recàrrega seqüencial sense talls. Funciona. PM2 en exec_mode: 'cluster' fa exactament el mateix: fa servir el mòdul cluster de Node per sota, amb el mateix mecanisme de repartiment del sòcol entre processos fill. No és màgia ni una tecnologia diferent.
src/cluster.js propi |
Mode clúster de PM2 | |
|---|---|---|
| Repartiment del port | node:cluster |
node:cluster (idèntic) |
| Reemplaçament de treballadors morts | El mantens tu | Inclòs |
| Recàrrega seqüencial | La vas escriure tu | pm2 reload |
| Canviar N instàncies en calent | No, cal reiniciar | pm2 scale |
| Reinici per memòria | S'hauria d'implementar | max_memory_restart |
| Mètriques per treballador | S'hauria d'implementar | pm2 monit, pm2 describe |
| Dependència externa | Cap | PM2 instal·lat i funcionant |
| Dins d'un contenidor | Val, i és l'habitual | Sobra: l'orquestrador ja escala |
Què s'hi guanya: deixes de mantenir i provar codi d'infraestructura que no és el teu negoci, i obtens de franc escalat en calent, reinici per memòria i monitoratge. Què s'hi perd: una dependència externa que cal instal·lar i actualitzar al servidor, i menys control fi sobre el cicle de vida de cada treballador.
I per què haver-lo escrit a mà no va ser temps perdut: perquè ara entens que instances: 4 no crea quatre fils sinó quatre processos amb memòria separada —d'aquí que les sessions i la limitació de peticions haguessin d'anar a Redis al mòdul 10—, que el pool de Sequelize es multiplica per quatre (i que per això src/db/sequelize.js el dimensiona segons el nombre de treballadors), i que l'estat en memòria d'un procés no existeix per als altres tres. Qui mai no ha escrit un clúster a mà configura instances: 16 i després es pregunta per què s'exhaureixen les connexions de PostgreSQL. En producció amb PM2, src/cluster.js deixa de fer-se servir: script apunta a src/servidor.js i PM2 posa el clúster. El fitxer es queda al repositori com a alternativa per a entorns on PM2 no hi és (dins d'un contenidor, per exemple, encara que allà tampoc no sol caldre).
reload enfront de restart: recàrrega sense talls
reload enfront de restart: recàrrega sense tallsAquesta és la raó principal per fer servir PM2 en un servidor propi. pm2 restart: mata tots els processos i n'arrenca de nous. Entre una cosa i l'altra hi ha un forat d'un a tres segons en què ningú escolta al port. Tot el que arribi en aquell forat és un 502.
pm2 reload (només en mode clúster): reemplaça els treballadors d'un en un. Arrenca el nou, espera que estigui llest, retira el vell del repartiment de connexions, li dóna temps per acabar el que té entre mans i el mata. Sempre queda algú escoltant. Zero errors per a l'usuari.
sequenceDiagram
participant PM2
participant T1 as Treballador 1 (vell)
participant T1n as Treballador 1 (nou)
participant T2 as Treballador 2 (vell)
PM2->>T1n: fork amb el codi nou
T1n-->>PM2: process.send('ready')
PM2->>T1: SIGINT
T1->>T1: deixa d'acceptar, drena peticions
T1-->>PM2: exit(0)
PM2->>PM2: repeteix amb el treballador 2
Note over T2: continua atenent tot el temps
Perquè això funcioni de veritat calen tres peces coordinades, i les dues primeres ja existeixen des del mòdul 6. 1. Aturada ordenada a src/servidor.js. PM2 envia SIGINT (no SIGTERM) als processos en clúster. El nostre gestor ja escolta totes dues senyals: deixa d'acceptar connexions, marca estat.acceptantTrafic = false perquè /salut/preparat retorni 503, tanca les connexions ocioses amb closeIdleConnections, espera les actives i surt amb un temporitzador de gràcia.
2. El senyal ready. Amb wait_ready: true, PM2 no considera viu el treballador fins que aquest ho diu explícitament:
// src/servidor.js (fragment de l'arrencada)
async function arrencarServidor() {
await connectarBaseDades();
await obtenirClientRedis().ping();
const app = crearAplicacio(dependencies);
const servidor = http.createServer(app);
await new Promise((resoldre) => servidor.listen(configuracio.port, resoldre));
logger.info(
{ port: configuracio.port, entorn: configuracio.entorn, pid: process.pid },
'servidor escoltant'
);
// Avisem PM2 NOMES quan tot esta realment llest:
// base de dades connectada, Redis responent i port escoltant.
if (process.send) {
process.send('ready');
}
return servidor;
}La diferència és enorme. Sense wait_ready, PM2 dóna per bo el treballador tan bon punt el procés existeix, i retira el vell quan el nou encara s'està connectant a PostgreSQL: els primers usuaris veuen errors. Amb wait_ready, la finestra de reemplaçament només s'obre quan el nou pot atendre de debò. Fixa't en l'if (process.send): només existeix quan el procés ha estat bifurcat per un altre, així que arrencar directament amb node src/servidor.js continua funcionant. 3. kill_timeout suficient. És el temps que PM2 espera des de SIGINT fins a SIGKILL. Ha de ser més gran que el drenatge més llarg que espera la teva aplicació. Si tens peticions de fins a 10 segons, un kill_timeout de 5000 mata respostes a mitges i la feina del mòdul 6 no serveix de res. Els nostres 15 segons a l'API cobreixen el pitjor cas amb marge.
I listen_timeout és el revers: si ready no arriba en 10 segons, PM2 assumeix que l'arrencada ha fallat. Protegeix contra l'escenari en què una versió nova no aconsegueix connectar-se a la base de dades i es queda penjada per sempre, deixant el desplegament a mitges.
- Reinici automàtic i protecció contra bucles
Amb autorestart: true, si un procés acaba, PM2 n'arrenca un altre. Bé. Però imagina que la versió nova té un error a l'arrencada: falta una variable d'entorn i src/config/index.js fa process.exit(1) (11-01). PM2 el reinicia. Falla un altre cop. El reinicia. En bucle, centenars de vegades per minut, cremant CPU i omplint el disc de registres. Tres paràmetres ho eviten:
| Paràmetre | Què fa | Valor a Escena Viva |
|---|---|---|
min_uptime |
Temps mínim viu per considerar l'arrencada «bona» | 30s (API), 60s (consumidor) |
max_restarts |
Reinicis consecutius per sota de min_uptime abans de rendir-se |
10 |
exp_backoff_restart_delay |
Retard creixent entre reintents (200, 400, 800 ms...) | 200 (API), 500 (consumidor) |
Amb aquesta combinació, un procés que mor al segon d'arrencar es reintenta deu vegades amb espera creixent i després passa a estat errored, on es queda. PM2 deixa d'insistir i espera intervenció humana, que és el correcte: l'aplicació està trencada i reintentar no l'arreglarà. I un avís que val per tota la lliçó: reiniciar sense arreglar la causa és tapar un problema. Si pm2 list mostra 3.400 reinicis a la columna ↺, no tens un sistema resilient: tens un defecte que es manifesta cada pocs minuts i un supervisor tapant-lo. Revisa aquella columna cada vegada que la miris. Un valor que creix és una investigació pendent, no una tranquil·litat.
- Reinici per memòria com a xarxa de seguretat
max_memory_restart: '700M' reinicia el procés quan supera aquell consum. És útil, però cal entendre bé què és i què no és.
Què és: una xarxa de seguretat. Davant d'una fuita de memòria lenta —d'aquelles que el mòdul 9 ens va ensenyar a diagnosticar amb instantànies del munt—, evita que el procés creixi fins que el nucli el mati per falta de memòria (OOM killer) o fins que el recol·lector d'escombraries es passi el dia treballant i el p99 es dispari. Què no és: una solució. Si el procés es reinicia per memòria cada dues hores, tens una fuita i cal trobar-la. El reinici compra temps per investigar, no tanca l'assumpte.
Per triar el valor: mira el consum estable en producció amb pm2 monit i posa-hi un 60-80 % per sobre. Massa baix i reiniciaràs sense motiu sota càrrega alta; massa alt i la xarxa de seguretat no actua mai. Tingues en compte a més que el límit és per procés: amb 4 instàncies a 700 MB necessites 2,8 GB només per a l'API, més el consumidor. Aquest càlcul es torna crític al contenidor de la lliçó següent.
- Arrencada en engegar la màquina
Un supervisor que no sobreviu a un reinici del servidor resol la meitat del problema. Dues ordres:
# 1. Genera i instal.la el servei de systemd que arrenca PM2 en engegar.
# Imprimeix una ordre amb sudo que has d'executar tal qual.
pm2 startup
# 2. Desa la llista ACTUAL d'aplicacions per restaurar-la a l'arrencada.
pm2 saveL'ordre importa i l'oblit és clàssic: pm2 startup fa que PM2 arrenqui, però sense llista d'aplicacions. És pm2 save el que desa el bolcat (~/.pm2/dump.pm2) que PM2 restaura. Si canvies el fitxer d'ecosistema i no tornes a fer pm2 save, després del pròxim reinici del servidor tornarà la configuració antiga. Regla: després de qualsevol canvi a les aplicacions, pm2 save. Nota curiosa i sana: per sota, pm2 startup crea una unitat de systemd. És a dir, systemd supervisa PM2 i PM2 supervisa les teves aplicacions. Si això et sembla una capa de més, és una intuïció correcta, i és part de l'argument per passar a contenidors a la lliçó següent.
- Registres de PM2 i la seva rotació
PM2 captura stdout i stderr de cada procés i els escriu als fitxers indicats a out_file i error_file. Com que pino ja escriu JSON estructurat (11-02), PM2 només ha de redirigir: res d'afegir marques de temps ni prefixos.
out_file: '/var/log/escena-viva/api.log',
error_file: '/var/log/escena-viva/api.error.log',
merge_logs: true, // Totes les instancies al mateix fitxer: ja porten pid a dins.
time: false, // Sense prefix: preserva el JSON valid linia a linia.merge_logs: true ajunta les quatre instàncies en un fitxer. Podria semblar confús, però cada línia JSON de pino ja porta pid i idPeticio, així que separar per fitxer no aporta res i complica la consulta.
Sense rotació, aquells fitxers creixen fins a omplir el disc, i un disc ple tomba l'aplicació i la base de dades. El mòdul de PM2 que ho resol:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 14 # 14 fitxers rotats
pm2 set pm2-logrotate:compress true # gzip dels antics
pm2 set pm2-logrotate:rotateInterval '0 0 * * *' # a mitjanitTot i així, recorda el factor XI dels dotze factors (11-02): l'ideal és que els registres no acabin en fitxers del servidor sinó en un agregador. Un patró habitual amb PM2 és deixar que escrigui a fitxer i posar un agent lleuger (Vector, Fluent Bit, Promtail) que llegeixi aquells fitxers i els enviï. Els fitxers passen a ser una memòria intermèdia temporal, no el destí final.
- Variables d'entorn i secrets a PM2
Els blocs env i env_production del fitxer d'ecosistema són còmodes... i són un risc, perquè aquell fitxer està versionat. Posar-hi JWT_SECRET és exactament el suspens de la prova del repositori públic de 11-01. Al fitxer d'ecosistema hi van només valors no secrets: NODE_ENV, PORT, NIVELL_REGISTRE, CONFIAR_EN_PROXY. Els secrets arriben per una altra via. Tres opcions, de menys a més recomanable:
a) Fitxer .env fora del control de versions, carregat per dotenv. És el que ja fa src/config/index.js. Al servidor existeix /opt/escena-viva/.env amb permisos 600 i propietari l'usuari de l'aplicació. Simple i suficient per a un VPS. b) EnvironmentFile de systemd a la unitat que genera pm2 startup. Les variables entren al dimoni de PM2 i les hereten totes les aplicacions. Menys flexible si tens diverses aplicacions amb secrets diferents.
c) Exportar-les abans d'arrencar PM2, obtenint-les d'un gestor de secrets:
# A l'script de desplegament, no al repositori.
export JWT_SECRET=$(vault kv get -field=jwt secret/escena-viva)
export SESSIO_SECRET=$(vault kv get -field=sessio secret/escena-viva)
pm2 reload ecosystem.config.js --env production --update-env--update-env és imprescindible: PM2 recorda l'entorn amb què va arrencar una aplicació i el reutilitza als reinicis. Sense aquell indicador, canvies una variable, recarregues, i continua executant-se amb el valor vell. És una de les confusions més freqüents amb PM2, i ha costat més d'una hora de depuració a molta gent. Si has rotat un secret seguint el procediment de 11-01 i sembla que no ha tingut efecte, comença per aquí.
- Desplegament i monitoratge
PM2 inclou un sistema de desplegament per SSH, pm2 deploy, que es configura al mateix fitxer:
// ecosystem.config.js (bloc addicional)
deploy: {
production: {
user: 'escena',
host: ['api1.escenaviva.test'],
ref: 'origin/master',
repo: '[email protected]:escena-viva/plataforma.git',
path: '/opt/escena-viva',
'post-deploy':
'npm ci --omit=dev && npm run migrar && pm2 reload ecosystem.config.js --env production --update-env',
},
},Amb pm2 deploy production es connecta per SSH, fa git fetch i checkout, executa post-deploy i recarrega sense talls. Fixa't en l'ordre: npm ci --omit=dev (el pagament del mòdul 5), migracions abans de recarregar, i reload, mai restart. És una solució digna per a un VPS i un equip petit. Té límits clars: desplega des del repositori a cada màquina, així que cada servidor construeix pel seu compte i pot acabar amb dependències lleugerament diferents. L'alternativa —construir un artefacte i promoure'l— és el que veurem amb Docker a 11-04 i amb la canonada de CI a 11-06. Mentrestant, pm2 deploy funciona.
Per al monitoratge diari:
pm2 monit: tauler interactiu amb CPU i memòria per procés i registres en viu. Útil durant un desplegament o un pic de càrrega com l'estrena del Festival de Jazz.pm2 describe escena-viva-api: la fitxa completa d'una aplicació. El primer que cal mirar en investigar: nombre de reinicis, temps actiu, entorn efectiu i rutes de registre.pm2 list: la vista ràpida. Vigila la columna de reinicis (↺) i la memòria.
Aquestes eines són per a inspecció puntual. El monitoratge continu és el de 11-02: mètriques a Prometheus, taulers a Grafana i alertes sobre símptomes. pm2 monit no et desperta de nit.
- El que PM2 no resol
I aquí arriba el tancament honest. PM2 resol el procés, no l'entorn. PM2 garanteix que la teva aplicació estigui viva, es reiniciï si cau, es recarregui sense talls i arrenqui amb la màquina. Tot això suposant que la màquina sigui correcta. I aquella suposició és enorme:
- La versió de Node ha de ser la que exigeix
engines(>=24.5.0 <25). Si el servidor té la 20,require('node:...')pot funcionar i altres coses no, i ho descobriràs en producció. - Les llibreries del sistema hi han de ser:
bcryptcompila contra la libc del sistema, les fonts tipogràfiques per generar PDF hi han de ser,libpqper a PostgreSQL. - Les variables d'entorn han d'estar posades, amb permisos correctes i actualitzades.
- L'usuari, els directoris, els permisos i les rutes de registre han d'existir.
Tot això es configura a mà a cada servidor. I tan bon punt hi ha dos servidors, comencen a divergir: un té una versió d'OpenSSL diferent, en un altre algú va instal·lar alguna cosa per depurar i no la va treure. El resultat és el clàssic «al servidor A funciona i al B no», que és la versió adulta del «a la meva màquina funciona». La resposta és empaquetar l'entorn juntament amb l'aplicació, de manera que el que desplegues no sigui codi que s'executa sobre una màquina desconeguda, sinó un artefacte que porta el seu propi sistema de fitxers a dins. Això és un contenidor.
Errors Comuns i Consells
- Arrencar sense
--env production. PM2 fa servir el blocenv(desenvolupament). Comprova sempre ambpm2 describequinNODE_ENVestà efectivament en ús. - Oblidar
--update-envdesprés de canviar una variable. PM2 reutilitza l'entorn desat i el teu canvi no fa efecte. - Usar
restarton tocavareload. Un tall de dos segons a cada desplegament, evitable amb una lletra diferent. kill_timeoutmassa curt. PM2 mata a mitja resposta i anul·la l'aturada ordenada del mòdul 6.- Deixar
time: trueamb registre JSON. El prefix trenca el JSON i l'agregador no pot indexar res. - Secrets a
env_production. Estan versionats. És el mateix error de 11-01 amb una altra roba. - Oblidar
pm2 savedesprés de canviar la configuració. En reiniciar el servidor torna la configuració anterior, de vegades mesos després, i ningú entén què passa. - Ignorar la columna de reinicis. Un comptador que creix és un defecte tapat pel supervisor.
- Consell: executa PM2 amb un usuari sense privilegis, mai com a root. I si l'aplicació necessités el port 80 (mòdul 4), posa-hi al davant un servidor intermediari invers en comptes de donar privilegis a Node.
- Consell: fixa la versió de PM2 al servidor i actualitza-la expressament. És infraestructura, i una actualització sorpresa durant una venda és una mala nit.
Exercicis
Exercici 1 — Verificar la recàrrega sense talls
Amb l'API arrencada en mode clúster amb quatre instàncies, llança autocannon (mòdul 10) contra /api/esdeveniments durant 30 segons i executa pm2 reload escena-viva-api a mitja prova. Comprova que no apareix cap error ni cap 502. Després repeteix-ho amb pm2 restart i compara.
Exercici 2 — Simular un bucle de reinicis
Provoca deliberadament una fallada d'arrencada (treu JWT_SECRET de l'entorn) i observa el comportament de PM2 amb pm2 logs i pm2 list. Comprova que després de max_restarts intents l'aplicació queda en estat errored. Documenta quant triga a rendir-se amb exp_backoff_restart_delay: 200.
Exercici 3 — Escalar el consumidor
La cua d'entrades acumula 2.000 tasques pendents durant la venda anticipada del Festival de Jazz. Escala el consumidor a sis instàncies sense aturar l'API, verifica l'efecte sobre la mètrica escenaviva_cua_pendents de 11-02 i explica quin altre límit podria assolir-se abans que ajudi afegir més instàncies.
Solucions
Exercici 1.
pm2 start ecosystem.config.js --env production
npx autocannon -c 50 -d 30 http://localhost:3000/api/esdeveniments &
sleep 10 && pm2 reload escena-viva-apiAmb reload, l'informe d'autocannon mostra non-2xx: 0 i errors: 0; a la latència s'aprecia com a molt un lleuger augment del p99 mentre un treballador menys atén el trànsit. Amb restart apareixen desenes d'errors de connexió rebutjada concentrats en l'instant del tall. La diferència es deu completament a les tres peces de l'apartat 6: aturada ordenada, wait_ready i kill_timeout suficient. Si en provar-ho amb reload també hi veus errors, revisa que process.send('ready') s'estigui enviant de debò: sense ell, wait_ready: true faria que l'arrencada expirés.
Exercici 2. Amb els reintents exponencials a partir de 200 ms (200, 400, 800, 1600...), deu intents sumen al voltant de 100 segons abans que PM2 es rendeixi. A pm2 logs es veu repetit el missatge de src/config/index.js:
I aquí s'aprecia el valor de la validació de 11-01: el motiu de la fallada apareix a la primera línia de cada intent, en comptes d'un críptic error intern de jsonwebtoken tres hores després. A pm2 list l'estat final és errored i el comptador de reinicis s'atura a 10.
Exercici 3.
L'escalat és immediat i no afecta l'API, perquè són aplicacions independents. escenaviva_cua_pendents hauria de baixar amb un pendent aproximadament tres vegades més gran. El límit que s'assoleix abans és el de connexions. Cada consumidor obre connexions a Redis (BullMQ en fa servir diverses per treballador) i a PostgreSQL per registrar les entrades emeses. Amb 6 consumidors × concurrència 5, més les 4 instàncies de l'API amb el seu pool, és fàcil superar el max_connections de PostgreSQL o el límit de clients de Redis. És la mateixa aritmètica que ens va obligar al mòdul 10 a dimensionar el pool de Sequelize segons el nombre de treballadors, i la que tornarà a aparèixer a 11-05 amb els límits de connexions dels plans gestionats. Afegir instàncies sense recalcular aquell producte converteix un problema de rendiment en una caiguda.
Conclusió
Escena Viva ja no depèn d'una sessió SSH oberta. PM2 la supervisa: dues aplicacions declarades en un fitxer d'ecosistema versionat —l'API en mode clúster i el consumidor de la cua en mode fork, cadascun amb la seva política de recursos—, recàrrega sense talls recolzada en l'aturada ordenada del mòdul 6 i en el senyal ready que ara emetem, protecció contra bucles de reinici, reinici per memòria com a xarxa de seguretat davant de fuites, arrencada automàtica amb la màquina i registres rotats sense trencar el JSON de pino. A més hem vist per què escriure src/cluster.js a mà al mòdul 10 continua sent valuós encara que ara no el fem servir: entendre el repartiment per processos és el que permet configurar instances sense exhaurir les connexions de la base de dades. Però PM2 supervisa el procés, no la màquina. Continua calent que el servidor tingui la versió exacta de Node, les llibreries del sistema per compilar bcrypt, les fonts tipogràfiques per als PDF i la configuració correcta — i que el segon servidor sigui idèntic al primer, cosa que no passa mai. A la propera lliçó, Empaquetatge amb Docker, fem que l'entorn viatgi amb l'aplicació: un Dockerfile multietapa explicat línia a línia, usuari no root, CMD en forma d'exec perquè SIGTERM arribi de debò al procés, HEALTHCHECK recolzat en /salut/preparat, i un docker compose amb API, consumidor, PostgreSQL, MongoDB i Redis que per fi et donarà l'entorn de desenvolupament complet que portes demanant des del mòdul 7.
Curs de Node.js: De Principiant a Avançat
Mòdul 1: Introducció a Node.js
- Què és Node.js?
- Instal·lació i Configuració de l'Entorn
- El Teu Primer Programa en Node.js
- El REPL de Node.js
- JavaScript Modern per a Node.js
- El Projecte del Curs: la Plataforma Escena Viva
Mòdul 2: Conceptes Bàsics
- Arquitectura de Node.js
- El Bucle d'Esdeveniments (Event Loop)
- Callbacks i Programació Asíncrona
- Promeses i async/await
- Esdeveniments i EventEmitter
- Mòduls CommonJS i require()
- Mòduls ES i Interoperabilitat
Mòdul 3: Sistema de Fitxers i E/S
- Lectura i Escriptura de Fitxers
- El Mòdul fs a Fons
- Rutes Multiplataforma amb el Mòdul path
- Treballant amb Streams
- Streams de Transformació i pipeline
- Buffers i Dades Binàries
Mòdul 4: HTTP i Servidors Web
- Creant un Servidor HTTP Simple
- Gestió de Sol·licituds i Respostes
- Enrutament Manual
- Servint Fitxers Estàtics
- Rebent Dades: Cossos de Petició i JSON
- Consumint APIs Externes des de Node.js
Mòdul 5: NPM i Gestió de Paquets
- Introducció a NPM i package.json
- Instal·lació i Ús de Paquets
- Versionat Semàntic i package-lock
- Scripts d'npm i Automatització del Projecte
- Creació i Publicació de Paquets
- Seguretat i Manteniment de Dependències
Mòdul 6: Framework Express.js
- Introducció a Express.js
- Configuració d'una Aplicació Express
- Enrutament a Express
- Middleware
- Middleware de Tercers Essencials
- Validació de Dades d'Entrada
- Gestió d'Errors
Mòdul 7: Bases de Dades i ORMs
- Introducció a les Bases de Dades
- Usant MongoDB amb Mongoose
- Operacions CRUD
- Relacions, Poblat i Consultes Avançades
- Usant Bases de Dades SQL amb Sequelize
- Migracions, Transaccions i Dades de Prova
Mòdul 8: Autenticació i Autorització
- Introducció a l'Autenticació
- Registre d'Usuaris i Hash de Contrasenyes
- Sessions i Galetes amb Passport.js
- Autenticació amb JWT
- Control d'Accés Basat en Rols
- Bones Pràctiques de Seguretat en APIs
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb Mocha i Chai
- Dobles de Prova amb Sinon
- Proves d'Integració
- Cobertura i Automatització de les Proves
- Depuració d'Aplicacions Node.js
Mòdul 10: Temes Avançats
- El Mòdul Cluster
- Fils de Treball (Worker Threads)
- Memòria Cau i Cues de Treball amb Redis
- Optimització del Rendiment
- Construcció d'APIs RESTful
- GraphQL amb Node.js
Mòdul 11: Desplegament i DevOps
- Configuració i Variables d'Entorn
- Registre i Monitoratge en Producció
- Usant PM2 per a la Gestió de Processos
- Empaquetatge amb Docker
- Desplegant a Heroku i Altres PaaS
- Integració i Desplegament Continus
