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

Mar*_*mou 4 systemd services node.js

在我提到我的问题之前,我已经检查了大部分与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}`)
    })
Run Code Online (Sandbox Code Playgroud)

最初,我创建了一个运行 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
Run Code Online (Sandbox Code Playgroud)

但是,我通过创建启动或停止服务的 .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
Run Code Online (Sandbox Code Playgroud)

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

tel*_*coM 5

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

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

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

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

理想情况下,您应该保留实际服务进程的开始,ExecStart=并在.service带有Environment=选项的实际文件中指定任何环境变量,或者在由EnvironmentFile=选项引用的单独文件中。任何额外的启动命令都可以成为ExecStartPre=和/或ExecStartPost=选项;这样你仍然可以保留默认值Type=simple和它提供的自动过程监控。如果需要,您仍然可以ExecStop=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可以正确识别您的服务的主进程,用于监视目的和在必要时杀死。

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