When managing KVM virtualization environments, gracefully handling guest VMs during host shutdown is critical for data integrity. The challenge becomes more complex with mixed guest OS environments (like CentOS variants) where ACPI support may vary.
The libvirt daemon (virtqemud) has built-in shutdown handling that can be configured in /etc/libvirt/qemu.conf
:
# Set default shutdown behavior auto_destroy = 0 # 0=shutdown, 1=destroy
However, this only initiates shutdown and doesn't guarantee completion before host poweroff.
For CentOS/RHEL hosts, systemd provides the most robust approach. Create a service unit to manage guest shutdown sequencing:
# /etc/systemd/system/kvm-shutdown.service [Unit] Description=KVM Guest Shutdown Handler DefaultDependencies=no Before=shutdown.target reboot.target halt.target Requires=libvirtd.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/true ExecStop=/usr/bin/virsh list --name | while read domain; do virsh shutdown "$domain" while virsh list --name | grep -q "$domain"; do sleep 1 done done [Install] WantedBy=multi-user.target
Enable with:
systemctl daemon-reload systemctl enable kvm-shutdown.service
For environments requiring state preservation, modify the ExecStop command:
ExecStop=/usr/bin/virsh list --name | while read domain; do virsh managedsave "$domain" done
For guests that ignore ACPI signals, implement forced shutdown after timeout:
TIMEOUT=300 # 5 minutes ExecStop=/usr/bin/virsh list --name | while read domain; do virsh shutdown "$domain" COUNT=0 while virsh list --name | grep -q "$domain" && [ $COUNT -lt $TIMEOUT ]; do sleep 1 COUNT=$((COUNT+1)) done [ $COUNT -ge $TIMEOUT ] && virsh destroy "$domain" done
Test without rebooting using systemd's dry-run:
systemctl start kvm-shutdown.service systemctl stop kvm-shutdown.service --no-block journalctl -u kvm-shutdown.service -f
For more granular control, use libvirt's shutdown hooks:
# /etc/libvirt/hooks/daemon #!/bin/bash case "$1" in "shutdown") for domain in $(virsh list --name); do virsh shutdown "$domain" done ;; "restart") # Handle restart differently if needed ;; esac exit 0
Remember to make it executable:
chmod +x /etc/libvirt/hooks/daemon
When a KVM host initiates shutdown, the default behavior varies depending on your virtualization stack configuration. The libvirt daemon (which manages KVM) typically handles this through its own shutdown sequence, but we can implement more graceful approaches.
The most straightforward approach is configuring libvirt's automatic guest handling through /etc/libvirt/qemu.conf
:
# Set auto-shutdown behavior
auto_shutdown = "shutdown"
# Alternative for suspend-to-disk:
# auto_shutdown = "suspend"
# Timeout for graceful shutdown (seconds)
shutdown_timeout = 300
After modifying the configuration, restart libvirtd:
service libvirtd restart
For modern CentOS systems using systemd, create a custom service unit:
[Unit]
Description=KVM Guest Shutdown Handler
Before=shutdown.target reboot.target halt.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
ExecStop=/usr/bin/virsh list --name | while read dom; do
virsh shutdown "$dom" || virsh destroy "$dom"
done
[Install]
WantedBy=multi-user.target
Enable the service with:
systemctl enable kvm-guest-shutdown.service
For environments with Windows guests or older Linux versions that might not handle ACPI properly, consider this enhanced script:
#!/bin/bash
for guest in $(virsh list --name); do
# Check guest OS type from XML definition
os_type=$(virsh dumpxml $guest | grep -oP '\K[^<]+')
case $os_type in
"linux")
virsh shutdown $guest
;;
"windows")
virsh shutdown $guest
sleep 10
# Force shutdown if still running
if virsh list | grep -q $guest; then
virsh destroy $guest
fi
;;
*)
virsh suspend $guest
;;
esac
done
To implement suspend-to-disk functionality during host shutdown:
#!/bin/bash
SNAPSHOT_DIR="/var/lib/libvirt/snapshots"
mkdir -p $SNAPSHOT_DIR
for guest in $(virsh list --name); do
virsh managedsave $guest
# Optional: Create external snapshot
virsh snapshot-create-as $guest \
--name "shutdown-snapshot-$(date +%Y%m%d)" \
--disk-only --atomic
done
Test your configuration without actually shutting down:
# Dry-run shutdown sequence
systemctl --no-block isolate runlevel0.target
# Check logs for shutdown events
journalctl -u libvirtd -u kvm-guest-shutdown -n 100