运行 使用 Yarn PNP 构建部署时 NestJS 处于 PM2 集群模式
Running NestJS in PM2 cluster mode when building a deployment with Yarn PNP
从 npm 迁移到 yarn pnp
几个月前,我们开始为我们的 monorepo 使用 yarn2 (pnpify),因为 node_modules 确实增长到 200K 个包。感谢 yarn2,我们将所有包的构建和部署时间从 40 分钟减少到 4-5 分钟,这真的很棒。
前端包很容易进行 tree-shaking 和捆绑,以创建一个小工件并上传到存储容器。
后端包(Nestjs Rest 和 GraphQl API's)为了 运行 在集群模式下使用 pm2 构建有点棘手。
PM2
使用 PM2,您可以 运行 您的应用处于 fork
或 cluster
模式。
当运行在同一端口上连接您的应用程序时,您需要使用cluster
模式。
Fork
模式在启动第一个 fork 后一直说端口已被使用(完全合法)
因为我们使用的是 yarn2,所以我们只能 运行 我们的应用程序使用 yarn 作为解释器,方法是 运行 将其设置为:
yarn node ./build/main.js
为了获得正确的模块解析,因为节点不理解它。
问题来了:
yarn(和 npm)在集群模式下表现不佳。
这是因为您需要使用节点本身作为解释器而不是纱线(或 npm)
所以我们最终得到以下 ecosystem.config.js
{
"apps": [
{
"args": "node ./build/main.js",
"exec_mode": "cluster",
"instances": "max",
"interpreter": "bash",
"name": "api",
"script": "yarn",
"time": true
}
]
}
我们将部署转移到具有 1 个以上 CPU 核心的 VM,并使用新的生态系统重新加载 pm2 服务。一切都是绿色的,但我们注意到只有 1 个进程实际上在监听端口 3000,所有其他进程确实抛出了 EADDRINUSE
错误。
yarn 发出错误,而不是抛出错误,因此 PM2 认为应用程序仍然存在。
或者至少,这是我们的结论...
捆绑 NestJS 是不行的:bundled-nest
我唯一的解决方案是通过执行以下操作对 nestjs 本身进行集群化:
import { Injectable } from '@nestjs/common';
import { fork, isMaster, on } from 'cluster';
import * as os from 'os';
const numCPUs = os.cpus().length;
// const randomNumber = (min: number, max: number) =>
// Math.floor(Math.random() * max) + min;
@Injectable()
export class ClusterService {
// eslint-disable-next-line @typescript-eslint/ban-types
static clusterize(callback: Function): void {
if (isMaster) {
// eslint-disable-next-line no-plusplus
for (let i = 0; i < numCPUs; i++) {
fork();
}
on('exit', (worker, code) => {
fork();
// eslint-disable-next-line no-console
console.log(
`[Cluster] worker[${worker.process.pid}] died with status: [${code}], creating new worker`,
);
});
} else {
callback();
}
}
}
和 运行 PM2 在单个实例上,但这感觉有点古怪,因为 PM2 可以为你做这件事......以更好的方式。
有没有办法用 yarn2 “弹出”node_modules,这样我们就可以 运行 应用程序作为一个真正的节点进程?
有没有办法在使用 yarn 作为解释器的同时在同一端口上以集群模式 运行 PM2?
如何在 yarn2 中抛出错误而不是发出错误以便 PM2 将创建一个新进程?
... 或者是否有另一种解决方案可以在 gitlab 中仅使用 npm 而不必等待 40 分钟来构建包和 运行 带有节点解释器 n pm2 的 nestjs 应用程序?
yarn exec pm2 start ecosystem.config.js
并且不保存pm2进程。
pm2只能在npm模式下复活,不会在yarn模式下执行你的脚本
缺点是您无法使用 pm2 启动在从 pm2 重新启动后仍然存在。所以你需要自己实现一个systemd服务,它会在启动时执行yarn pm2:start
2 种可能的特征解决方案:
- yarn2 将能够在构建步骤后弹出 .yarn 缓存以重新创建 node_modules 以便在 npm 中启动您的应用程序。
- pm2 将添加一项功能以结合集群模式在 yarn 而不是 npm 中启动您的应用程序
更新
您不需要 yarn 来 运行 具有 yarn 模块分辨率的应用程序。
您唯一需要做的就是在使用节点作为解释器 运行 连接应用程序时要求 .pnp.js
。
将 "interpreter_args": "--require /path/to/.pnp.js",
添加到您的 pm2 配置
ecosystem.config.js:
{
"apps": [
{
"name": "my-app",
"script": "./main.js",
"interpreter_args": "--require /path/to/.pnp.js",
"exec_mode": "cluster"
}
]
}
现在您可以执行 pm2 start ecosystem.config.js
而不会遇到有关未找到节点模块的任何错误。
从 npm 迁移到 yarn pnp
几个月前,我们开始为我们的 monorepo 使用 yarn2 (pnpify),因为 node_modules 确实增长到 200K 个包。感谢 yarn2,我们将所有包的构建和部署时间从 40 分钟减少到 4-5 分钟,这真的很棒。
前端包很容易进行 tree-shaking 和捆绑,以创建一个小工件并上传到存储容器。
后端包(Nestjs Rest 和 GraphQl API's)为了 运行 在集群模式下使用 pm2 构建有点棘手。
PM2
使用 PM2,您可以 运行 您的应用处于 fork
或 cluster
模式。
当运行在同一端口上连接您的应用程序时,您需要使用cluster
模式。
Fork
模式在启动第一个 fork 后一直说端口已被使用(完全合法)
因为我们使用的是 yarn2,所以我们只能 运行 我们的应用程序使用 yarn 作为解释器,方法是 运行 将其设置为:
yarn node ./build/main.js
为了获得正确的模块解析,因为节点不理解它。
问题来了:
yarn(和 npm)在集群模式下表现不佳。
这是因为您需要使用节点本身作为解释器而不是纱线(或 npm)
所以我们最终得到以下 ecosystem.config.js
{
"apps": [
{
"args": "node ./build/main.js",
"exec_mode": "cluster",
"instances": "max",
"interpreter": "bash",
"name": "api",
"script": "yarn",
"time": true
}
]
}
我们将部署转移到具有 1 个以上 CPU 核心的 VM,并使用新的生态系统重新加载 pm2 服务。一切都是绿色的,但我们注意到只有 1 个进程实际上在监听端口 3000,所有其他进程确实抛出了 EADDRINUSE
错误。
yarn 发出错误,而不是抛出错误,因此 PM2 认为应用程序仍然存在。
或者至少,这是我们的结论...
捆绑 NestJS 是不行的:bundled-nest
我唯一的解决方案是通过执行以下操作对 nestjs 本身进行集群化:
import { Injectable } from '@nestjs/common';
import { fork, isMaster, on } from 'cluster';
import * as os from 'os';
const numCPUs = os.cpus().length;
// const randomNumber = (min: number, max: number) =>
// Math.floor(Math.random() * max) + min;
@Injectable()
export class ClusterService {
// eslint-disable-next-line @typescript-eslint/ban-types
static clusterize(callback: Function): void {
if (isMaster) {
// eslint-disable-next-line no-plusplus
for (let i = 0; i < numCPUs; i++) {
fork();
}
on('exit', (worker, code) => {
fork();
// eslint-disable-next-line no-console
console.log(
`[Cluster] worker[${worker.process.pid}] died with status: [${code}], creating new worker`,
);
});
} else {
callback();
}
}
}
和 运行 PM2 在单个实例上,但这感觉有点古怪,因为 PM2 可以为你做这件事......以更好的方式。
有没有办法用 yarn2 “弹出”node_modules,这样我们就可以 运行 应用程序作为一个真正的节点进程?
有没有办法在使用 yarn 作为解释器的同时在同一端口上以集群模式 运行 PM2?
如何在 yarn2 中抛出错误而不是发出错误以便 PM2 将创建一个新进程?
... 或者是否有另一种解决方案可以在 gitlab 中仅使用 npm 而不必等待 40 分钟来构建包和 运行 带有节点解释器 n pm2 的 nestjs 应用程序?
yarn exec pm2 start ecosystem.config.js
并且不保存pm2进程。
pm2只能在npm模式下复活,不会在yarn模式下执行你的脚本
缺点是您无法使用 pm2 启动在从 pm2 重新启动后仍然存在。所以你需要自己实现一个systemd服务,它会在启动时执行yarn pm2:start
2 种可能的特征解决方案:
- yarn2 将能够在构建步骤后弹出 .yarn 缓存以重新创建 node_modules 以便在 npm 中启动您的应用程序。
- pm2 将添加一项功能以结合集群模式在 yarn 而不是 npm 中启动您的应用程序
更新
您不需要 yarn 来 运行 具有 yarn 模块分辨率的应用程序。
您唯一需要做的就是在使用节点作为解释器 运行 连接应用程序时要求 .pnp.js
。
将 "interpreter_args": "--require /path/to/.pnp.js",
添加到您的 pm2 配置
ecosystem.config.js:
{
"apps": [
{
"name": "my-app",
"script": "./main.js",
"interpreter_args": "--require /path/to/.pnp.js",
"exec_mode": "cluster"
}
]
}
现在您可以执行 pm2 start ecosystem.config.js
而不会遇到有关未找到节点模块的任何错误。