kubevirt除了定义VirtualMachineInstance
(后文简称VMI)这个与虚拟机对应的CRD,还定义了一个叫做VirtualMachine
(后文简称VM)的CRD。本文假设读者对VMI有一定的了解,并基于kubevirt@0.49.0来探讨下VM。
首先来看看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中,有以下的参数:
对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函数)
微信公众号卡巴斯同步发布,欢迎大家关注。