kubevirt(三)迁移(migration)

这篇具有很好参考价值的文章主要介绍了kubevirt(三)迁移(migration)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

通过之前《kubevirt(一)虚拟化技术》和《kubevirt(二)实现原理》两篇文章,我们对kubevirt有了初步的了解,本文基于这些内容,我们来看看kubevirt虚拟机的迁移(migration)。

注:

本文内容仅限于同一个kubernetes集群内的虚拟机迁移,且本文内容基于kubevirt@0.49.0

前言

虚拟机迁移一般是指因宿主机出现故障时,需要将上面运行的虚拟机迁移到其它宿主机上的过程。

在做虚拟机迁移前,首先需要考虑迁移前后宿主机的硬件资源差异性,包括宿主机架构(x86、ARM等)、宿主机cpu类型(Intel、AMD等)等因素,这部分内容需要结合具体业务场景具体分析,不在本文讨论范围内。

除去硬件资源差异,kubevirt虚拟机迁移还需要考虑以下问题:

  1. kubevirt如何做kvm迁移?

kubevirt的kvm虚拟机是运行在pod中的,为了实现kvm迁移,kubevirt定义了一个叫作VirtualMachineInstanceMigration的CRD。用户如果想迁移kubevirt kvm,编写一个VirtualMachineInstanceMigration对象apply即可,apply后对应的controller会在其它节点创建一个新的kvm pod,之后再做切换,从而完成kvm的迁移。

  1. 迁移过程对业务的影响?

迁移可以分为冷迁移(offline migration,也叫常规迁移、离线迁移)和热迁移(live migration,也叫动态迁移、实时迁移),冷迁移在迁移之前需要将虚拟机关机,然后将虚拟机镜像和状态拷贝到目标宿主机,之后再启动;热迁移则是将虚拟机保存(save)/回复(restore),即将整个虚拟机的运行状态完整的保存下来,拷贝到其它宿主机上后快速的恢复。热迁移相比于冷迁移,对业务来说几乎无感知。

kubevirt的VirtualMachineInstanceMigration支持live migration,官方资料可参考《Live Migration in KubeVirt》

  1. 数据对迁移的限制?

这里说的数据主要考虑内存数据、系统盘/数据盘数据,系统盘和数据盘如果使用了宿主机的本地盘(如SSD),迁移后数据会丢失,云盘(包括pvc)则无影响。

  1. 如何保证kubevirt kvm pod不再调度到本机?

kubevirt的kvm pod调度由k8s调度器完成,因此为了防止新的kvm pod再次调度到本节点,可以通过给节点打污点等方法来解决。

  1. 业务方对虚拟机的ip是敏感的,如何保证迁移后虚拟机的ip不变?

kubevirt的kvm虚拟机是在pod中启动的,从而该pod和对应的虚拟机ip与k8s网络方案有关,因此可以考虑在CNI网络方案中实现kvm的固定ip。

VirtualMachineInstanceMigration

VirtualMachineInstanceMigration(下文简写vmiMigration)是kubevirt定义的一个CRD,接下来我们从源码和流程两个角度来看看kubevirt是如何通过这个CRD实现kvm迁移的。

vmiMigration源码分析

vmiMigration这个CRD的定义如下,从CRD的定义中可以看出,一个vmiMigration对应一台虚拟机(vmiMigration.spec.vmiName)的迁移,迁移本身是一个很复杂的过程,vmiMigration和vmi本身都有相关字段记录迁移的状态。

// staging/src/kubevirt.io/api/core/v1/types.go
type VirtualMachineInstanceMigration struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec              VirtualMachineInstanceMigrationSpec   `json:"spec" valid:"required"`
    Status            VirtualMachineInstanceMigrationStatus `json:"status,omitempty"`
}

type VirtualMachineInstanceMigrationSpec struct {
    // The name of the VMI to perform the migration on. VMI must exist in the migration objects namespace
    VMIName string `json:"vmiName,omitempty" valid:"required"`
}

type VirtualMachineInstanceMigrationStatus struct {
    Phase      VirtualMachineInstanceMigrationPhase       `json:"phase,omitempty"`
    Conditions []VirtualMachineInstanceMigrationCondition `json:"conditions,omitempty"`
}

vmiMigration作为一个CRD必然会有对应的controller逻辑,它的controller逻辑是放在virt-controller中的,因此我们从virt-controller的入口开始:

// cmd/virt-controller/virt-controller.go
func main() {
    watch.Execute()
}

再到virt-controller的主逻辑找到vmiMigration controller的入口:

// pkg/virt-controller/watch/application.go
func Execute() {
    ...
    app.initCommon()
    ...
}

func (vca *VirtControllerApp) initCommon() {
    ...
    vca.migrationController = NewMigrationController(
        vca.templateService,
        vca.vmiInformer,
        vca.kvPodInformer,
        vca.migrationInformer,
        vca.nodeInformer,
        vca.persistentVolumeClaimInformer,
        vca.pdbInformer,
        vca.migrationPolicyInformer,
        vca.vmiRecorder,
        vca.clientSet,
        vca.clusterConfig,
    )
    ...

vmiMigration controller注册事件处理函数如下,它会监听vmi的add/delete/update事件、pod的add/delete/update事件、vmiMigration的add/delete/update事件以及pdb(PodDisruptionBudget)的update事件。

// pkg/virt-controller/watch/migration.go
func NewMigrationController(...) {
    ...
    c.vmiInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc:    c.addVMI,
        DeleteFunc: c.deleteVMI,
        UpdateFunc: c.updateVMI,
    })

    c.podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc:    c.addPod,
        DeleteFunc: c.deletePod,
        UpdateFunc: c.updatePod,
    })

    c.migrationInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc:    c.addMigration,
        DeleteFunc: c.deleteMigration,
        UpdateFunc: c.updateMigration,
    })

    c.pdbInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
        UpdateFunc: c.updatePDB,
    })
    ...
}

不管上述哪种事件处理函数,都是去找到关联的vmiMigration对象然后放入队列中。我们以pod的update事件处理函数为例来看:

// pkg/virt-controller/watch/migration.go
// When a pod is updated, figure out what migration manages it and wake them
// up. If the labels of the pod have changed we need to awaken both the old
// and new migration. old and cur must be *v1.Pod types.
func (c *MigrationController) updatePod(old, cur interface{}) {
    curPod := cur.(*k8sv1.Pod)
    oldPod := old.(*k8sv1.Pod)
    if curPod.ResourceVersion == oldPod.ResourceVersion {
        // Periodic resync will send update events for all known pods.
        // Two different versions of the same pod will always have different RVs.
        return
    }

    labelChanged := !reflect.DeepEqual(curPod.Labels, oldPod.Labels)
    if curPod.DeletionTimestamp != nil {
        // having a pod marked for deletion is enough to count as a deletion expectation
        c.deletePod(curPod)
        if labelChanged {
            // we don't need to check the oldPod.DeletionTimestamp because DeletionTimestamp cannot be unset.
            c.deletePod(oldPod)
        }
        return
    }

    curControllerRef := c.getControllerOf(curPod)
    oldControllerRef := c.getControllerOf(oldPod)
    controllerRefChanged := !reflect.DeepEqual(curControllerRef, oldControllerRef)
    if controllerRefChanged && oldControllerRef != nil {
        // The ControllerRef was changed. Sync the old controller, if any.
        if migration := c.resolveControllerRef(oldPod.Namespace, oldControllerRef); migration != nil {
            c.enqueueMigration(migration)
        }
    }

    migration := c.resolveControllerRef(curPod.Namespace, curControllerRef)
    if migration == nil {
        return
    }
    log.Log.V(4).Object(curPod).Infof("Pod updated")
    c.enqueueMigration(migration) // 这里把关联的vmiMigration对象放入队列中
    return
}

有了上面入队的逻辑,再来看看队列消费逻辑:

// pkg/virt-controller/watch/migration.go
func (c *MigrationController) runWorker() {
    // 不断消费队列数据
    for c.Execute() {
    }
}

func (c *MigrationController) Execute() bool {
    // 如果队列中有数据,每次从队列中取一个数据消费
    key, quit := c.Queue.Get()
    if quit {
        return false
    }
    defer c.Queue.Done(key)
    err := c.execute(key.(string))

    if err != nil {
        log.Log.Reason(err).Infof("reenqueuing Migration %v", key)
        c.Queue.AddRateLimited(key)
    } else {
        log.Log.V(4).Infof("processed Migration %v", key)
        c.Queue.Forget(key)
    }
    return true
}

func (c *MigrationController) execute(key string) error{
    ...
    if needSync {
        syncErr = c.sync(key, migration, vmi, targetPods)
    }
    err = c.updateStatus(migration, vmi, targetPods)
    ...
}

队列消费这边有两个重要的函数:c.sync和c.updateStatus。先看看c.sync:

// pkg/virt-controller/watch/migration.go
func (c *MigrationController) sync(...) {
    ...
    // 根据vmiMigration的状态做不同处理
    switch migration.Status.Phase {
    case virtv1.MigrationPending:
        if !targetPodExists {
            // 如果目标pod(迁移后起来的pod)还不存在,则会根据vmi和vmi的pod生成一个新的pod
            // 这个pod就是迁移后跑虚拟机的pod
        } else if isPodRead(pod) {
            // 如果目标pod已存在且是ready状态,检查是否热插拔(hotplug)卷
            // 如果有,则会检查attachmentPods有没有,没有就创建
        } else {
            // 处理超时的目标pod,超时没起来超过一定时间自动删除(后续逻辑会继续尝试重建)
        }
    
    case virtv1.MigrationScheduling:
        // 处理超时的目标pod,超时没起来超过一定时间自动删除
        
    case virtv1.MigrationScheduled:
        // 当目标pod存在且ready时,更新vmi的status.migrationState相关字段,并将vmi和目标pod关联起来
        
    case virtv1.MigrationPreparingTarget, virtv1.MigrationTargetReady, virtv1.MigrationFailed:
        // 如果目标pod不存在或者已经退出,更新vmi的status.migrationState相关字段,并标注该vmi的迁移结束
    
    case virtv1.MigrationRunning:
        // 如果vmiMigration的deletionTimestamp被打上(即vmiMigration被删除)
        // 更新vmi的status.migrationState相关字段,并标注为abort
    }
    ...

再看看c.updateStatus:

// pkg/virt-controller/watch/migration.go
func (c *MigrationController) updateStatus(...) {
    ...
    if migration.IsFinal() {
        // 如果迁移已经结束(不管是成功还是失败),移除vmiMigration的finalizer
    } else if vmi == nil {
        // 如果vmi不存在,vmiMigration.status.phase标识为failed
    } else if vmi.IsFinal() {
        // 如果vmi已经是结束状态(failed或succeed),vmiMigration.status.phase标识为failed
    } else if podExists && podIsDown(pod) {
        // 如果目标pod存在但是是failed或succeed状态,vmiMigration.status.phase标识为failed
    } else if migration.TargetIsCreated() && !podExists {
        // 如果vmiMigration状态中标识目标pod已创建但实际目标pod不存在,,vmiMigration.status.phase标识为failed
    } else if migration.TargetIsHandedOff() && vmi.Status.MigrationState == nil {
        // 如果vmiMigration状态是已结束,但vmi.status.migrationState还是空的,vmiMigration.status.phase标识为failed
    } else if migration.TargetIsHandedOff() &&
        vmi.Status.MigrationState != nil &&
        vmi.Status.MigrationState.MigrationUID != migration.UID {
        // 如果vmiMigration状态是已结束,但vmi的migration UID不是自己,vmiMigration.status.phase标识为failed
    }else if vmi.Status.MigrationState != nil &&
        vmi.Status.MigrationState.MigrationUID == migration.UID &&
        vmi.Status.MigrationState.Failed {
        // 如果vmi的migration UID是自己但是vmi migration标识为failed,vmiMigration.status.phase也标识为failed
    } else if migration.DeletionTimestamp != nil && !migration.IsFinal() &&
        !conditionManager.HasCondition(migration, virtv1.VirtualMachineInstanceMigrationAbortRequested) {
        // 如果vmiMigration被打上删除标记,但本身状态不是终止状态,vmiMigration的condition会增加一个abort记录
    } else if attachmentPodExists && podIsDown(attachmentPod) {
        // 热插拔pod不正常时,vmiMigration.status.phase也标识为failed
    } else {
        switch migration.Status.Phase {
        case virtv1.MigrationPhaseUnset:
            // 如果vmiMigration状态没有设置(为空),且vmi可以迁移,则更新vmiMigration状态为Pending
        case virtv1.MigrationPending:
            // 如果状态为Pending,且目标pod已存在,则更新vmiMigration状态为Scheduling
        case virtv1.MigrationScheduling:
            // 如果状态为Scheduling,且目标pod已经ready,则更新vmiMigration状态为Scheduled
        case virtv1.MigrationScheduled:
            // 如果状态为Scheduled,且vmi.status.migrationState.targetNode不为空,则更新vmiMigration状态为PreparingTarget
        case virtv1.MigrationPreparingTarget:
            // 如果状态为PreparingTarget,且vmi.status.migrationState.targetNodeAddress不为空,则更新vmiMigration状态为TargetReady
        case virtv1.MigrationTargetReady:
            // 如果状态为TargetReady,且vmi.status.migrationState.StartTimestamp不为空(即已经开始迁移),则更新vmiMigration状态为Running
        case virtv1.MigrationRunning:
            // 如果状态是Running,且vmi.status.migrationState.completed = true,则更新vmiMigration状态为Succeed
        }
    }
    ...
}

通过代码可以发现,单纯依靠vmiMigration controller是无法完成整个迁移过程的,因为vmi.status.migrationState下的targetNode、startTimestamp、completed等字段都不是在vmiMigration controller中设置。事实上这部分数据是virt-handler设置的,virt-handler这部分逻辑从代码上不是很好讲述,因此本文略过此部分,但在下文的流程中会对virt-handler的作用做相关说明。

流程分析

有了上面的源码分析,我们假设现在有一个running的kvm(即存在一个running状态的vmi和pod),此时apply一个VirtualMachineInstanceMigration会发生什么:

kubevirt,kubernetes,kubernetes,云计算

  1. 初始状态,k8s中存在一个vmi和它对应的pod(src pod),即etcd中存在一个vmi对象和一个src pod对象,且node以上正常运行着这个pod;
  2. kubectl apply一个vmiMigration;
  3. apiServer收到这个请求,把vmiMigration对象存入etcd;
  4. virt-controller中的vmiMigration controller(后文简称migration controler)的informer机制监听到有vmiMigration创建,且状态未设置(为空),于是调apiServer接口把vmiMigration状态更新为Pending;
  5. apiServer更新etcd中vmiMigration对象状态为Pending;
  6. migration controller的informer机制监听到vmiMigration的update事件,且状态是Pending,且目标pod(dst pod)还不存在,则调apiServer接口创建dst pod;
  7. apiServer把dst pod对象存入etcd;
  8. migration controller监听到dst pod创建事件,调apiServer接口更新vmiMigration状态为Scheduling;
  9. apiServer更新etcd中的vmiMigration状态为Scheduling;
  10. k8s scheduler的informer监听到dst pod创建,通过调度算法,把dst pod的spec.nodeName设置为node2,并调apiServer接口更新pod信息;
  11. apiServer更新src pod的spec.nodeName字段;
  12. node2上的kubelet创建dst pod,并不断向apiServer更新pod状态,包括最终dst pod变成ready状态;
  13. apiServer接收node2上kubelet上报的dst pod状态,更新到etcd中;
  14. 经过一段时间后,dst pod变为ready状态,并被migration controller监听到,于是migration controller调apiServer更新vmiMigration状态为Scheduled;
  15. apiServer更新etcd中的vmiMigration状态为Scheduled;
  16. migration controller监听到vmiMigration状态变为Scheduled,查找集群中的migration policy(kubevirt的另一个CRD资源),并调apiServer更新vmi(注意这里是vmi而不是vmiMigration)status.migrationState下的migrationUID(更新为vmiMigration的uid)、targetNode(即node2)、sourceNode(即node1)、targetPodName(dst pod名称)、migrationPolicyName和migrationConfiguration字段;
  17. apiServer更新etcd中vmi对象的status.migrationState下上述字段;
  18. migration controller监听到vmiMigration状态是Scheduled且vmi对象的status.migrationState.targetNode不为空,调apiServer更新vmiMigration状态为PreparingTarget;
  19. apiServer更新etcd中vmiMigration状态为PreparingTarget;
  20. node2上的virt-handler会先通过grpc调用virt-launcher方法拿到一些数据,然后基于这些数据启动一个dst migration proxy,用于后续迁移时数据传输;同理node1上的virt-handler也会起个src migration proxy,这两个proxy之间的通信可以配置另外的网卡,防止迁移流量影响k8s本身网络;之后node2上virt-handler的vmController调apiServer接口给vmi打上migration的finalizer,同时更新vmi.status.migrationState下的targetNodeAddress和targetDirectMigrationNodePorts。virt-handler是以daemonSet+hostNetwork形式部署的,所以targetNodeAddress其实只需要virt-handler取其pod ip即可。
  21. apiServer更新etcd中vmi的migration finalizer和targetNodeAddress、targetDirectMigrationNodePorts字段;
  22. migration controller监听到vmi的targetNodeAddress不为空,调apiServer接口更新vmiMigration状态为TargetReady;
  23. apiServer更新etcd中的vmiMigration状态为TargetReady;
  24. node1上virt-handler的vmController会在第19步后,通过unix sock的方式调用源pod virt-launcher的grpc接口,virt-launchert调用libvirt开始异步执行migration,如果是nonroot,uri为qemu+unix:///session?socket={源proxy unix socket文件};否则uri为qemu+unix:///system?socket={源proxy unix socket文件}。最后如果node1上virt-handler发现migration已经开始了,则调apiServer接口更新vmi的status.migrationState.startTimestamp等字段;
  25. apiServer更新etcd中vmi的status.migrationState.startTimestamp等字段;
  26. migrationController监听到vmi的status.migrationState.startTimestamp不为空,调apiServer接口更新vmiMigration状态为Running;
  27. apiServer更新etcd中vmiMigration的状态为Running;
  28. node1上virt-handler的vmController会调virt-launcher接口检查migration是否已完成,如果已完成调apiServer接口更新vmi的status.migrationState.completed为true;
  29. apiServer更新etcd中vmi的status.migrationState.completed为true;
  30. migration监听到vmi.status.migrationState.completed为true,调apiServer更新vmiMigration状态为succeeded;
  31. apiServer更新etcd中vmiMigration状态为succeeded;
  32. node1和node2上virt-handler的vmController执行相关清理动作,包括清理proxy、删除源kvm对应的domain资源等。

微信公众号卡巴斯同步发布,欢迎大家关注。文章来源地址https://www.toymoban.com/news/detail-611871.html

到了这里,关于kubevirt(三)迁移(migration)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 记Flask-Migrate迁移数据库失败的两个Bug——详解循环导入问题

    1、找不到数据库:Unknown database ‘***’ 若还没有创建数据库,该迁移工具不会自动创建。你可以使用SQL命令手动创建一个数据库: 2、迁移后没有效果:No changes in schema detected. 我的情况长话短说,就是创建的数据模型类没有注册到程序实例 app ,解决方案是使用工厂函数。细

    2024年02月11日
    浏览(36)
  • 【Flask 连接数据库,使用Flask-Migrate实现数据库迁移及问题汇总】

    Flask 连接数据库,使用Flask-Migrate实现数据库迁移 安装Flask-Migrate插件 使用Flask-Migrate步骤 app.py主要用于数据库连接 model.py 中导入了 db,作用是存储一个User类 ,用于生成表头。 manager.py用于数据迁移管理,运行后将生成一个文件夹。 Flask-Migrate运行 问题汇总 问题一:flask_mig

    2024年01月16日
    浏览(46)
  • Django的数据库模型迁移命令makemigrations和migrate是否会导致数据库中的数据丢失?

    我们知道,如果在Django的文件models.py中写好了数据库模型,要生成对应的数据库,需要执行下面两条命令: 其中命令 makemigrations 是生成迁移执行文件,命令 migrate 是执行迁移命令。 那么如果修改了数据库模型文件models.py的内容,比如新增了一张表,那么是否会造成原来数据

    2024年02月12日
    浏览(50)
  • 【Jetpack】使用 Room 中的 Migration 升级数据库异常处理 ( 多个数据库版本的迁移 | fallbackToDestructiveMigration() 函数处理升级异常 )

    Room Migration 数据库迁移工具 是 Android Jetpack Architecture Components ( 架构组件 ) 的一部分 , 它是一个方便的 数据库迁移工具 , 用于为 Android 中使用 Room 框架创建的数据库 提供 自动化迁移方案 ; Room Migration 数据库迁移工具用途如下 : 数据库修改 : 修改数据库表结构 ; 迁移代码 : 为

    2024年02月08日
    浏览(46)
  • Debezium系列之:使用 Strimzi 将 Kafka 和 Debezium 迁移到 Kubernetes

    在本文中,将探讨在生产中实现debezium与K8s的结合: 在 Kubernetes 集群中安装和管理 Apache Kafka 集群。 在 Kubernetes 集群中部署 Debezium Kafka Connect。 Kubernetes 是一个开源容器编排器,本文使用 minikube 作为 Kubernetes 集群,但相同的步骤在任何其他实现中都应该有效。 启动集群 在终

    2024年02月15日
    浏览(44)
  • kubevirt(二)实现原理

    在《kubevirt(一)虚拟化技术》一文中,我们对 libevirt + qemu + kvm 虚拟化做了一些简单的说明,本文基于这些内容,来看看kubevirt是怎么结合kubernetes平台实现虚拟化的。 kubevirt项目地址:https://github.com/kubevirt/kubevirt,在介绍kubevirt之前,我们先对kubernetes的 informer 和 CRD 两个概念

    2023年04月08日
    浏览(24)
  • kubevirt(六)网络

    通过前面的文章,我们对kubevirt有了一些简单的了解,本文我们来看看kubevirt虚拟机的网络实现原理。 kubevirt是k8s的一个CRD实现,每个kubevirt虚拟机对应一个vmi对象和一个pod对象,而k8s本身对pod网络有了一些规范(CNI),所以在了解kubevirt虚拟机网络前,有必要先对k8s的pod网络

    2024年01月19日
    浏览(30)
  • KubeVirt技术介绍及实验部署

    在云计算发展中,有两类虚拟化平台: openstack(iaas):关注于资源的利用,虚拟机的计算,网络和存储 Kubernetes(pass):关注容器的编排调度,自动化部署,发布管理 1、 Type-1,native or bare-metal hypervisors :硬件虚拟化 这些Hypervisor是直接安装并运行在宿主机上的硬件之上的,

    2024年02月07日
    浏览(26)
  • ubuntu k8s 安装kubevirt

    ubuntu版本 内核版本 k8s版本

    2024年02月04日
    浏览(21)
  • kubevirt(四)热插拔卷(hotplug volume)

    在使用虚拟机时,会有因磁盘空间不足需要外挂存储卷的操作(当然也有反向的操作,即卸载存储卷),本文我们来了解下kubevirt对运行中的虚拟机动态操作存储卷的实现,也就是热插拔存储卷。 hotplug volume热插拔卷,热插拔在这里指的是虚拟机在不关机断电的情况支持插入

    2024年02月03日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包