Node JS - Déploiement et Zero Downtime avec systemd

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous.

Pour tester Next.js, je suis en train de me faire un petit site web perso.

Mais je ne comprend pas comment en Node JS, on peut faire du Zero Downtime (en utilisant systemd, sur un VPS que j’ai moi-même configuré de zéro).

Pour la prod, je fais un reverse proxy avec Nginx :

server {
    listen 443 ssl http2;

    server_name www.mon-domaine.com;

    #### SSL
    ssl_certificate /etc/letsencrypt/live/mon-domaine.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mon-domaine.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/mon-domaine.com/fullchain.pem;
    include includes/ssl.conf;

    #### Reverse Proxy
    location / {
        proxy_pass http://127.0.0.1:3003/;

        proxy_http_version 1.1;

        proxy_set_header Host $host;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
    }
}

Et j’ai créé un service :

[Unit]
Description=nom-de-mon-service-prod

[Service]
Type=simple
User=steph
Restart=on-failure
RestartSec=10s
WorkingDirectory=/home/steph/www/mon-domaine.com/prod/front-nextjs
ExecStart=npm run start_prod

[Install]
WantedBy=multi-user.target

PS : "npm run start_prod" exécute ceci : next start -p 3003

Ceci marche. Par contre, à chaque nouveau déploiment (à chaque nouveau build) de mon App Next.js, je suis obligé de restart ce service (ce qui est mauvais sur une App en prod).

Avec des projets PHP, en gros, j’avais l’habitude de faire ceci sur mon serveur pour avoir Zéro Downtime :

  • J’envois ma nouvelle verison de mon App PHP sur une nouvelle release (un dossier avec la date de jour avec secondes, par exemple).

  • Ensuite je change le symlink (fichier "current") en le faisant pointer vers le dossier de cette nouvelle release.

Car avec PHP il n’y a pas de service à redémarrer. Mais avec Node JS, si.

Avez-vous des pistes SVP ?

Merci d’avance.

Hello!

Pour faire du zero-downtime deployment, tu dois être sûr qu’au moins un process sera toujours présent pour gérer les requêtes entrantes, y compris lors d’un nouveau déploiement. Il te faut donc une sorte de load-balancer, qui transférera progressivement les requêtes de l’ancienne version vers la nouvelle. Il te faudra donc un Nginx (ou outil similaire) devant ton app qui fera tourner plusieurs process Node.

Pour la gestion et l’orchestration de process Node, je t’invite à jeter un oeil à PM2.

Si jamais tu recherche quelques chose de plus simple, tu peux jeter un oeil aux offres de certains cloud providers comme Render, Digital Ocean, Clever Cloud, etc… car ils offrent ce genre de fonctionnalités par défaut.

+0 -0

Merci pour ta réponse.

Si jamais tu recherche quelques chose de plus simple, tu peux jeter un oeil aux offres de certains cloud providers comme Render, Digital Ocean, Clever Cloud, etc… car ils offrent ce genre de fonctionnalités par défaut.

widokast

Merci, mais je veux apprendre a y faire moi-même (pour culture perso, et ça peut servir).

Pour faire du zero-downtime deployment, tu dois être sûr qu’au moins un process sera toujours présent pour gérer les requêtes entrantes, y compris lors d’un nouveau déploiement. Il te faut donc une sorte de load-balancer, qui transférera progressivement les requêtes de l’ancienne version vers la nouvelle. Il te faudra donc un Nginx (ou outil similaire) devant ton app qui fera tourner plusieurs process Node.

widokast

Donc tu me conseil d’utiliser Nginx (et de faire un load-balancer avec) et aussi PM2 (donc PM2 au lieu de systemd) ?

Je me suis documenté sur PM2 cet aprem, je l’ai installé.

Pour testé, j’ai fais ceci :

(En fait, j’ai arrété mon service systemd. Puis j’ai installé PM2 puis j’ai créé un sercice avec. Tout en gardant mon reversy proxy Nginx.)

Si je fais ceci (dans le fichier "/home/steph/ecosystem.config.js"), ça fonctionne :

module.exports = {
  apps: [
    {
      name: "nom-de-mon-service-prod",
      cwd: "/home/steph/www/apps/mon-domaine.com/prod/front-nextjs",
      script: "npm run start_prod",
    },
  ],
}

Maic ceci n’est bien sûr pas compatible avec le Zero Downtime. (je ne règle pas la problématique, j’ai juste remplacé mon service systemd par un service PM2).

Pour commencer à tester le système de cluster de PM2, j’ai aussi fais ceci (en ajoutant 2 lignes à la fin), ça ne fonctionne pas :

