ubuntu systemd simple(oneshot) 和 fork 之间的区别?

ubuntu systemd simple(oneshot) 和 fork 之间的区别?
#!/bin/bash
sleep 2
nohup java -jar /home/ubuntu/radius/radius-test.jar >> /home/ubuntu/radius/radius.log &
nohup java -jar /home/ubuntu/radius/radius-test-2.jar >> /home/ubuntu/radius/radius-2.log &

我有一个类似于上面的脚本文件,它应该在启动时自动运行。

在我发现的许多方法中,我决定使用 systemd 并成功地获得了所需的行为。

但是,我想知道是否有正确的方法来指定 Type 选项或者是否没有问题。

[Unit]
Description=My Shell Script

[Service]
Type=simple or oneshot
RemainAfterExit=yes
ExecStart=/home/ubuntu/radius/radius-start.sh

[Install]
WantedBy=multi-user.target


---

[Unit]
Description=My Shell Script

[Service]
Type=forking
ExecStart=/home/ubuntu/radius/radius-start.sh

[Install]
WantedBy=multi-user.target

在这两种方法中,服务都会在启动时运行,并且我的 jar 文件也会运行。并且该服务仍然处于活动状态并且 jar 进程也得到维护。然后,当服务终止时,所有jar进程也会终止。

我查了一下,发现fork适合后台方法,子进程运行,然后父进程死掉。 (这部分我也不明白。)它还说必须指定pid文件或者不使用fork方法。

simple或者oneshot据说是需要前台操作的方式来使用。但我的jar进程需要在后台运行。

这两种方法有什么区别?哪个选项是正确的使用方法?

答案1

让我们看看您的单元文件的两个版本,然后我将向您展示我的建议:

simpleoneshot服务

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/home/ubuntu/radius/radius-start.sh

Type=oneshotRemainAfterExit=yes与 一起使用比更合适Type=simple

simple你平时跑步一样ExecStart=。然后,当该过程结束时,所有内容都将被清理,并且服务将“停止”。但是,radius-start.sh启动一些进程然后立即停止。这意味着 systemd 将查找任何孤立进程并杀死它们作为清理的一部分。

有了oneshot,那真的意味着跑ExecStart=然后死。 RemainAfterExit=可以使用有用的方法帮助您在该单元停止时触发ExecStop=命令或利用关系来停止其他单元。ConsistsOf=但它并没有为您提供您生成的子进程的最新状态。

这种技术的一个大问题是,它要么立即终止您的(孤立的)子进程,要么在子进程退出时无法理解该单元已停止/失败。


forking服务

[Service]
Type=forking
ExecStart=/home/ubuntu/radius/radius-start.sh

这是您的服务的正确版本。forking将运行ExecStart=,预计将很快退出。然后,systemd 预计会产生一些子进程,并将跟踪这些子进程的状态。当主进程退出时,systemd 会认为服务已停止。

如果您坚持使用此方法,那么建议进行设置PIDFile=,以便 systemd 了解两个进程中哪一个是主要进程。

[Service]
...
PIDFile=/run/myservice.pid
...
#!/bin/bash
sleep 2
nohup java -jar /home/ubuntu/radius/radius-test.jar >> /home/ubuntu/radius/radius.log &
echo $! > /run/myservice.pid
nohup java -jar /home/ubuntu/radius/radius-test-2.jar >> /home/ubuntu/radius/radius-2.log &

然而,systemd 的文档明确指出:

请注意,现代项目中应避免使用 PID 文件。尽可能使用 Type=notify 或 Type=simple,这样不需要使用 PID 文件来确定服务的主进程,并避免不必要的分叉。

无需调用bash此处来运行您的进程,因为您想要执行的所有操作都已由 systemd 本机提供。


逐字建议Type=simple

如果我要逐字实现您的服务,我将使用两项服务来实现它:

# radius-test.service
[Service]
Type=simple
ExecStartPre=/bin/sleep 2
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test.jar
StandardOutput=append:/home/ubuntu/radius/radius.log
# radius-test2.service
[Unit]
After=radius-test.service

[Service]
Type=simple
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test-2.jar
StandardOutput=append:/home/ubuntu/radius/radius-2.log

现在,如果其中一项服务出现故障,我们可以检测到哪一项出现故障并恢复它。如果这些服务需要某种关系,则可以对其进行定义。例如,可以使用 来实现订购After=。如果您希望其中一个在另一个失败时干净地停止,您可以添加BindsTo=.


改进解决方案

  • ExecStartPre=/bin/sleep 2: 这通常是黑客攻击。延迟会在好日子带来不必要的延迟,或者在坏日子带来不够的延迟。最好理解为什么你需要睡觉。例如,如果您需要在运行服务之前使网络可用,请改用After=network.target
  • Type=simple: 这实际上是默认的。所以这条线可以去掉。
  • 如果您总是想在启动 radius-test 时运行 radius-test2,则使用关系(或在's部分Wants=指定)WantedBy=radius-testradius-test2[Install]
  • 如果你想在停止radius-test2时停止,那么使用关系。radius-testPartOf=
  • StandardOutput=append:...:虽然将内容记录到您自己的文件中没有问题,但您可能会发现使用内置日志很方便。只需删除此行,您就可以通过 获取日志journalctl -u myservice.servicejournalctl提供许多额外的功能,如日志轮换、大小限制和过滤。我经常做这样的事情:

journalctl -u myservice --since "5 hours ago" --until "4 hours ago"

改进的版本是这样的:

# /etc/systemd/system/radius-test.service
[Unit]
Description=Radius Test 1
After=network.target
Before=radius-test-2
Wants=radius-test-2

[Service]
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test.jar

[Install]
WantedBy=multi-user-target

# /etc/systemd/system/radius-test2.service
[Unit]
Description=Radius Test 2
PartOf=radius-test

[Service]
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test-2.jar

最终的简单解决方案

然而,现在我们已经删除StandardOutput=并使用了日志,也可以将它们合并为一个单元。这是一个更简单的解决方案,使用ExecStartPost=

# /etc/systemd/system/radius-test.service
[Unit]
Description=Radius Test
After=network.target

[Service]
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test.jar
ExecStartPost=/usr/bin/java -jar /home/ubuntu/radius/radius-test-2.jar

[Install]
WantedBy=multi-user-target

相关内容