如何确保在systemd启动服务之前有延迟?

122

我有一个依赖于Cassandra正常启动和集群已准备就绪的服务。

为确保满足依赖顺序,我有以下单元文件:

[Unit]
Requires=cassandra.service
After=cassandra.service

[Service]
Environment=JAVA_HOME=/usr/java/jre
ExecStart=@bringup.instance.path@/webapps/bringup-app/bin/bringup
TimeoutStartSec=0
ExecStop=
PIDFile=@bringup.instance.path@/logs/bringup.pid
Restart=always

[Install]
WantedBy=multi-user.target

我该如何确保bringup-app进程在尝试启动之前等待30秒钟?目前虽然在Cassandra之后启动,但我注意到Cassandra集群尚未启动,因此任何作为启动的一部分尝试连接到Cassandra的bringup-app都将失败。

因此,我想添加一个延迟。这是否可以通过unit文件实现?


cassandra-service 应该在完全启动后才返回。也就是说,启动器应该等待服务准备就绪,然后退出。此外,cassandra-service 可以利用套接字激活。 - André Werlang
7个回答

196
你可以在使用 ExecStartPre 命令之前,运行 sleep 命令来等待一段时间,再执行 ExecStart 命令。
[Service]
ExecStartPre=/bin/sleep 30

29
不重启服务的情况下,你如何解决这个问题? - rubo77
12
对于我的使用情况来说,这能够在服务重新启动时起到额外的好处。 - sergtech
不知道为什么,但服务会循环30秒,而ExecStart不会启动。 - M. Rostami
5
我觉得systemd并不真正希望我这样做。由于延迟时间较长,会导致超时错误,例如systemctl start x在睡眠完成前将无法返回。 - phiresky
我也希望看到一种更优雅的依赖“连接”方式,类似于WaitOn=cassandra.service.output.deliverable的精神;这可能就是这个答案试图实现的内容? - Johnny Utahh
7
当使用超过90秒的延迟时,服务的启动超时时间必须增加(例如,TimeoutStartSec=120)。 - SomeDude

70

您可以创建一个.timer systemd单元文件来控制.service单元文件的执行。

例如,在启动后等待1分钟再启动foo.service,请在相同目录中创建一个foo.timer文件,其内容如下:

[Timer]
OnBootSec=1min

为了使所有的工作正常运行,重要的是禁用该服务(以便它不会在启动时启动),并启用计时器(感谢用户tride提供此信息):

systemctl disable foo.service
systemctl enable foo.timer
你可以在这里找到更多选项和所需的所有信息:https://wiki.archlinux.org/index.php/Systemd/Timers

3
虽然这样做可以实现目标,但对于简单的延迟来说有些过于复杂了。对于简单的延迟,使用"ExecStartPre"即可;而对于更复杂的计划任务,则可以使用定时器。 - mikijov
2
OnBootSec 显然是相对于“首次启动”时期的,所以如果您尝试在某个服务之后“启动”的服务未能在1分钟内启动,它仍可能会过早触发,我想知道这样是否正确?https://www.freedesktop.org/software/systemd/man/systemd.timer.html 但我猜,如果您设置足够长的数字,它可能会起作用... - rogerdpack
@rogerdpack 这是真的。然而,使用Cassandra,即使服务启动后,启动会话(使用cqlsh或其他方式)也不是立即可行的,这就是为什么这种方法对于这种特定情况更加方便的原因。但是,你基本上是在“猜测”它变得可用所需的时间。 - mj3c
1
如果我没记错的话,您还需要启动计时器 systemctl start foo.timer - Robert Klemme
5
这是最佳答案,因为systemd认为在执行ExecStartPre指令时单元正在'启动'。 - Paul Back

37

不要编辑启动服务,而是在它所依赖的服务中添加一个后启动延迟。将cassandra.service进行编辑,像这样:

ExecStartPost=/bin/sleep 30

这样增加睡眠时间不应该减慢依赖它的启动服务的重启速度(尽管会使其自身的启动变慢,也许这是可取的?)。


32

结合 @Ortomala Lokni 和 @rogerdpack 的回答,另一个选择是使依赖的服务在第一个服务启动/完成您正在等待的事情时进行监视。

例如,这是我如何让 fail2ban 服务等待 Docker 打开 443 端口(以便 fail2ban 的 iptables 条目优先于 Docker):

[Service]
ExecStartPre=/bin/bash -c '(while ! nc -z -v -w1 localhost 443 2>/dev/null; do echo "Waiting for port 443 to open..."; sleep 2; done); sleep 2'

只需将 nc -z -v -w1 localhost 443 替换为一个在第一个服务启动时失败(非零退出代码),并在其上运行成功的命令。

