Libvirt通过存储池和卷对物理主机进行存储管理。
存储池是由管理员(通常是专门的存储管理员)预留给虚拟机使用的存储数量。存储池由存储管理员或系统管理员将存储池划分为存储卷,存储卷作为块设备分配给虚拟机。
例如,负责NFS服务器的存储管理员创建一个共享来存储虚拟机的数据。系统管理员在虚拟化主机上定义一个带有共享详细信息的池(例如,nfs.example.com:/path/to/share应该挂载在/vm_data上)。当池启动时,libvirt在指定的目录上挂载共享,就像系统管理员登录并执行’mount nfs.example.com:/path/to/share /vmdata’一样。如果池被配置为自动启动,libvirt确保NFS共享挂载在libvirt启动时指定的目录上。
一旦启动池,NFS共享中的文件将被做为卷,并且可以使用libvirt api查询存储卷的路径。然后,可以将卷的路径复制到VM的XML定义中,该定义描述了VM块设备的源存储。对于NFS,使用libvirt方法的应用程序可以在池(NFS共享中的文件)中创建和删除卷,但不能超过池的大小限制(共享的存储容量)。不是所有类型的池都支持创建和删除卷。停止池将取消启动操作,在本例中是卸载NFS共享。销毁操作不会修改共享上的数据,尽管名称不同。详情见man virsh。
第二个示例是iSCSI池。存储管理员发放iSCSI目标器,用于将lun集合呈现给虚拟机所在的主机。当libvirt配置为将iSCSI目标管理为池时,libvirt将确保主机登录到iSCSI目标,然后libvirt可以将可用的lun报告为存储卷。卷的路径可以在虚拟机的XML定义(如NFS示例)中查询和使用。在这种情况下,lun是在iSCSI服务器上定义的,libvirt不能创建和删除卷。
虚拟机的正常运行不需要存储池和卷。池和卷为libvirt提供了一种方法,以确保特定的存储块对VM可用,但一些管理员更喜欢管理自己的存储,并且在没有定义任何池或卷的情况下,VM将能够正常运行。在不使用池的系统上,系统管理员必须使用他们喜欢的任何工具来确保虚拟机存储的可用性,例如,将NFS共享添加到主机的fstab中,以便在引导时挂载该共享。
是否此时池和卷的价值超过传统的系统管理工具尚不清楚,请注意,libvirt的特点之一是它的远程协议,所以有可能来管理虚拟机的生命周期的所有方面,以及所需的虚拟机的配置资源。这些操作可以完全在Python libvirt模块内的远程主机上执行。换句话说,使用libvirt的管理应用程序可以让用户执行为虚拟机配置主机所需的所有任务:分配资源、运行虚拟机、关闭它和释放资源,而不需要shell访问或任何其他控制通道。
Libvirt支持以下存储池类型:
Directory backend
Local filesystem backend
Network filesystem backend
Logical backend
Disk backend
iSCSI backend
SCSI backend
Multipath backend
RBD (RADOS Block Device) backend
Sheepdog backend
Gluster backend
ZFS backend
存储池XML格式在https://libvirt.org/formatstorage.html#StoragePool上指定。
存储池是存储卷的容器。一个系统可以有任意多的存储池,每个存储池可以包含任意多的存储卷。
可以使用virConnect类的listAllStoragePools方法获取存储池对象列表。
flags参数可以是以下常量中的一个或多个:
VIR_CONNECT_LIST_STORAGE_POOLS_INACTIVE
VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE
VIR_CONNECT_LIST_STORAGE_POOLS_PERSISTENT
VIR_CONNECT_LIST_STORAGE_POOLS_TRANSIENT
VIR_CONNECT_LIST_STORAGE_POOLS_AUTOSTART
VIR_CONNECT_LIST_STORAGE_POOLS_NO_AUTOSTART
VIR_CONNECT_LIST_STORAGE_POOLS_DIR
VIR_CONNECT_LIST_STORAGE_POOLS_FS
VIR_CONNECT_LIST_STORAGE_POOLS_NETFS
VIR_CONNECT_LIST_STORAGE_POOLS_LOGICAL
VIR_CONNECT_LIST_STORAGE_POOLS_DISK
VIR_CONNECT_LIST_STORAGE_POOLS_ISCSI
VIR_CONNECT_LIST_STORAGE_POOLS_SCSI
VIR_CONNECT_LIST_STORAGE_POOLS_MPATH
VIR_CONNECT_LIST_STORAGE_POOLS_RBD
VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG
VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER
VIR_CONNECT_LIST_STORAGE_POOLS_ZFS
import sys
import libvirt
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
pools = conn.listAllStoragePools(0)
if pools == None:
print('Failed to locate any StoragePool objects.', file=sys.stderr)
exit(1)
for pool in pools:
print('Pool: '+pool.name())
conn.close()
exit(0)
在virStoragePool类中有许多可用的方法。下面的示例程序介绍了描述池的一些属性的一些方法。
import sys
import libvirt
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
pool = conn.storagePoolLookupByName('default')
if pool == None:
print('Failed to locate any StoragePool objects.', file=sys.stderr)
exit(1)
info = pool.info()
print('Pool: '+pool.name())
print(' UUID: '+pool.UUIDString())
print(' Autostart: '+str(pool.autostart()))
print(' Is active: '+str(pool.isActive()))
print(' Is persistent: '+str(pool.isPersistent()))
print(' Num volumes: '+str(pool.numOfVolumes()))
print(' Pool state: '+str(info[0]))
print(' Capacity: '+str(info[1]))
print(' Allocation: '+str(info[2]))
print(' Available: '+str(info[3]))
conn.close()
exit(0)
前面示例中显示的许多方法提供了关于远程文件系统、磁盘系统或本地文件系统类型的存储池的信息。例如。如果设置了autostart标志,那么当用户连接到存储池时,libvirt将自动使存储池可用(如果它不在本地文件系统上,例如NFS挂载)。如果没有设置自动启动,也需要启动本地文件系统上的存储池。
isActive方法指示用户是否必须以某种方式激活存储池。create方法可以激活存储池。
isPersistent方法指示是否需要使用create方法激活存储池。取值为1表示该存储池为持久化存储池,释放存储池后存储池将保留在文件系统上。
flags参数可以是以下常量中的一个或多个:
VIR_STORAGE_XML_INACTIVE
下面的示例展示了如何获取存储池的XML描述。
import sys
import libvirt
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
pool = conn.storagePoolLookupByName('default')
if pool == None:
print('Failed to locate any StoragePool objects.', file=sys.stderr)
exit(1)
xml = pool.XMLDesc(0)
print(xml)
conn.close()
exit(0)
下面的示例演示如何创建和销毁持久存储池和非持久存储池。 注意,如果存储池处于活动状态,则不能销毁它。 默认情况下,存储池创建时处于非活动状态。
import sys
import libvirt
xmlDesc = """
<pool type='dir'>
<name>mypool</name>
<uuid>8c79f996-cb2a-d24d-9822-ac7547ab2d01</uuid>
<capacity unit='bytes'>4306780815</capacity>
<allocation unit='bytes'>237457858</allocation>
<available unit='bytes'>4069322956</available>
<source>
</source>
<target>
<path>/home/dashley/images</path>
<permissions>
<mode>0755</mode>
<owner>-1</owner>
<group>-1</group>
</permissions>
</target>
</pool>"""
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
# create a new persistent storage pool
pool = conn.storagePoolDefineXML(xmlDesc, 0)
if pool == None:
print('Failed to create StoragePool object.', file=sys.stderr)
exit(1)
# destroy the storage pool
pool.undefine()
# create a new non-persistent storage pool
pool = conn.storagePoolCreateXML(xmlDesc, 0)
if pool == None:
print('Failed to create StoragePool object.', file=sys.stderr)
exit(1)
# destroy the storage pool
pool.undefine()
conn.close()
exit(0)
注意,除非调用delete方法,否则存储池中定义的存储卷将保留在文件系统上。 需要注意的是存储卷保留的位置,因为如果他们存在于一个远程文件系统或磁盘文件系统中,则guest domain不能使用,因为没有机制来激活libvirt的远程文件系统或磁盘存储系统在未来的某一时间。
可以通过检查存储池的XML描述来发现存储池的源。 下面是一个打印出池源描述属性的示例程序。
目前,storagePoolCreateXML方法的flags参数应该始终为0。
import sys
import libvirt
from xml.dom import minidom
poolName = 'default'
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
sp = conn.storagePoolLookupByName(poolName)
if sp == None:
print('Failed to find storage pool '+poolName, file=sys.stderr)
exit(1)
raw_xml = sp.XMLDesc(0)
xml = minidom.parseString(raw_xml)
name = xml.getElementsByTagName('name')
print('pool name: '+poolName)
spTypes = xml.getElementsByTagName('source')
for spType in spTypes:
attr = spType.getAttribute('name')
if attr != None:
print(' name = '+attr)
attr = spType.getAttribute('path')
if attr != None:
print(' path = '+attr)
attr = spType.getAttribute('dir')
if attr != None:
print(' dir = '+attr)
attr = spType.getAttribute('type')
if attr != None:
print(' type = '+attr)
attr = spType.getAttribute('username')
if attr != None:
print(' username = '+attr)
conn.close()
exit(0)
有许多方法可以配置存储池的各个方面。 主要方法是setAutostart方法。
import sys
import libvirt
poolName = 'default'
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
sp = conn.storagePoolLookupByName(poolName)
if sp == None:
print('Failed to find storage pool '+poolName, file=sys.stderr)
exit(1)
print('Current autostart seting: '+str(sp.autostart()))
if sp.autostart() == True:
sp.setAutostart(0)
else:
sp.setAutostart(1)
print('Current autostart seting: '+str(sp.autostart()))
conn.close()
exit(0)
从default存储池中显示存储卷
import sys
import libvirt
from xml.dom import minidom
poolName = 'default'
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
sp = conn.storagePoolLookupByName(poolName)
if sp == None:
print('Failed to find storage pool '+poolName, file=sys.stderr)
exit(1)
stgvols = sp.listVolumes()
print('Storage pool: '+poolName)
for stgvol in stgvols :
print(' Storage vol: '+stgvol)
conn.close()
exit(0)
通过info方法获取存储卷的信息。 下面的程序展示了如何列出“默认”存储池中每个存储卷的信息。
import sys
import libvirt
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
pool = conn.storagePoolLookupByName('default')
if pool == None:
print('Failed to locate any StoragePool objects.', file=sys.stderr)
exit(1)
stgvols = pool.listVolumes()
print('Pool: '+pool.name())
for stgvolname in stgvols:
print(' Volume: '+stgvolname)
stgvol = pool.storageVolLookupByName(stgvolname)
info = stgvol.info()
print(' Type: '+str(info[0]))
print(' Capacity: '+str(info[1]))
print(' Allocation: '+str(info[2]))
conn.close()
exit(0)
存储卷使用存储池createXML方法创建。 存储卷的类型和属性在传递给createXML方法的XML中指定。
flags参数可以是以下常量中的一个或多个:
VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA
VIR_STORAGE_VOL_CREATE_REFLINKVIR_CONNECT_LIST_STORAGE_POOLS_INACTIVE
import sys
import libvirt
stgvol_xml = """
<volume>
<name>sparse.img</name>
<allocation>0</allocation>
<capacity unit="G">2</capacity>
<target>
<path>/var/lib/virt/images/sparse.img</path>
<permissions>
<owner>107</owner>
<group>107</group>
<mode>0744</mode>
<label>virt_image_t</label>
</permissions>
</target>
</volume>"""
pool = 'default'
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
pool = conn.storagePoolLookupByName(pool)
if pool == None:
print('Failed to locate any StoragePool objects.', file=sys.stderr)
exit(1)
stgvol = pool.createXML(stgvol_xml, 0)
if stgvol == None:
print('Failed to create a StorageVol objects.', file=sys.stderr)
exit(1)
# remove the storage volume
# physically remove the storage volume from the underlying disk media
stgvol.wipe(0)
# logically remove the storage volume from the storage pool
stgvol.delete(0)
conn.close()
exit(0)
克隆存储卷与创建新的存储卷类似,不同的是大部分属性使用的是现有的存储卷。 新卷只使用XML参数中的名称和权限,其他所有内容都继承自现有卷。
需要注意的是,根据要克隆的存储卷的大小,克隆可能需要很长时间才能完成。 这是因为克隆进程将数据从源卷复制到新的目标卷。
import sys
import libvirt
stgvol_xml = """
<volume>
<name>sparse.img</name>
<allocation>0</allocation>
<capacity unit="G">2</capacity>
<target>
<path>/var/lib/virt/images/sparse.img</path>
<permissions>
<owner>107</owner>
<group>107</group>
<mode>0744</mode>
<label>virt_image_t</label>
</permissions>
</target>
</volume>"""
stgvol_xml2 = """
<volume>
<name>sparse2.img</name>
<allocation>0</allocation>
<capacity unit="G">2</capacity>
<target>
<path>/var/lib/virt/images/sparse.img</path>
<permissions>
<owner>107</owner>
<group>107</group>
<mode>0744</mode>
<label>virt_image_t</label>
</permissions>
</target>
</volume>"""
pool = 'default'
conn = None
try:
conn = libvirt.open("qemu:///system")
except libvirt.libvirtError as e:
print(repr(e), file=sys.stderr)
exit(1)
pool = conn.storagePoolLookupByName(pool)
if pool == None:
print('Failed to locate any StoragePool objects.', file=sys.stderr)
exit(1)
# create a new storage volume
stgvol = pool.createXML(stgvol_xml, 0)
if stgvol == None:
print('Failed to create a StorageVol object.', file=sys.stderr)
exit(1)
# now clone the existing storage volume
print('This could take some time...')
stgvol2 = pool.createXMLFrom(stgvol_xml2, stgvol, 0)
if stgvol2 == None:
print('Failed to clone a StorageVol object.', file=sys.stderr)
exit(1)
# remove the cloned storage volume
# physically remove the storage volume from the underlying disk media
stgvol2.wipe(0)
# logically remove the storage volume from the storage pool
stgvol2.delete(0)
# remove the storage volume
# physically remove the storage volume from the underlying disk media
stgvol.wipe(0)
# logically remove the storage volume from the storage pool
stgvol.delete(0)
conn.close()
exit(0)