当指向 shell 脚本时,node.js 应用程序的 systemd 服务文件不起作用

当指向 shell 脚本时,node.js 应用程序的 systemd 服务文件不起作用

在我提到我的问题之前,我已经检查了大部分与systemd相关的问题,但我找不到令人信服的答案。我编写了一个运行服务器的 Nodejs 应用程序。

const express = require('express');
const app     = express(),
      port    = process.env.PORT || 5000;

    app.get('/' , ( req , res ) => {
        res.send('Hello World')
    })

    app.listen( port , () => {
        console.log(`The server listens on port ${port}`)
    })

最初,我创建了一个运行 server.js 的服务,它的工作非常顺利。

[Unit]
Description=Hello World
After=network.target

[Service]
ExecStart=/usr/bin/node /home/msimou/Desktop/helloWorld/server.js
User=msimou
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

但是,我通过创建启动或停止服务的 .sh 文件尝试了相同的任务,并尝试嵌入到单元文件中,但由于某种未知原因,服务器无法工作。更新后的单元文件如下所示:

[Unit]
Description=Hello World
After=network.target

[Service]
ExecStart=/bin/bash /home/msimou/Desktop/helloWorld/init/startHelloWorld.sh
ExecStop=/bin/bash /home/msimou/Desktop/helloWorld/init/stopHelloWorld.sh
User=msimou
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

我检查了日志文件 和/var/log/syslogjournalctl发现错误,但我唯一能看到的是 systemd 连续启动和停止我的服务 5 秒。当我检查 using 时systemctl status helloWorld.service,它说该服务处于成功状态,但是我找不到与我的 Nodejs 应用程序相关的任何进程。

答案1

由于您没有指定服务类型,systemd因此将采用默认的Type=simple.

因此,systemd假设您的ExecStart命令将启动实际的服务进程。它将在自己的控制组 (cgroup) 中启动该进程,并对其进行监视。该进程的任何子进程ExecStart都将是同一控制组的成员。

ExecStart进程终止时,systemd 会认为这意味着您的服务终止了。由于您现在使用脚本来启动实际服务,因此这种假设是不正确的。此时,它将杀死控制组中剩余的所有进程以清理服务(有效地杀死实际的服务进程)。然后它尝试重新启动服务,并重复循环......

通过脚本间接启动服务,您有效地将服务从simpletype 更改为forkingtype,但没有将其告知systemd。但该forking类型还有一些与之相关的额外遗留包袱。这也使得systemd监控​​您的服务流程变得更加困难;除非绝对必要,否则您应该避免使用它。

理想情况下,您应该保留实际服务进程的开始位置,并在带有选项的ExecStart=实际文件中或在选项引用的单独文件中指定任何环境变量。任何额外的启动命令都可以成为和/或选项;这样您仍然可以保留默认值及其提供的自动进程监控。如果需要,您仍然可以使用服务。.serviceEnvironment=EnvironmentFile=ExecStartPre=ExecStartPost=Type=simpleExecStop=Type=simple


如果您使用Type=forking,systemd 仍将通过其控制组跟踪服务。如果服务创建了其他进程,它不会知道其中哪个是服务的主进程,因此您至少需要提供选项PIDFile=来帮助 systemd 在停止服务时首先杀死主服务进程,或者一个适合ExecStop=做一些比盲目终止进程更服务友好的事情。

如果服务的控制组中没有进程,systemd仍然会检测到服务已失败。但使用Type=forking和不使用PIDFile=时,服务的主进程可能会终止,并且只要至少有一个子进程存在,故障就可能不会被检测到。

ExecStop=进程完成时,如果服务的进程组中还有任何进程,则systemd假定这意味着有序关闭由于某种原因失败,并且将立即用于SIGKILL清理控制组中的剩余进程,除非使用各种选项另有指定手册页中列出systemd.kill(5)

因此,如果您使用Type=simple,您将不需要 aPIDFile=并且不必担心如果服务或整个系统崩溃导致您留下陈旧的 PID 文件该怎么办。

如果您使用Type=forking并且您的服务使用多个进程,则应该使用一个PIDFile=能够systemd正确识别服务的主进程的进程,既可以用于监视目的,也可以在必要时杀死。

如果您的服务需要一个更复杂的关闭过程,而不仅仅是“发送一个SIGTERM到其主/唯一进程”,请使用ExecStop=无论Type=选项如何,但请注意它也需要处理任何必要的等待/超时;预期的结果是服务关闭完全的当该ExecStop=过程结束时。如果此后服务的控制组中还有任何剩余进程,systemd则将假定它们可以安全地被终止,并将立即用于SIGKILL清理它们。

相关内容