当前位置: 首页 > 工具软件 > KubeVirt > 使用案例 >

kubevirt(五)VirtualMachine

牛骞仕
2023-12-01

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 created
    Spec VirtualMachineSpec `json:"spec" valid:"required"`
    // Status holds the current state of the controller and brief information
    // about its associated VirtualMachineInstance
    Status VirtualMachineStatus `json:"status,omitempty"`
}

type VirtualMachineSpec struct {
    // Running controls whether the associatied VirtualMachineInstance is created or not
    // Mutually exclusive with RunStrategy
    Running *bool `json:"running,omitempty" optional:"true"`

    // Running state indicates the requested running state of the VirtualMachineInstance
    // mutually exclusive with Running
    RunStrategy *VirtualMachineRunStrategy `json:"runStrategy,omitempty" optional:"true"`

    // FlavorMatcher references a flavor that is used to fill fields in Template
    Flavor *FlavorMatcher `json:"flavor,omitempty" optional:"true"`

    // Template is the direct specification of VirtualMachineInstance
    Template *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声明为该vm
        dataVolumes, err = cm.ClaimMatchedDataVolumes(dataVolumes)
        if err != nil {
            return err
        }
    }

    var syncErr syncError

    syncErr, 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为true
    dataVolumesReady, 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模板创建一个新的vmi
        
    case virtv1.RunStrategyRerunOnFailure:
        // 1.如果vmi存在,判断虚拟机是否有stop操作或者vmi状态为failed,则删除旧的vmi
        // 2.根据vm模板创建一个新的vmi
        
    case virtv1.RunStrategyManual:
        // 1.如果vmi存在且有stop虚拟机的动作,则删除旧的vmi
        // 2.如果vmi不存在且有start虚拟机的动作,则根据vm模板创建一个新的vmi
        
    case virtv1.RunStrategyHalted:
        // 1.如果vmi不存在,且有start虚拟机的动作,把vm对象的runStrategy改为virtv1.RunStrategyAlways
        // 2.如果vmi存在,则删除vmi
        
    default:
        // 报错
    }
}

总结

通过上面的代码可以看出,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函数)

微信公众号卡巴斯同步发布,欢迎大家关注。

 类似资料: