1. 虛拟機鏡像的建立和resize流程
nova建立虛拟機涉及的元件比較多,調用比較複雜,這裡隻列出跟虛拟機鏡像建立相關的流程,友善理清虛拟機狀态變化的整個流程。
nova-api
nova.api.openstack.compute.servers.ServersController.create() # 接受建立請求,解析出image_uuid
nova.compute.api.API.create ()
nova.compute.api.API._create_instance() # 調用glance api擷取image對象
nova.conductor.api.LocalComputeTaskAPI.build_instances()
nova.conductor.manager.ConductorManager.build_instances() # 此處雖然接收block_device_mapping參數,但是是為了相容舊版,沒有使用。實際通過nova.objects.BlockDeviceMappingList.get_by_instance_uuid()擷取
nova.compute.rpcapi.ComputeAPI.build_and_run_instance() # 使用cast方法調用nova-compute的build_and_run_instance方法。
nova-compute
nova.compute.manager.ComputeManager.build_and_run_instance()
nova.compute.manager.ComputeManager._do_build_and_run_instance()
nova.compute.manager.ComputeManager._build_and_run_instance()
nova.compute.manager.ComputeManager._build_resources()
nova.compute.manager.ComputeManager._prep_block_device()
nova.virt.block_device.attach_block_devices()
nova.virt.block_device.DriverImageBlockDevice.attach()
nova.volume.cinder.API.create()
nova.virt.libvirt.driver.LibvirtDriver.spwan()
nova.virt.libvirt.driver.LibvirtDriver._create_image() # 此處會判斷如果不是從volume啟動,則調用imagebackend去建立虛拟機鏡像
nova.virt.libvirt.driver.LibvirtDriver._try_fetch_image_cache()
nova.virt.libvirt.imagebackend.Image.cache()
nova.virt.libvirt.imagebackend.Rbd.create_image()
nova.virt.libvirt.imagebackend.Rbd.clone()
nova.virt.libvirt.storage.rbd_utils.RBDDriver.clone() # 建立虛拟機鏡像,此處如果所使用的image後端不支援clone,或者鏡像不可clone(比如rbd中不是raw格式的鏡像),會觸發異常,create_image調用下面的fetch_image函數
nova.virt.libvirt.utils.fetch_image()
nova.virt.images.fetch_to_raw()
nova.virt.images.fetch()
nova.image.API.download()
nova.virt.images.convert_image()
nova.virt.images._convert_image() # 将鏡像拷貝到本地的/var/lib/instances/_base/目錄下,檔案名為md5(image).part,然後用qemu-img convert轉換為raw格式,名為md5(image).converted,最後重命名為md5(image)
nova.virt.libvirt.storage.rbd_utils.RBDDriver.import_image() # 這一步是在clone失敗,執行fetch_image的情況下,判斷虛拟機鏡像不存在,執行import_image将fetch的鏡像導入到RBD後端作為虛拟機鏡像。
nova.virt.libvirt.storage.rbd_utils.RBDDriver.resize() # 調整虛拟機鏡像大小
nova.virt.libvirt.imagebackend.Rbd.resize_image() # 調整虛拟機鏡像大小,RBD後端實際上在create_image時已經resize了,不會執行這一步,這裡應該是為了確定其他後端能夠正确設定虛拟機鏡像的大小
為了便于分析,用graphviz畫了在nova-compute的調用關系圖:

注:存儲後端用的是Ceph,是以調用的後端代碼是nova.virt.libvirt.imagebackend.Rbd,如果nova使用了不同的後端,比如本地的qcow2鏡像、raw鏡像、lvm等,隻需要對照nova.virt.libvirt.imagebackend中提供的對應實作,出入不會太大,因為它們都繼承nova.virt.libvirt.imagebackend.Image,有相同的接口。
至此,虛拟機的鏡像已經建立完畢,并且resize為flavor所設定的大小。後面是虛拟機啟動後,resize分區和檔案系統的過程。
一般虛拟機鏡像中會安裝cloud-init或者配置啟動腳本來對虛拟機做初始化配置。在cloud-init或啟動腳本中調用growpart和resizefs來完成分區和檔案系統的擴容。
2. 分區的resize
cloud-init支援使用growpart和gpart對分區進行擴容,時配置的mode而定,預設會按順序檢測系統中是否安裝了這兩個工具,使用第一個找到的。
growpart是AWS的擴充分區工具,它分别使用sfdisk和sgdisk對MBR和GPT分區表操作,先将分區表導出,然後改寫分區的其實扇區位置,最後将改寫後的分區表導入,完成分區的擴容。
# growpart [diskdev] [partnum]
gpart是FreeBSD推出的磁盤管理工具,GPT分區表将metadata的主本儲存在硬碟的開始,将副本儲存在硬碟的末尾,是以當虛拟機鏡像被擴容,相當于硬碟的容量變大,在GPT看來末尾的metadata副本丢失了,需要先執行recover指令恢複,然後再進行擴容。
# gpart recover [diskdev]
# gpart resize -i [partnum] [diskdev]
3. 檔案系統的resize
cloud-init通過依次嘗試解析/proc/$$/mountinfo、/etc/mtab和mount指令的輸出,來擷取根目錄所挂載的分區和檔案系統格式。
針對不通的檔案系統,使用不同的指令擴容:
# resize2fs [devpth] # ext檔案系統
# xfs_growfs [devpth] # xfs檔案系統
# growfs [devpth] # ufs檔案系統
# btrfs filesystem resize max [mount_point] # btrfs檔案系統