杰瑞科技汇

Python libvirt API如何实现虚拟机管理?

Of course! Here is a comprehensive guide to using the Python libvirt API, covering everything from installation and basic connection to managing domains, networks, and storage.

Python libvirt API如何实现虚拟机管理?-图1
(图片来源网络,侵删)

What is libvirt?

libvirt is a C toolkit and daemon (libvirtd) that provides a stable and abstract API to manage platform virtualization technologies. It's not a hypervisor itself; it's a management layer that can control various hypervisors like:

  • KVM/QEMU (The most common on Linux)
  • Xen
  • LXC (Linux Containers)
  • VMware (ESXi, Workstation)
  • Hyper-V
  • Bhyve (on BSD)

The Python libvirt API is a Python wrapper around this C library, allowing you to automate and script the management of virtual machines and other virtualization resources.


Installation

Before you can use the API, you need to install both the libvirt library and the Python bindings.

A. Install the libvirt System Library

This is the core C library that the Python bindings will talk to.

Python libvirt API如何实现虚拟机管理?-图2
(图片来源网络,侵删)

On Debian/Ubuntu:

sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
  • qemu-kvm: The hypervisor (KVM + QEMU).
  • libvirt-daemon-system: The libvirtd service and its configuration.
  • libvirt-clients: Command-line tools like virsh.
  • bridge-utils: For creating network bridges.

On CentOS/RHEL/Fedora:

sudo dnf install -y qemu-kvm libvirt libvirt-client virt-install bridge-utils

Start and Enable the Service:

sudo systemctl enable --now libvirtd
sudo systemctl status libvirtd

B. Install the Python Bindings

You can install this using pip.

Python libvirt API如何实现虚拟机管理?-图3
(图片来源网络,侵删)
pip install libvirt-python

Connecting to the Hypervisor

The first step in any libvirt script is to establish a connection to the libvirt daemon.

import libvirt
# The URI determines how you connect.
# 'qemu:///system' is the most common for KVM on Linux,
# connecting to the system-wide instance of libvirtd.
try:
    conn = libvirt.open('qemu:///system')
except libvirt.libvirtError as e:
    print(f'Failed to open connection to qemu:///system: {e}')
    exit(1)
if conn is None:
    print('Failed to open connection.')
    exit(1)
print(f'Successfully connected to Hypervisor. Version: {conn.getVersion()}')

Common Connection URIs:

URI Description
qemu:///system Connects to the system-wide libvirtd instance. Manages system-wide VMs. (Most common)
qemu:///session Connects to a user-specific libvirtd instance. Manages VMs for your user account.
xen:// Connects to a Xen hypervisor.
vmware:// Connects to a VMware hypervisor.

Closing the Connection: Always close the connection when you're done.

conn.close()

Managing Virtual Machines (Domains)

In libvirt terminology, a running or stopped virtual machine is called a domain.

A. Listing All Domains

domains = conn.listAllDomains()
if len(domains) == 0:
    print("No domains found.")
else:
    print(f"Found {len(domains)} domains:")
    for domain in domains:
        print(f" - Name: {domain.name()}, ID: {domain.ID()}, State: {domain.state()[0]}")
  • listAllDomains() returns a list of all defined and running domains.
  • state() returns a tuple. The first element is an integer code for the state (e.g., libvirt.VIR_DOMAIN_RUNNING, libvirt.VIR_DOMAIN_SHUTOFF).

B. Getting a Specific Domain

You can get a domain by its name or its UUID.

# By Name
domain_name = 'my-vm'
domain = conn.lookupByName(domain_name)
# By UUID (UUID is a string)
domain_uuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
domain = conn.lookupByUUIDString(domain_uuid)
if domain is None:
    print(f"Domain '{domain_name}' not found.")
    exit(1)
print(f"Found domain: {domain.name()}")

C. Getting Domain Information

The XMLDesc() method is extremely powerful. It returns the full XML definition of the domain, which you can parse or use for backups.

xml_desc = domain.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
print("--- Domain XML Definition ---")
print(xml_desc)
print("-----------------------------")
# Get basic info
print(f"Name: {domain.name()}")
print(f"ID: {domain.ID()}")
print(f"Max Memory (MB): {domain.maxMemory() / 1024 / 1024}")
print(f"Current Memory (MB): {domain.memory() / 1024 / 1024}")
print(f"Number of vCPUs: {domain.vcpuMax()}") # Max vCPUs

D. Lifecycle Management

Create (Start) a Domain from an XML Definition This is useful for starting a VM that is defined but not currently running.

# Define the VM's configuration in XML
# This is a minimal example for a KVM VM
xml_config = """
<domain type='kvm'>
  <name>new-vm-from-api</name>
  <memory unit='KiB'>4194304</memory>
  <currentMemory unit='KiB'>4194304</currentMemory>
  <vcpu placement='static'>2</vcpu>
  <os>
    <type arch='x86_64'>hvm</type>
    <boot dev='hd'/>
  </os>
  <devices>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/var/lib/libvirt/images/new-vm-from-api.qcow2'/>
      <target dev='vda' bus='virtio'/>
    </disk>
    <interface type='network'>
      <source network='default'/>
      <model type='virtio'/>
    </interface>
    <graphics type='vnc' port='-1' autoport='yes'/>
  </devices>
</domain>
"""
# Create the domain from the XML
try:
    new_domain = conn.createXML(xml_config, 0)
    print(f"Successfully created domain: {new_domain.name()}")
except libvirt.libvirtError as e:
    print(f"Failed to create domain: {e}")

Start an Existing Domain If the domain is already defined (e.g., in /etc/libvirt/qemu/), you can just start it.

domain = conn.lookupByName('my-vm')
try:
    domain.create()
    print(f"Domain '{domain.name()}' started successfully.")
except libvirt.libvirtError as e:
    print(f"Failed to start domain: {e}")

Graceful Shutdown (ACPI Power Button) This sends a signal to the guest OS to shut down cleanly.

domain = conn.lookupByName('my-vm')
try:
    domain.shutdown()
    print(f"Domain '{domain.name()}' is shutting down.")
except libvirt.libvirtError as e:
    print(f"Failed to shutdown domain: {e}")

Force Stop (Power Off) This is equivalent to pulling the power cord. Use with caution.

domain = conn.lookupByName('my-vm')
try:
    domain.destroy()
    print(f"Domain '{domain.name()}' has been forcibly destroyed.")
except libvirt.libvirtError as e:
    print(f"Failed to destroy domain: {e}")

Managing Storage

Storage is typically represented as storage pools (directories, LVM volumes, etc.) and storage volumes (disk image files within those pools).

A. Listing Storage Pools

pools = conn.listAllStoragePools()
if not pools:
    print("No storage pools found.")
else:
    print("Available storage pools:")
    for pool in pools:
        print(f" - Name: {pool.name()}, Capacity: {pool.info()[1] / (1024**3):.2f} GB")

B. Getting a Storage Pool and its Volumes

pool_name = 'default'
pool = conn.storagePoolLookupByName(pool_name)
if pool is None:
    print(f"Storage pool '{pool_name}' not found.")
    exit(1)
# Get information about the pool
info = pool.info()
print(f"Pool '{pool.name()}' is in state: {info[0]}, Capacity: {info[1] / (1024**3):.2f} GB")
# List all volumes in the pool
volumes = pool.listAllVolumes()
print("\nVolumes in the pool:")
for vol in volumes:
    print(f" - Name: {vol.name()}, Capacity: {vol.info()[1] / (1024**3):.2f} GB")

C. Creating a New Storage Volume (Disk Image)

# Define the volume in XML
vol_xml = """
<volume type='file'>
  <name>my-new-disk.qcow2</name>
  <capacity unit='G'>20</capacity>
  <target>
    <path>/var/lib/libvirt/images/my-new-disk.qcow2</path>
    <format type='qcow2'/>
  </target>
</volume>
"""
try:
    # Create the volume
    new_vol = pool.createXML(vol_xml, 0)
    print(f"Successfully created volume: {new_vol.name()}")
except libvirt.libvirtError as e:
    print(f"Failed to create volume: {e}")

Managing Networks

Networks in libvirt are virtualized networks (like bridges or NAT networks).

A. Listing Networks

nets = conn.listAllNetworks()
if not nets:
    print("No networks found.")
else:
    print("Available networks:")
    for net in nets:
        print(f" - Name: {net.name()}, Active: {net.isActive()}")

B. Getting Network Information

net = conn.networkLookupByName('default')
if net is None:
    print("Network 'default' not found.")
    exit(1)
print(f"Network '{net.name()}' is active: {net.isActive()}")
print("--- Network XML ---")
print(net.XMLDesc(0))
print("--------------------")

A Complete Practical Example: Snapshotting a VM

This script connects to a VM, creates a snapshot, and then reverts to it.

import libvirt
import time
# --- Configuration ---
VM_NAME = 'my-vm'
SNAPSHOT_NAME = 'snapshot-before-update'
SNAPSHOT_DESCRIPTION = 'Snapshot taken before a major OS update.'
def main():
    # 1. Connect
    conn = libvirt.open('qemu:///system')
    if conn is None:
        print('Failed to open connection to qemu:///system')
        return -1
    # 2. Get the domain
    domain = conn.lookupByName(VM_NAME)
    if domain is None:
        print(f"Domain '{VM_NAME}' not found.")
        conn.close()
        return -1
    print(f"Working with domain: {domain.name()}")
    # 3. Define the snapshot XML
    snapshot_xml = f"""
    <domainsnapshot>
        <name>{SNAPSHOT_NAME}</name>
        <description>{SNAPSHOT_DESCRIPTION}</description>
        <memory snapshot='internal'/>
        <disks>
            <disk name='vda' snapshot='external'/>
        </disks>
    </domainsnapshot>
    """
    # 4. Create the snapshot
    print(f"Creating snapshot '{SNAPSHOT_NAME}'...")
    try:
        snapshot = domain.snapshotCreateXML(snapshot_xml, 0)
        print(f"Snapshot created successfully: {snapshot.getName()}")
        print(f"UUID: {snapshot.getUUIDString()}")
        print(f"State: {snapshot.getState()}")
    except libvirt.libvirtError as e:
        print(f"Failed to create snapshot: {e}")
        conn.close()
        return -1
    # 5. (Optional) List snapshots for the domain
    print("\n--- Listing all snapshots for the domain ---")
    try:
        snapshots = domain.snapshotListNames()
        if snapshots:
            for snap_name in snapshots:
                print(f" - {snap_name}")
        else:
            print("No snapshots found.")
    except libvirt.libvirtError as e:
        print(f"Failed to list snapshots: {e}")
    # 6. (Simulate some work) - In a real script, you would do something here.
    print("\nSimulating some work on the VM...")
    time.sleep(5)
    # 7. Revert to the snapshot
    print(f"\nReverting VM to snapshot '{SNAPSHOT_NAME}'...")
    try:
        # You need to get the snapshot object by its name to revert to it
        snapshot_to_revert = domain.snapshotLookupByName(SNAPSHOT_NAME)
        domain.revertToSnapshot(snapshot_to_revert)
        print("VM successfully reverted to the snapshot.")
    except libvirt.libvirtError as e:
        print(f"Failed to revert to snapshot: {e}")
    # 8. Cleanup: Delete the snapshot
    print(f"\nDeleting snapshot '{SNAPSHOT_NAME}'...")
    try:
        domain.snapshotDelete(snapshot_to_revert)
        print("Snapshot deleted successfully.")
    except libvirt.libvirtError as e:
        print(f"Failed to delete snapshot: {e}")
    # 9. Close connection
    conn.close()
    print("Connection closed.")
if __name__ == '__main__':
    main()

Best Practices and Tips

  1. Error Handling: Always wrap libvirt calls in try...except libvirt.libvirtError blocks. The libvirt API is powerful but can fail in many ways (e.g., VM not found, permission denied, connection lost).
  2. XML is King: The XMLDesc() method is your best friend. You can define an entire VM, network, or storage volume in XML and then use createXML() to instantiate it. This makes your scripts highly declarative and easy to version control.
  3. Permissions: The user running the Python script needs to be in the libvirt group to connect to qemu:///system.
    sudo usermod -aG libvirt $USER
    # You may need to log out and log back in for this to take effect.
  4. Async Operations: For long-running operations like migration or disk resizing, libvirt provides asynchronous APIs (e.g., domainMigrateSetMaxSpeed with a callback) to avoid blocking your script.
  5. Use virsh for Testing: Before writing a complex Python script, use the virsh command-line tool to perform the actions manually. This helps you understand the correct commands and XML structures needed. For example:
    • virsh list --all
    • virsh dominfo my-vm
    • virsh snapshot-create my-vm snapshot.xml
分享:
扫描分享到社交APP
上一篇
下一篇