kubevirt除了定义VirtualMachineInstance
(后文简称VMI)这个与虚拟机对应的CRD,还定义了一个叫做VirtualMachine
(后文简称VM)的CRD。本文假设读者对VMI有一定的了解,并基于kubevirt@0.49.0来探讨下VM。
VirtualMachine定义
首先来看看VM这个CRD的定义:
type VirtualMachine struct {metav1.TypeMeta `json:",inline"`metav1.ObjectMeta `json:"metadata,omitempty"`// Spec contains the specification of VirtualMachineInstance createdSpec VirtualMachineSpec `json:"spec" valid:"required"`// Status holds the current state of the controller and brief information// about its associated VirtualMachineInstanceStatus VirtualMachineStatus `json:"status,omitempty"`
}type VirtualMachineSpec struct {// Running controls whether the associatied VirtualMachineInstance is created or not// Mutually exclusive with RunStrategyRunning *bool `json:"running,omitempty" optional:"true"`// Running state indicates the requested running state of the VirtualMachineInstance// mutually exclusive with RunningRunStrategy *VirtualMachineRunStrategy `json:"runStrategy,omitempty" optional:"true"`// FlavorMatcher references a flavor that is used to fill fields in TemplateFlavor *FlavorMatcher `json:"flavor,omitempty" optional:"true"`// Template is the direct specification of VirtualMachineInstanceTemplate *VirtualMachineInstanceTemplateSpec `json:"template"`// dataVolumeTemplates is a list of dataVolumes that the VirtualMachineInstance template can reference.// DataVolumes in this list are dynamically created for the VirtualMachine and are tied to the VirtualMachine's life-cycle.DataVolumeTemplates []DataVolumeTemplateSpec `json:"dataVolumeTemplates,omitempty"`
}
在VirtualMachineSpec中,有以下的参数:
- Running:与RunStrategy字段互斥(即只能二选一),如果该字段为true,创建了VM对象后会根据Template中的内容创建VMI,false则不会。
- RunStrategy:与Running字段互斥(即只能二选一),虚拟机的运行策略,可选值为:Always-表示VMI对象应该一直是running状态;Halted-表示VMI对象永远都不应该是running状态;Manual-VMI可以通过API接口启动或者停止;RerunOnFailure-VMI初始应为running,当有错误发生时会自动重启;Once-VMI只会运行一次,当出现错误等情况时不会重启。
- Flavor:虚拟机底层特性配置,从代码上看目前只有CPU的配置,相关配置项包括是否绑定CPU(绑定的CPU只能给该虚拟机使用)、虚拟机中的线程数、NUMA配置等。flavor有VirtualMachineFlavor和VirtualMachineClusterFlavor两种类型数据。该字段会被填充到VMI模板对应字段中。
- Template:VMI的模板,类似deployment中配置的pod模板。
- DataVolumeTemplates:数据卷模板,这里配置的数据卷会自动创建,并且可以被Template字段的模板中使用。这些数据卷的生命周期和VM对象的生命周期一致。
VirtualMachine controller
对VirtualMachine CRD定义有些了解后,再来看看VirtualMachine对应的controller部分逻辑。
初始化部分:
// cmd/virt-controller/virt-controller.go
func main() { watch.Execute()
}// pkg/virt-controller/watch/application.go
func Execute() {/*...*/app.initVirtualMachines()/*...*/
}// pkg/virt-controller/watch/application.go
func (vca *VirtControllerApp) initVirtualMachines() {/*...*/vca.vmController = NewVMController(...)
}// pkg/virt-controller/watch/vm.go
func NewVMController(...) *VMController {/*...*/c.vmInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: c.addVirtualMachine,DeleteFunc: c.deleteVirtualMachine,UpdateFunc: c.updateVirtualMachine,})c.vmiInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: c.addVirtualMachineInstance,DeleteFunc: c.deleteVirtualMachineInstance,UpdateFunc: c.updateVirtualMachineInstance,})c.dataVolumeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: c.addDataVolume,DeleteFunc: c.deleteDataVolume,UpdateFunc: c.updateDataVolume,})return c
}
从初始化部分代码来看,vm controller注册了vm、vmi、dv三种资源的add/update/delete事件处理函数,当这三种资源事件发生后且满足一定的条件时,vm controller会把对应的vm对象的key(namespace/name)放入workQueue队列中。
有了入队,再看看出队消费逻辑:
// pkg/virt-controller/watch/vm.go
func (c *VMController) Execute() bool {key, quit := c.Queue.Get()/*...*/if err := c.execute(key.(string)); err != nil {/*...*/} else {/*...*/}return true
}// pkg/virt-controller/watch/vm.go
func (c *VMController) execute(key string) error {/*...*/// 根据vm对象DataVolumeTemplate中定义的dv列表// 获取已经创建的dv列表dataVolumes, err := c.listDataVolumesForVM(vm)if err != nil {logger.Reason(err).Error("Failed to fetch dataVolumes for namespace from cache.")return err}if len(dataVolumes) != 0 {// 如果dv已经存在,则把该dv的ownerReference声明为该vmdataVolumes, err = cm.ClaimMatchedDataVolumes(dataVolumes)if err != nil {return err}}var syncErr syncErrorsyncErr, err = c.sync(vm, vmi, key, dataVolumes)if err != nil {return err}/*...*/
}// pkg/virt-controller/watch/vm.go
func (c *VMController) sync(vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance, key string, dataVolumes []*cdiv1.DataVolume) (syncError, error) {/*...*/// 判断是否有dv不存在,如果不存在则创建// 当所有dv都已经ready时,dataVolumesReady为truedataVolumesReady, err := c.handleDataVolumes(vm, dataVolumes)if err != nil {syncErr = &syncErrorImpl{fmt.Errorf("Error encountered while creating DataVolumes: %v", err), FailedCreateReason}} else if dataVolumesReady || runStrategy == virtv1.RunStrategyHalted {syncErr = c.startStop(vm, vmi)} else {log.Log.Object(vm).V(3).Infof("Waiting on DataVolumes to be ready. %d datavolumes found", len(dataVolumes))}/*...*/
}// pkg/virt-controller/watch/vm.go
func (c *VMController) startStop(vm *virtv1.VirtualMachine, vmi *virtv1.VirtualMachineInstance) syncError {/*...*/switch runStrategy {case virtv1.RunStrategyAlways:// 1.如果vmi存在,判断虚拟机是否有stop操作,如果有则先删除旧的vmi// 2. 根据vm模板创建一个新的vmicase virtv1.RunStrategyRerunOnFailure:// 1.如果vmi存在,判断虚拟机是否有stop操作或者vmi状态为failed,则删除旧的vmi// 2.根据vm模板创建一个新的vmicase virtv1.RunStrategyManual:// 1.如果vmi存在且有stop虚拟机的动作,则删除旧的vmi// 2.如果vmi不存在且有start虚拟机的动作,则根据vm模板创建一个新的vmicase virtv1.RunStrategyHalted:// 1.如果vmi不存在,且有start虚拟机的动作,把vm对象的runStrategy改为virtv1.RunStrategyAlways// 2.如果vmi存在,则删除vmidefault:// 报错}
}
总结
通过上面的代码可以看出,vm controller的逻辑还是比较简单的,主要是创建/删除vmi,并根据vm中的runStrategy和vmi相匹配。有点类似于deployment和pod的关系:pod对应具体的应用实例,deployment则是基于pod做一些更完善的动作,例如控制pod副本数等。
总结起来vm和vmi的关系为:vmi对应虚拟机实例,vm则是在vmi的基础上的一层更完善的逻辑控制。通过vm创建的vmi虚拟机可以做到硬重启(删除vmi再创建vmi)等功能,单独的vmi虚拟机只能做到软重启、暂停等功能。
除了上述差异,virtctl操作这两种资源的子命令也不同。对vm资源的操作命令包括:virtctl restart/migrate/start/stop/portforward/addvolume/removevolume,对vmi资源的操作命令则为:virtctl freeze/unfreeze/softreboot/pause/unpause/console/vnc/usbredir/portforward/test/guestosinfo/userlist/filesystemlist/addvolume/removevolume。(对应pkg/virt-api/api.go composeSubresources函数)
微信公众号卡巴斯同步发布,欢迎大家关注。