module.exports = {
  apps: [
    {
      name: "nom-de-mon-service-prod",
      cwd: "/home/steph/www/apps/mon-domaine.com/prod/front-nextjs",
      script: "npm run start_prod",
      instances : 4,
      exec_mode : "cluster",
    },
  ],
}

Pour tester, j’ai définis 4 instances que que je veux exécuter à l’intérieur du cluster.

Si ça fonctionne pas, j’imagine que c’est parce que je dois rajouter de la conf dans mon Nginx ? Et c’est le load-balancer dont tu me parles ?

Encore merci.

Je n’ai jamais utilisé PM2. Je le connais de nom car il s’agit de l’outil le plus populaire pour orchestrer des process Node. Par contre, une recherche Google avec "nodejs zero downtime deployment pm2 nginx" donne des résultats. Tu devrais pouvoir trouver un tuto étape par étape.

Par exemple:

https://www.howtoforge.com/tutorial/how-to-deploy-nodejs-applications-with-pm2-and-nginx-on-ubuntu/

ou https://github.com/dwyl/learn-devops/blob/master/nodejs-pm2-zero-downtime.md

Encore merci pour vos réponse (et désolé pour mon retour tardif).

Avec un VPS, ça me semble impossible de faire le moindre « zero downtime », du moins sur la définition que j’en ai. Qu’est-ce que tu entends par « zero downtime » ?

sgble

Que le site web ne soit jamais HS.

Par exemple avec PHP, vu qu’il n’y a pas de service à redémarrer, c’est possible avec la technique du symlink et avec un système de releases (tel je l’ai résumé brièvement dans mon 1er message de ce post), et comme expliqué "à peut prêt" ici par exemple : https://adrien.poupa.net/zero-downtime-laravel-deployments-with-laravel-envoy/

Après, il est possible qu’en vrai que le site web soit HS quelques nanosecondes voir quelques millisecondes. Mais c’est invisible à l’œil nu, et il y a quasiement aucune chance que ça déclanche une problématique (à moins de bosser sur des sites web à très fort trafic tel que Amazon, etc.). Mais je ne bosse aps sur ce genre d’énormes sites web. Et à mon niveau, sur un site web que je déploie en prod sur un seul VPS, je n’ai pas trouvé mieux que cette technique du symlink pour des projets PHP / Laravel.

Mais avec Next.js/Node.js, vu qu’à chaque nouveau déploiement (nouveau build) il faut redémarrer le service (que ce soit avec systemd ou avec PM2), le site web est HS une bonne seconde. Et là, oui, c’est visible à l’œil nu… (et ça peut poser des problématiques sur des sites web avec paiements en lignes par exemple).

Donc s’il y a moyen de faire du zero downtime avec un seul VPS (ou de m’en rapprocher le + possible) en bossant avec des technos tel que Node.js et/ou Next.js (qui utilise Node de toute façon), ça m’interesse fortement. Je suis en train de me documenter sur le load balancing de Nginx et le système de cluster de PM2, je verrais si c’est bien.

Et si vous êtes presuidé que c’est impossible de faire du 100% zero downtime avec un seul serveur VPS (même en travaillant qu’avec PHP), ça m’interesse d’avoir une petite explication. Et ça m’interesse d’avoir une petite explication de comment avec plusieurs serveurs c’est possible.

Encore merci.

D’accord, je comprends mieux ce que tu entendais par zero downtime (deployment).

Donc si il y a moyen de faire du zero downtime avec un seul VPS (ou de m’en rapprocher le + possible) en bossant avec des technos tel que Node.js et/ou Next.js (qui utilise Node de toute façon), ça m’interesse fortement. Je suis en train de me document sur le load balancing de Nginx et le système de cluster de PM2, je verrais sir c’est bien.

Sur un même serveur, mettre un load balancer entre deux instances de l’app permet effectivement d’implémenter ça (indépendant de la techno), que ce soit un VPS ou non.

Par exemple avec PHP, vu qu’il n’y a pas de service à redémarrer, c’est possible avec la technique du symlink et avec un système de releases (tel je l’ai résumé brièvement dans mon 1er message de ce post), et comme expliqué "à peut prêt" ici par exemple : https://adrien.poupa.net/zero-downtime-laravel-deployments-with-laravel-envoy/

Certains serveurs applicatifs permettent aussi cela, par exemple uWSGI (utilisé surtout pour Python mais pas que) peut recharger l’app Web sans redémarrer et en gérant proprement les connexions encore actives. Si tu disposes d’un tel serveur applicatif compatible avec ta stack, la technique avec du load-balancing n’est pas forcément nécessaire (mais peut quand même avoir certains intérêts).

Et si vous êtes presuidé que c’est impossible de faire du 100% zero downtime avec un seul serveur VPS (même en travaillant qu’avec PHP), ça m’interesse d’avoir une petite explication. Et ça m’interesse d’avoir une petite explication de comment avec plusieurs serveurs c’est possible.

Encore merci.

stephweb

Je n’avais pas compris zero downtime deployment/reloading mais plutôt zero downtime tout court, ce qui me semblait quelque peu différent, désignant le fait que le site ne soit jamais HS (ou plutôt en réduisant drastiquement les probabilités qu’il le soit).

Dans cette définition que j’entendais, c’est effectivement impossible d’atteindre cela avec un seul VPS ou un seul serveur en général puisque si le VPS tombe, le site est évidemment bel et bien HS, ce qu’on compterait raisonnablement comme du downtime. Mais tes questions ne semblaient pas porter sur cet aspect.

+0 -0

Merci pour ta réponse.

Je me suis documenté sur PM2 et son mode cluster.

Et j’ai l’impression, que pour faire de zéro downtime strict avec un language tel que Node.js, qu’il faut que je copie le même code sources minimum 2 fois sur le même serveurs, et de lancer 2 services (1 service par code source de mon App Node.js, sur 2 ports différents). Je me trompe ?

C’est un sujet assez complexe, et c’est compliqué de se renseigner dessus. Dans Google je n’ai pas trouvé une solution qui fonctionne. Et quand j’en parle aux développeurs de mon entourage, j’ai l’impression que tout le monde s’en fou de faire du zéro downtime.

Une solution pour faire du zéro downtime sur un même serveur avec Next.js/Node.js (ou avec d’autres technos qui ont besoin d’un service pour fonctionner) :

Préparation (en résumé) :

  • Système de releases (avec un dossier "releases", et un fichier "current" qui est un symlink qui pointe vers la dernière release).

  • Dans le fichier "package.json" de mon App Node.js, dans "scripts" on ajoute ces 2 lignes :

"start_prod_1": "next start -p 3003",
"start_prod_2": "next start -p 3004",
  • Sur le serveur, duppliquer l’App (les codes sources) Next.js en 2 (dans la dernière release). Dans cette exemple, l’App sera envoyée dans les sous-dossiers "front-nextjs_1" et "front-nextjs_2".

  • Lancer un service (avec PM2 dans cet exemple) pour chaque App (chaque code source) :

module.exports = {
  apps: [
    {
      name: "nextjs_mon-site-prod_1",
      cwd: "/home/steph/www/mon-site.com/current/front-nextjs_1",
      script: "npm run start_prod_1",
    },
    {
      name: "nextjs_mon-site-prod_2",
      cwd: "/home/steph/www/mon-site.com/current/front-nextjs_2",
      script: "npm run start_prod_2",
    },
  ],
};

On lance les 2 Apps (un service par App, car chaque service lance l’App sur un port distinct).

  • Dans la conf de Nginx, faire ceci :
upstream mon-site-prod {
    server 127.0.0.1:3003;
    server 127.0.0.1:3004;
}

server {
    listen 443 ssl http2;

    server_name www.mon-site.com;

    ssl_certificate /etc/letsencrypt/live/mon-site.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mon-site.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/mon-site.com/fullchain.pem;
    include includes/ssl.conf;

    location / {
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_pass http://mon-site-prod;
    }
}

On met un load balancer locale, avec 2 lignes (une ligne pour chaque port).

Pour faire une mise en prod (en résumé) :

  • Sur le serveur, on créé une nouvelle release.

  • Dans cette nouvelle release, on envoie l’App dans "front-nextjs_1" et "front-nextjs_2" (on a donc 2 Apps, qui est la même App).

  • Une fois que c’est fait, on fait pointer (symlink) "current" vers cette dernière release.

  • Ensuite on reload le service PM2 de l’App 1 :

pm2 reload /home/steph/ecosystem.config.js --only nextjs_mon-site-prod_1

Durant le temps que l’App "front-nextjs_1" qui tourne sous ce service (via le port 3003) se relead (donc le temps que cette App est indisponible), Nginx basculera les req vers l’autre App ("front-nextjs_2", qui elle tourne via le port 3004).

Une fois que l’App "front-nextjs_1" est de nouveau dispo, on peut enfine reload le service de l’autre App ("front-nextjs_2").

Vos avis SVP

Qu’en pensez-vous ?

Je suis conscient qu’il faut mieux utiliser plusieurs serveurs, et faire pointer le load balancer vers ces différents serveurs distants (en cas de panne d’un serveur). Mais bon, c’est surtout le principe qui m’interesse.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte