nuxt&pm2:零停機(jī)部署
中文版:
nuxt是一個(gè)非常棒的vue.js框架,它使得建立ssr站點(diǎn)變得非常簡(jiǎn)單。nuxt在引擎蓋下使用node.js服務(wù)器來(lái)呈現(xiàn)服務(wù)器端的頁(yè)面。
ssr有很多優(yōu)點(diǎn),在許多其他文章中都有深入的介紹,但是對(duì)于本文,我們將重點(diǎn)關(guān)注nuxt服務(wù)器的部署。
對(duì)于node.js應(yīng)用程序來(lái)說(shuō),pm2是一個(gè)很好的進(jìn)程管理器,它有許多很好的特性,包括通過(guò)“cluster mode”實(shí)現(xiàn)負(fù)載平衡。cluster mode創(chuàng)建node.js進(jìn)程的多個(gè)實(shí)例,以便在不修改任何代碼的情況下跨多個(gè)cpu擴(kuò)展應(yīng)用程序。
除了利用多個(gè)cpu的性能優(yōu)勢(shì)外,集群模式還允許通過(guò)一次重新加載一個(gè)實(shí)例來(lái)實(shí)現(xiàn)零停機(jī)部署。因此,如果您有4個(gè)實(shí)例在運(yùn)行,您可以一次重新加載一個(gè)實(shí)例,而當(dāng)一個(gè)實(shí)例正在重新加載時(shí),其他3個(gè)實(shí)例將使服務(wù)器保持活動(dòng)狀態(tài)。因此,服務(wù)器可以在不離線的情況下更新一秒。
對(duì)于大多數(shù)節(jié)點(diǎn)應(yīng)用程序來(lái)說(shuō),設(shè)置和使用pm2集群模式非常容易,但是我們?cè)趪L試將集群模式與nuxt服務(wù)器一起使用時(shí)遇到了一些問(wèn)題。
首先,我們可以嘗試使用以下命令啟動(dòng)nuxt服務(wù)器的單個(gè)實(shí)例:
pm2 start npm --name MyAppName -- start

但是,當(dāng)嘗試啟動(dòng)多個(gè)實(shí)例時(shí),我們很快就會(huì)遇到問(wèn)題。
例如,如果我們運(yùn)行該命令兩次,就會(huì)遇到錯(cuò)誤。

現(xiàn)在,雖然默認(rèn)情況下是在“fork”模式下運(yùn)行,但即使指定集群模式,也會(huì)遇到錯(cuò)誤。
如果我們只使用一個(gè)實(shí)例并嘗試重新加載服務(wù)器呢?好吧,由于nuxt在幕后的工作方式,如果我們?cè)噲D在服務(wù)器運(yùn)行時(shí)重建它,那么在刪除生成文件并為新的生成重新設(shè)置灰燼之后,我們將遇到404個(gè)錯(cuò)誤。如果靜態(tài)文件夾中的文件發(fā)生更改,我們也會(huì)遇到問(wèn)題。
因此,如果我們不能重建和重新加載服務(wù)器,并且不能運(yùn)行多個(gè)實(shí)例,那么我們?nèi)绾螌?shí)現(xiàn)零停機(jī)部署?
正確配置PM2集群模式
我們之前遇到問(wèn)題是因?yàn)槲覀冊(cè)噲D以“錯(cuò)誤的方式”生成服務(wù)器的多個(gè)實(shí)例。在我們的實(shí)例中,我們讓pm2執(zhí)行'npm run start',實(shí)際上是啟動(dòng)npm腳本執(zhí)行的多個(gè)實(shí)例,而不是直接正確地啟動(dòng)服務(wù)器。在啟動(dòng)實(shí)例后,我們可以在控制臺(tái)中看到這一點(diǎn):
[PM2] Starting /usr/bin/npm in fork_mode (1 instance)
如您所見(jiàn),我們啟動(dòng)npm,然后通過(guò)命令行參數(shù)告訴npm啟動(dòng)服務(wù)器。相反,我們應(yīng)該使用pm2直接調(diào)用服務(wù)器的啟動(dòng)腳本。使用以下命令啟動(dòng)nuxt服務(wù)器:
Nuxt start
但是,如果我們?cè)噲D告訴PM2啟動(dòng)NUXT,它就不會(huì)起作用。相反,我們需要直接指向腳本nuxt調(diào)用,它位于
./node_modules/nuxt/bin/nuxt-start
現(xiàn)在用node_modules文件夾中的腳本啟動(dòng)pm2會(huì)很乏味,必須有更好的方法。謝天謝地,有!pm2允許我們創(chuàng)建一個(gè)生態(tài)系統(tǒng)文件,它基本上是pm2命令的配置文件。我們可以指定諸如要運(yùn)行的腳本、工作目錄、要生成多少實(shí)例以及別名等屬性。您可以在這里看到所有可用的屬性。
因此,現(xiàn)在我們知道需要運(yùn)行什么腳本,并且知道需要使用什么設(shè)置來(lái)執(zhí)行該腳本,我們可以通過(guò)運(yùn)行以下命令來(lái)創(chuàng)建生態(tài)系統(tǒng)文件:
pm2 init
然后,我們將按如下方式配置此文件:
module.exports = {
apps : [{
name : 'MyAppName', // App name that shows in `pm2 ls`
exec_mode : 'cluster', // enables clustering
instances : 'max', // or an integer
cwd : './current', // only if using a subdirectory
script : './node_modules/nuxt/bin/nuxt-start', // The magic key
}]
};
您可能注意到我正在指定一個(gè)當(dāng)前工作目錄(CWD)。我這么做是因?yàn)檫@允許我們?cè)谀缓蠼粨Q服務(wù)器代碼,而不會(huì)遇到nuxt造成的限制。
我們通過(guò)創(chuàng)建如下文件結(jié)構(gòu)來(lái)完成此操作:
/MyAppName
|--/releases
|----/v1.0.0
|----/v1.1.0
|----/v1.2.0
|----/...
|--/current -> /releases/v1.2.0 (symlink)
|--ecosystem.config.js
要啟動(dòng)服務(wù)器,只需在/myappname目錄中運(yùn)行以下命令:
pm2 start
現(xiàn)在要部署,我們只需將最新版本的repo克隆到“/releases”下的新文件夾,構(gòu)建新版本(或者,您可以使用ci/cd服務(wù)提前構(gòu)建該版本,并直接將這些構(gòu)建文件上載到服務(wù)器)。然后我們將符號(hào)鏈接從/releases/v1.2.0更新到/releases/v1.3.0并運(yùn)行:
pm2 reload MyAppName

