Far sopravvivere PM2 a un riavvio del server

Il server si è riavviato e il sito non è tornato. È tornato un'ora dopo, quando qualcuno ha lanciato un deploy. Sembra un problema di deploy e non lo è: quel deploy si è limitato a eseguire di nuovo pm2 start a mano.

Perché la tua applicazione riparta da sola servono due cose distinte, e ognuna senza l'altra non serve a niente.

Le due metà

pm2 save scrive l'elenco dei processi attuali in ~/.pm2/dump.pm2. PM2 rilancia quello che c'è in quel file e nient'altro. Uno script di deploy che fa pm2 delete e pm2 start ma non salva mai lascia il dump vecchio, o vuoto se non è mai stato scritto.

pm2 startup installa un'unità systemd che al boot esegue pm2 resurrect, ed è lei a leggere quel dump.

Una senza l'altra ti dà un avvio che non tira su niente, oppure un elenco salvato che non legge nessuno.

La trappola

Eseguire pm2 startup con un utente senza privilegi non installa assolutamente nulla. Il comando rileva systemd, calcola il comando che installerebbe l'unità, lo stampa e termina. L'output è lungo, verde, e finisce con una riga che ti invita a copiare il comando. Sembra un successo. Non è successo niente.

Quindi: eseguilo come root, e verifica con systemd invece che con quello che ha stampato PM2.

L'ordine giusto

L'applicazione deve essere in esecuzione prima di salvare, altrimenti salvi un elenco vuoto.

# come <user> — conferma che l'applicazione gira
pm2 status
# come root — installa l'unità per quell'utente
env PATH=$PATH:/usr/bin pm2 startup systemd -u <user> --hp /home/<user>

Il prefisso env PATH=$PATH:/usr/bin non è un ornamento. Il PATH di root di solito non contiene Node, e l'unità si porta dietro il percorso con cui è stata generata. Senza quel prefisso l'unità esiste, ma al boot non trova node.

# di nuovo come <user> — scrivi il dump
pm2 save

Verifica ogni metà separatamente

Si rompono in modo indipendente, quindi si controllano in modo indipendente.

systemctl is-enabled pm2-<user>

Deve rispondere enabled. Qualsiasi altra cosa, disabled o Failed to get unit file state, vuol dire che l'unità non è mai stata installata, checché ne abbia detto PM2.

grep -o '"name":"[^"]*"' /home/<user>/.pm2/dump.pm2

Lì deve comparire <app>. Se il file non esiste, o il grep non stampa niente, pm2 save non è mai stato eseguito con l'applicazione online.

Provalo senza riavviare

Non serve riavviare una macchina in produzione per sapere se funziona.

# come <user>
pm2 kill
# come root
systemctl start pm2-<user>
# come <user>
pm2 status
curl -sf http://127.0.0.1:3000/health

Se <app> è tornata online, entrambe le metà funzionano. È esattamente quello che fa il boot.

Non eseguire mai pm2 save dopo pm2 kill. Con il demone ucciso l'elenco dei processi è vuoto, e salvando scrivi quel vuoto nel dump, disfacendo proprio la cosa che stavi provando. Se ti scappa per riflesso, riavvia l'applicazione e rifai pm2 save.

Metti pm2 save nello script di deploy

Il dump si disallinea appena un deploy tocca il processo. Se il tuo script cancella e riavvia, deve salvare subito dopo:

pm2 delete <app> || true
pm2 start ecosystem.config.cjs --only <app>
pm2 save

Il || true conta alla prima esecuzione, quando non c'è niente da cancellare e pm2 delete esce con codice diverso da zero, cosa che sotto set -e ammazza tutto lo script.

Senza quell'ultima riga tutto quanto sopra continua a funzionare fino al primo deploy che rinomina l'applicazione, cambia il numero di istanze o sposta il punto di ingresso. Da lì in poi il dump descrive un processo che non esiste più, e il riavvio successivo resuscita quello.

Un'ultima cosa sull'ordine di avvio

L'unità generata porta After=network.target. Non è ordinata dopo il tuo database. A freddo l'applicazione può partire prima che MariaDB o PostgreSQL accettino connessioni, fallire alla prima query e uscire.

Di solito si risolve da sé: PM2 riavvia il processo, al secondo o terzo tentativo il database è pronto e il sito sale. L'indizio è un contatore di riavvii sopra lo zero su un processo per il resto sano, nella colonna di pm2 status.

Se invece non si riprende, allora ordina l'unità in modo esplicito:

sudo systemctl edit pm2-<user>
[Unit]
After=network.target mariadb.service

Aggiungilo solo se l'hai visto fallire. Mettere un'unità dietro a un servizio lento, o che vive su un'altra macchina, ti scambia un avvio che riprova con uno che resta appeso.

Analizza il tuo sito · Altri articoli · Chi siamo