对于Cassandra案例,理想的是一个仅在集群可用时返回0的命令。

(可能还需要将 TimeoutStartSec 从默认值90秒增加到更长的时间,或设置 TimeoutStartSec=0 禁用启动超时)


5
这里的最佳答案非常被低估了!我增加了rogerdpack关于增加TimeoutStartSec的注释,或者你可以将其设置为0来禁用启动超时。 - PolyTekPatrick
1
这个方法的优点在于它可以适应几乎任何需求,而且由于它不必等待固定的时间才能继续引导序列,所以它是一种延迟引导过程的好方法,特别是如果延迟的长度未知。 - Robidu
这是一个鼓舞人心的答案!我将其添加到我的服务中;我讨厌任意长的延迟。我希望事情能尽快开始运转!这是用于挂载Veritas文件系统的:ExecStartPre=/bin/bash -c 'delay=60; i=0; while [ $i -lt $delay ]; do printf "$i "; [ -e /dev/vx/dsk/veritas0 ] && break; let i=$i+1; sleep 1; done; if [ $i = $delay ] ; then echo "ERROR: Timeout; exited."; exit 1; else exit 0; fi' - Mike S
请注意,在while循环中不需要加括号。我没有测试过,但我的示例没有使用它们。在这种情况下,它们只会添加一个不必要的子shell。 - Mike S

9

我认为Super User上的这个回答更好。

来自https://superuser.com/a/573761/67952

“但是,既然你要求不使用Before和After,你可以使用:

Type=idle

man systemd.service所解释的那样。

idle的行为与simple非常相似; 但是,服务程序的实际执行被延迟到所有活动作业都被分派之后。这可以用于避免 shell 服务的输出与控制台上的状态输出交错。请注意,此类型仅用于改善控制台输出,不适用作通用单元排序工具,并且此服务类型的效果受到5秒超时的影响,在此之后,服务程序将被调用。


它与oneshot非常相似,简单地不会为像vncservers这样在执行后退出shell的命令运行ExecStop(当然可以调整为不在后台运行),但我的意思是它也适用于后台进程。 - m3nda
4
你方便地从引文中省略了最重要的部分:“这种类型只有在改善控制台输出时才有用,它不适用于作为一般的单位排序工具,而且该服务类型的效果受到5秒超时的限制,在此之后服务程序仍将被调用[...]通常不建议使用空闲或单次触发来运行长时间的服务。” - bviktor
2
@bviktor,文档已更新。 你是正确的,它没有正确解释其目的。 文档确实说“然而,服务程序的实际执行要等到所有活动作业都被分派。”但是文档似乎自相矛盾。 - nelaaro
如果您的服务启动后立即进入“结束并进入空闲状态”的引导序列,则可能仍然不足以产生足够长的延迟... :| - rogerdpack
我怀疑这样做可能行不通,因为你正在引发竞态条件。预计已经安排的任何作业都将在你标记为空闲之前完成,因此你尝试在检查后执行的任何操作很有可能会在它们之前执行。你最好的选择仍然是直接检查所需服务的可用性,例如通过寻找它们的控制套接字或任何其他可行的方法。 - Robidu

9
systemd的方法是在进程设置完成后通过打开套接字或发送通知(或父脚本退出)来进行“回话”。当然,这并不总是直截了当的,特别是对于第三方软件而言。你可以尝试内联方式实现某些操作。
ExecStart=/bin/bash -c '/bin/start_cassandra &; do_bash_loop_waiting_for_it_to_come_up_here'

或者编写一个执行相同操作的脚本。或者将do_bash_loop_waiting_for_it_to_come_up_here放在ExecStartPost中。
或者创建一个帮助器.service,等待其启动,因此帮助器服务依赖于cassandra,并等待其启动,然后您的其他进程可以依赖于帮助器服务。
(可能还要将TimeoutStartSec从默认的90秒增加)

8

我使用了systemd定时器来延迟服务,效果非常好。

# /lib/systemd/system/foo.timer 
[Unit]
Description=Wait some second before run foo

[Timer]
OnActiveSec=5sec
AccuracySec=1s

[Install]
WantedBy=timers.target

查看定时器: systemctl list-timers

日志:

journalctl -f -u foo.timer
journalctl -f -u foo

1
在启动时延迟执行程序但不影响重新启动的最佳方法是什么? - Oriol Vilaseca
我所知道的就是上面这些,对我来说,在foo开始之后才运行,而不是在foo开始之前。也就是说,它没有延迟foo一丝一毫。 - Dan Jacobson
https://github.com/systemd/systemd/issues/24026#issuecomment-1615867761 提到要使用“edit”命令。这将创建一个 /etc/systemd/system/foo.service.d/override.conf 文件。非常方便! - Dan Jacobson

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