成功!現(xiàn)在,您可以生成多個(gè)nuxt服務(wù)器實(shí)例以獲得更好的性能(在多線程系統(tǒng)上),并利用零停機(jī)部署在更新期間保持網(wǎng)站
希望這能幫助你充分利用你的項(xiàng)目!
英文版:
Nuxt & PM2: Zero Downtime Deployment
Nuxt is an awesome framework for Vue.js that makes setting up an SSR site super simple. Nuxt uses a Node.js server under the hood in order to render pages server side.
There are many advantages to SSR, which are covered in depth in many other posts, but for this post we are going to be focusing on the deployment of a Nuxt server.
PM2 is a great process manager for Node.js applications, with many great features including load balancing through “cluster mode.” Cluster mode creates multiple instances of a Node.js process in order to allow applications to be scaled across multiple CPUs without any code modifications.
Besides the performance advantage of utilizing multiple CPUs, cluster mode also allows for a zero downtime deployment by reloading the server process one instance at a time. Thus if you have 4 instances running, you can reload the server one instance at a time, and while one instance is reloading, the other 3 instances will keep the server alive. Thus a server can be updated without being taken offline for even a second.
Setting up and using PM2 cluster mode is extremely easy for most node applications, however we run into some issues attempting to use cluster mode with a Nuxt server.
First we can try starting a single instance of our nuxt server with the following command:
$ pm2 start npm --name MyAppName -- start

But we quickly run into issues when trying to start multiple instances.
For example, if we run that command twice, we’ll run into errors.

Now while that is by default running in “fork” mode, even if we specify cluster mode, we run into errors.
What if we only use 1 instance and attempt to reload the server? Well due to how Nuxt works under the hood, if we attempt to rebuild the server while it’s running, we’ll hit 404 errors after the build files are removed and rehashed for the new build. We also run into issues if files in our static folder are changed.
So if we can’t just rebuild and reload a server, and we can’t run multiple instances, then how do we achieve a zero downtime deployment?
Properly Configure PM2 Cluster Mode
We were running into issues previously because we were attempting to spawn multiple instances of our server in the “wrong way”. In our instance we are having PM2 execute npm run start
which essentially is starting multiple instances of npm script executions, and not properly launching our server directly. We can see this in our console after starting an instance:
[PM2] Starting /usr/bin/npm in fork_mode (1 instance)
As you can see, we’re starting npm and then telling npm to start our server through it’s command line arguments. Instead of this, we should be using PM2 to call the server’s start script directly. Starting a Nuxt server is done with the following command:
Nuxt start
However, if we try to tell PM2 to start Nuxt, it won’t work. Instead, we need to point directly to the script Nuxt calls, which is located in
./node_modules/nuxt/bin/nuxt-start
Now starting PM2 with a script from the node_modules folder is going to be tedious, there has to be a better way. Thankfully, there is! PM2 allows us to create an Ecosystem File, which is basically a configuration file for our PM2 commands. We can specify properties such as the script to run, the working directory, how many instances to spawn, and an alias name. You can see all available properties here.
So now that we know what script we need to run, and we know what settings we need to execute that script with, we can create the ecosystem file by running the following command:
pm2 init
We’ll then configure this file as such:
module.exports = {
apps : [{
name : 'MyAppName', // App name that shows in `pm2 ls`
exec_mode : 'cluster', // enables clustering
instances : 'max', // or an integer
cwd : './current', // only if using a subdirectory
script : './node_modules/nuxt/bin/nuxt-start', // The magic key
}]
};
You may notice I’m specifying a current working directory (cwd). I’m doing this because this is what allows us to swap our server code behind the scenes without running into the limitations caused by Nuxt.
We do this by creating a file structure like this:
/MyAppName
|--/releases
|----/v1.0.0
|----/v1.1.0
|----/v1.2.0
|----/...
|--/current -> /releases/v1.2.0 (symlink)
|--ecosystem.config.js
To start our server, we just need to run the following command from within the /MyAppName directory:
pm2 start
Now to deploy we just clone the latest version of our repo to a new folder under ‘/releases’, build our new version (or alternatively, you can use a CI/CD service to build that version ahead of time, and directly upload those build files to the server). Then we just update the symlink from /releases/v1.2.0 to /releases/v1.3.0 and run:
pm2 reload MyAppName

Success! You’re now able to spawn multiple instances of your Nuxt server for better performance (on multi threaded systems), AND take advantage of zero downtime deployments to keep your website online during updates.
Hopefully this helps you make the most out of your project!
參考: