npm start在Docker中启动Node应用程序的问题

5
我阅读了一些关于Docker和Node.js最佳实践的文章,例如 https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md在 Docker 中容器化 Node.js Web 应用程序的 10 种最佳实践Node 和 NPM 的 Dockerfile 最佳实践。所有这些文章都是至少在2021年编写或更新的,我没有列出2021年之前编写的文章,但有相当多的文章。
它们都反对 CMD ["npm", "run", "start"]。主要原因是 npm 会吞噬退出信号,如 SIGTERM 和 SIGINT,因此我的 node 应用程序中的优雅关闭代码不会运行。
我猜这可能是旧版 npm 的情况(尽管我没有测试过),但我已经测试过 node14+npm6 和 node16+npm8,并且我可以验证 npm6/8 不会吞噬这些事件,我的优雅关闭代码会运行。不确定是否是因为 npm 修复了它。
所以唯一的问题是还需要运行一个进程,npm,即 NPM 作为 PID 1 运行。一些文章说这样做的问题是“PID 1 不会响应 SIGINT”,但是我已经验证它不是这种情况。
许多文章(例如这个 nodejs 文档)建议只使用 CMD [ "node", "server.js" ],但是在https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals 中也提到“PID 1 运行的 Node.js 进程将不会响应 SIGINT(CTRL-C)和类似的信号。”,即 nodejs 自己的文档与自身相矛盾(但我确实看到 nodejs 作为 PID 1 响应了 SIGINT)。
所以我对于 CMD ["npm", "run", "start"]CMD [ "node", "server.js" ] 的问题感到困惑。
对于我的应用程序,还有一个要考虑的问题,我的npm脚本有预钩子以使应用程序正确运行,我有prestart npm脚本来使npm start工作。所以目前我只使用 CMD ["npm", "run", "start"] ,但是我对如何在docker中启动我的节点应用程序的“最佳实践”感到困惑。
--- 更新 ---
我发现了这个已关闭的npm问题lifecycle: propagate SIGTERM to child 因此他们确实解决了它,但该问题中的最新评论是在2017年,其中说:“是的,这不起作用,至少与bash一起; npm在shell中运行其生命周期进程,而bash不会将SIGTERM转发给其子进程。”
我意识到我只在我的Mac和我们的CentOS服务器上以及基于alpine的docker上进行了测试。这也可能是因为CMD中我使用了exec form,而不是shell form,所以我得到了退出信号。 通过Node.js和Kubernetes优雅地关闭表示他们的alpine映像未使用npm start接收到SIGTERM,而我在alpine3.15上测试可以接收到。

如果你已经在自己的环境中证明了npm run start的运行方式符合你的预期,那么我认为使用它没有任何问题。更广泛的Unix语句是,进程1必须显式订阅SIGINT和SIGTERM才能接收它们,否则它们将被忽略。 - David Maze
很多关于 CMD ["npm", "run", "start"] 的文章都是错误或过时的,这让我感到困惑。我担心自己在这方面错过了什么,这也是我提出问题的部分原因。 - Qiulang
2个回答

0
为了避免 SIGINT 和 SIGTERM 的影响,我这样做:
CMD [ "bash", "-c", "npm run db:migrate && node ./dist/server/index.js" ]

我的npm start命令中的内容是npm run db:migrate && node ./dist/server/index.js


正如我在问题中所说,燕子在MacOS、CentOS和基于alpine的docker中都不会发生。那么你在哪里遇到了这个问题? - Qiulang
在一个Ubuntu 22.04 VPS上,我来到这里寻找Docker CMD无法将SIGTERM和SIGINT转发到我的节点应用程序的问题,只是想完成此线程。 - user5365075

0

使用NPM:

CMD [ "npm", "run", "start" ]

检查Docker容器上的进程图

$ ps ajxf
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0     1     1     1 pts/0        1 Ssl+     0   0:01 npm run start
    1    19     1     1 pts/0        1 S+       0   0:00 sh -c -- node server.js
   19    20     1     1 pts/0        1 Sl+      0   0:00  \_ node server.js

npm 进程会生成一个 shell 进程,然后再生成 node 进程。这意味着 npm 不会直接生成 node 进程。

这会导致 npm 进程无法将信号传递给 node 进程。

这与本地 npm 的行为不同,在本地它会直接生成 node 进程。

使用 Node

CMD [ "node", "server.js" ]

处理图

$ ps ajxf
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0     1     1     1 pts/0        1 Ssl+     0   0:00 node server.js

Node.js并不是为了在Docker中作为PID 1运行而设计的,这会导致意外行为。例如,作为PID 1运行的Node.js进程将无法响应SIGINT(CTRL-C)和类似的信号。

解决方案:

CMD [ "bash", "-c", "node server.js" ]

或者使用 tini/s6 init 系统


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接