Automating KVM Guest Shutdown/Suspend During Host Poweroff: libvirt hooks and systemd integration


3 views

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