Faire survivre PM2 à un redémarrage du serveur

Le serveur a redémarré et le site n'est pas revenu. Il est revenu une heure plus tard, quand quelqu'un a lancé un déploiement. Cela ressemble à un problème de déploiement, et ce n'en est pas un : ce déploiement a simplement relancé pm2 start à la main.

Pour que votre application démarre seule, il faut deux choses distinctes, et chacune ne sert à rien sans l'autre.

Les deux moitiés

pm2 save écrit la liste des processus en cours dans ~/.pm2/dump.pm2. PM2 relance ce que contient ce fichier, et rien d'autre. Un script de déploiement qui fait pm2 delete puis pm2 start sans jamais sauvegarder laisse le dump périmé, ou vide s'il n'a jamais été écrit.

pm2 startup installe une unité systemd qui exécute pm2 resurrect au démarrage, et c'est elle qui lit ce dump.

L'une sans l'autre vous donne un démarrage qui ne lance rien, ou une liste sauvegardée que personne ne lit.

Le piège

Lancer pm2 startup avec un utilisateur non privilégié n'installe strictement rien. La commande détecte systemd, calcule la commande qui installerait l'unité, l'affiche et se termine. La sortie est longue, en vert, et se termine par une ligne qui vous invite à copier-coller la commande. On dirait un succès. Il ne s'est rien passé.

Donc : lancez-la en root, et vérifiez avec systemd plutôt qu'avec ce que PM2 a affiché.

Le bon ordre

L'application doit tourner avant la sauvegarde, sinon vous sauvegardez une liste vide.

# en tant que <user> — vérifier que l'application tourne
pm2 status
# en tant que root — installer l'unité pour cet utilisateur
env PATH=$PATH:/usr/bin pm2 startup systemd -u <user> --hp /home/<user>

Le préfixe env PATH=$PATH:/usr/bin n'est pas décoratif. Le PATH de root ne contient généralement pas Node, et l'unité conserve le chemin avec lequel elle a été générée. Sans ce préfixe, l'unité existe mais ne trouve pas node au démarrage.

# de nouveau en tant que <user> — écrire le dump
pm2 save

Vérifier chaque moitié séparément

Elles échouent séparément, donc on les vérifie séparément.

systemctl is-enabled pm2-<user>

La réponse doit être enabled. Toute autre réponse, disabled ou Failed to get unit file state, signifie que l'unité n'a jamais été installée, quoi qu'ait affiché PM2.

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

<app> doit apparaître. Si le fichier n'existe pas, ou si le grep n'affiche rien, c'est que pm2 save n'a jamais tourné avec l'application en ligne.

Tester sans redémarrer

Inutile de redémarrer une machine de production pour savoir si cela fonctionne.

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

Si <app> est de retour et en ligne, les deux moitiés fonctionnent. C'est exactement ce que fait le démarrage.

Ne lancez jamais pm2 save après pm2 kill. Le démon étant tué, la liste des processus est vide, et la sauvegarde écrit ce vide dans le dump, ce qui annule précisément ce que vous étiez en train de tester. Si le réflexe vous échappe, relancez l'application puis refaites pm2 save.

Mettez pm2 save dans le script de déploiement

Le dump se décale dès qu'un déploiement touche au processus. Si votre script supprime et relance, il doit sauvegarder ensuite :

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

Le || true compte au premier passage, quand il n'y a rien à supprimer et que pm2 delete sort avec un code non nul, ce qui tue tout le script sous set -e.

Sans cette dernière ligne, tout ce qui précède continue de fonctionner jusqu'au premier déploiement qui renomme l'application, change le nombre d'instances ou modifie le point d'entrée. À partir de là, le dump décrit un processus qui n'existe plus, et le redémarrage suivant ressuscite celui-là.

Un mot sur l'ordre de démarrage

L'unité générée porte After=network.target. Elle n'est pas ordonnée après votre base de données. À froid, l'application peut démarrer avant que MariaDB ou PostgreSQL n'accepte les connexions, échouer à sa première requête et sortir.

En général cela se règle tout seul : PM2 relance le processus, la base est prête à la deuxième ou troisième tentative, et le site remonte. L'indice est un compteur de redémarrages supérieur à zéro sur un processus par ailleurs sain, dans la colonne de pm2 status.

Si la reprise n'a pas lieu, alors ordonnez l'unité explicitement :

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

À n'ajouter que si vous l'avez vu échouer. Ordonner une unité derrière un service lent, ou hébergé sur une autre machine, vous échange un démarrage qui réessaie contre un démarrage qui reste bloqué.

Analysez votre site · Plus d'articles · À propos d'AgentReady