我对 systemd 服务的行为有点困惑。我有上述 systemd 服务。
[Unit]
After=libvirtd.service
[Service]
Type=simple
Environment=VM_XML=xxxxxxx
ExecStartPre=/usr/bin/bash /usr/local/lib/common/createQcowImage.sh ${VM_XML}
ExecStart=/usr/bin/bash /usr/local/lib/common/createVM.sh ${VM_XML}
ExecStop=/usr/bin/bash /usr/local/lib/common/destroyVM.sh ${VM_XML}
Restart=always
[Install]
WantedBy=multi-user.target
当此单元在 createVM.sh 脚本中启动时,它会创建一个 VM 并监视其状态。如果 VM 的 PID 不再存在,则脚本将退出并返回代码 1。我注意到,当发生这种情况时,会执行 ExecStop(我正在管理 /var/log/messages,当我使用 virsh destroy 手动销毁 VM 时,我将用于调试的 echo 播撒在从 ExecStop 执行的脚本中,以打印在 /var/log/messages 中)。这是 systemd 的默认行为吗?在单元退出时执行 ExecStop(我也尝试过以代码 0 退出,行为相同)。这不是我真正想要做的,因为在我销毁 VM 后,ExecStop 会尝试销毁同一个 VM,这会导致 systemd 单元故障。有什么办法可以避免这种情况吗?
答案1
如果您想创建一个按照预期运行的 systemd 服务配置,您需要充分了解所有选项的含义。
man systemd.service
我建议您开始阅读有关Type=
、、的文档(您可以使用例如) ExecStart=
。ExecStop=
也许Type=exec
、或Type=oneshot
更适合您的脚本?
在我看来,您所描述的行为确实是预期的。
为了处理您的虚拟机已被用户停止的情况,您需要测试它是否仍在运行,如果不运行,请避免销毁它。另外,请不要忘记 ExecStop 必须等待机器停止,而不仅仅是要求它停止。
我附加了我用来启动 VirtualBox VM 的用户服务,也许您可以在那里找到一些灵感。
[Unit]
Description=Virtual Machine as a headless service.
After=network.target virtualbox.target
Before=runlevel2.target shutdown.target
[Service]
Type=simple
Restart=no
Environment=VMNAME=my-vbox-vm
Environment=TIMEOUT=60
#ExecStartPre=VBoxManage snapshot $VMNAME take "before service start"
ExecStart=VBoxHeadless --startvm $VMNAME
# Sleep might be needed if there is some ExecStartPost later, to make sure that the VM was really powered up
#ExecStartPost=sleep 1
# sometimes it happens that the machine is paused from some reason
#ExecStartPost=bash -c "VBoxManage showvminfo $VMNAME | grep -E ^State:\\\\s+paused && VBoxManage controlvm $VMNAME resume || exit 0"
# If the VM uses the VirtualBox hdd encryption, so a line like below is needed to unlock the drive
#ExecStartPost=VBoxManage controlvm $VMNAME addencpassword $VMNAME "/home/user/VirtualBox VMs/my-vbxo-vm/my-vbox-vm.vmdk.pass"
# The 'savestate' is the only reliable way to turn off the machine.
# Optionally sending acpishutdown, can be considered, but better that needs to use the savestate as a fallback anyway,
# because the acpishutdown can be from various reasons ignored by the VM or delayed significantly.
#
# Alternatives for ExecStop
# - save state
# (should be reliable but not really turning off the VM;
# exit 0 necessary to handle the situation that VM was turned off already,
# from inside)
# ExecStop=bash -c "VBoxManage controlvm $VMNAME savestate || exit 0"
# - stop vm using acpi, with active waiting
# (not reliable - if acpi signal ignored by VM, or shutdown takes too long,
# systemd will just kill it after some timeout, (normally 90s);
# note that the active waiting is necessary,
# otherwise systemd might kill the process before shutdown is finished)
# ExecStop=bash -c "VBoxManage controlvm $VMNAME acpipowerbutton && while (VBoxManage list runningvms | grep $VMNAME); do sleep 1; done"
# - stop vm using acpi or, after a timeout, with save state
# (timeout let's say max 60s, to keep 30s for possible savestate;
# exit 0 necessary in order not to have the service in error state when stopped already by acpi, or maybe even by user from inside of VM)
ExecStop=bash -c "VBoxManage controlvm $VMNAME acpipowerbutton && (let timeout=$TIMEOUT; while VBoxManage list runningvms | grep --silent $VMNAME && test $timeout -gt 0; do sleep 1; let timeout=$timeout-1; done) && VBoxManage list runningvms | grep --silent $VMNAME && VBoxManage controlvm $VMNAME savestate || exit 0"
[Install]
WantedBy=default.target
答案2
这是 systemd 的默认行为
当使用 ExecStart 配置的进程是服务的主进程时,使用 Type=simple(默认设置)。此类单元将等待 ExecStart 指定的进程返回,然后通过运行 ExecStop 指定的进程来停用
我设法通过在从 ExecStop 执行的脚本中添加一个小检查来解决我的问题,以便仅当 VM 存在时才销毁 VM。
DOMAIN="$(xmllint --xpath '//domain/name/text()' $1)"
tmp=$(/usr/bin/virsh list --all | grep "$DOMAIN" | awk '{ print $3}')
if ([ "$tmp" != "" ]);then
echo "Destroying $DOMAIN"
/usr/bin/virsh destroy $(xmllint --xpath '//domain/name/text()' $1)
fi