systemd 忽略单元文件中的 ExecStop,将其作为 ExecStart 的一部分运行

systemd 忽略单元文件中的 ExecStop,将其作为 ExecStart 的一部分运行

我正在尝试让几个 VirtualBox 映像在启动时自动启动,并在主机关闭前正确关闭。我有一个 bash 脚本,/usr/local/bin/vmctl.sh它使用对 VBoxManage 的调用来处理客户机映像的启动和停止。启动调用非常简单 - 它只是遍历一个映像列表并调用VBoxManage startvm --type headless "<imgname>"然后退出 0。停止调用循环遍历列表并调用VBoxManage controlvm "<imgname>" acpipowerbutton然后循环直到VBoxManage list runningvms返回一个空列表或 60 秒后才退出 0。从命令行运行脚本非常有效。

我已经在以下位置设置了一个单元文件/lib/systemd/system/vmctl.service

[Unit]
Description=VirtualBox Control
After=virtualbox.service

[Service]
ExecStart=/usr/local/bin/vmctl.sh start
ExecStop=/usr/local/bin/vmctl.sh stop

[Install]
WantedBy=multi-user.target

当我运行 时systemctl start vmctl.service,它会调用启动和停止行。当我调用 时,systemctl stop vmctl.service系统日志中有一个条目显示,Stopped VirtualBox Control但它不执行任何操作。

我对 systemd 完全是个新手。我最近将这个 Ubuntu 机器升级到了 16.04。我很确定这种行为有一个简单的解释,只是我没有看到。

谢谢!


根据马克的建议进行更新:

我确认了语法systemd-analyze verify /etc/systemd/system/vmctl.service(将文件移到那里后 - 感谢您的提示)。然后我按照您的建议更改了 ExecStart 和 ExecStop,运行systemctl daemon-reload后仍然看到相同的行为。日志显示调用时两者都在执行systemctl start vmctl,但运行时均未执行systemctl stop vmctl

# journalctl -u vmctl | tail
.
.
.
Apr 06 19:28:18 macmi10-builder systemd[1]: Started VirtualBox Control.
Apr 06 19:28:18 macmi10-builder echo[13901]: I started
Apr 06 19:28:18 macmi10-builder echo[13904]: I stopped
Apr 06 19:28:33 macmi10-builder systemd[1]: Stopped VirtualBox Control.

答案1

正如其他人提到的,问题在于vmctl.sh立即退出。与@Christophe的回答相反,分叉很可能不会起作用,因为vmctl.sh很可能不会分叉。您需要的是一个oneshot带有的服务RemainAfterExit=true。如果您将其更改为oneshot,您将获得完全相同的行为。该RemainAfterExit部分告诉它即使在ExecStart退出后,服务仍应被视为正在运行,因此它不应该运行ExecStop(s)。

答案2

你的systemd语法是正确的。你的问题出在其他地方。

首先,您可以使用以下命令确认语法本身是否正确:

systemd-analyze verify /path/to/your/vmctl.service

其次,尝试替换以下几行:

ExecStart=/bin/echo "I started"
ExecStop=/bin/echo "I stopped"

运行systemctl start vmctl或后systemctl stop vmctl,使用journalctl -u vmctl检查日志。我希望您确认systemd运行了正确的命令。

/lib/systemd/system可用于管理 systemd 文件。人类修改和手动管理的文件应该放在/etc/systemd/system

答案3

服务单元的默认类型设置为类型=简单,当配置了 ExecStart= 的进程是服务的主进程时使用。这样的单元将等待 ExecStart 指定的进程返回,然后通过运行 ExecStop 指定的进程来停用。在您的例子中,这将在虚拟机启动后立即发生(这不是您想要的)。

类型=分叉当 ExecStart 指定的进程预计在启动完成后退出,而其子进程继续在后台运行时,使用此方法。这是传统 UNIX 守护进程的行为,也是您的建议选择。ExecStop 指定的进程将在服务崩溃或执行“systemctl stop vmctl”命令时运行。

因此你的单元文件应该是:

[Unit]
Description=VirtualBox Control
After=virtualbox.service

[Service]
Type=forking
ExecStart=/usr/local/bin/vmctl.sh start
ExecStop=/usr/local/bin/vmctl.sh stop

[Install]
WantedBy=multi-user.target

相关内容