deep dive virtio networking and vhost net

在这篇文章里我们将会解释在 Post not found: introduction-virtio-networking-and-vhost-net [introduction] 里描述的vhost-net架构,并通过技术视角来弄清楚所有东西是怎么协同工作的。这系列博客里的这部分内容是为了让你们更好的理解virtio-networking领域是如何将虚拟化和网络连接在一起的

本文主要主要是面向对有兴趣理解上一篇blog提到的vhost-net/virtio-net底层原理的架构师和研发人员

我们将从描述不同的virtio规范的标准组件和共享内存在hypervisor里如何组织的,QEMU如何模拟一个virtio网络设备以及一个guest使用根据virtio规范来实现一个虚拟化驱动来管理并和设备通讯的

在展示过QEMU virtio架构,我们将会分析I/O瓶颈和限制,同时我们将会用host的kernel来解决这个问题,同时最后给出一个宏观的vhost-net的架构

最后,我们将会展示如何通过在它所运行的host上使用OVS(一个开源的虚拟化,SDN,分布式交换机)连接虚拟机到外部网络

希望在读完这篇文章后,你能够理解vhost-net/virtio-net架构是如何工作的,同时能够理解这架构中每一个组件的目标和作用以及数据包是如何被发送和接收的

Previous Concepts

在这个部分我们将会简短的介绍一些帮助你完全理解这篇文章需要知道的概念。对于精通这个问题的人是比较基础的内容,但主要是为了提供一个共同的基础

Networking

让我们从最基础的开始。一个物理网卡(Nic,Network Interface Card)是一个真实的硬件组件,允许物理机连接到外部网络。网卡可以承担一些offload,比如代替CPU执行checksum计算,Segmentation Offload(把一个很大片的数据转换成很多切片,切片大小就是以太网MTU的大小)或者是Large Receive Offload(从CPU角度看,就是把很多数据包合成一个数据包)

另外我们还有tun/tap设备,虚拟化的用户态用来交换数据包的点对点网络设备。交换二层(ethernet frames)数据的叫做tap设备,如果交换 (IP packets)三层数据的就是tun设备。

当tun的kernel模块被加载之后会创建一个特殊的/dev/net/tun设备。进程可以创建tap设备,并发打开这个设备发送一些特殊的ioctl命令给这个设备。新创建的tap设备在。dev文件系统里存在一个名字,并且其他的进程可以打开,并发送接收Ethernet frames

IPC,System programming

Unix sockets是一个在同一台物理机器上做进程间通信的有效方法。在这篇文章涉及的范围内,通讯的服务端监听了文件系统上一个路径下的Unix socket,因此一个客户端(client)可以连接到这个路径使用它。这样,进程间就可以交换消息了。注意,unix sockets也可以用来在进程间交换文件描述符。

eventfd是一个轻量级IPC的实现。虽然Unix sockets允许发送和接收各种消息,eventfd只是一个生产者可以修改,消费者可以读取的整型值。这个使得eventfd更适合用于等待通知机制,而不是传输信息的场景。

这两个IPC系统都为通信中的每个进程公开一个文件描述符。 fcntl调用对该文件描述符执行不同的操作,使它们成为非阻塞的(因此,如果没有可读取的内容,则读取操作会立即返回)。 ioctl调用遵循相同的模式,但实现特定于设备的操作,例如发送命令。

共享内存是我们要介绍的最后一个IPC方法。不同于提供一个进程间通讯的channel,共享内存使用进程的内存区域指向相同的内存页面,因此一个进程覆盖了这部分内存的修改也会影响其他进程之后的读操作。

QEMU and device emulation

QEMU是一个host层的虚拟机模拟器,给guest提供了一系列不同的硬件和设备模型。对host来说,qemu是一个标准Linux可调度的标准进程,有自己的进程内存。在进程里QEMU分配了内存区域来给guest当作物理内存么,同时QEMU还要执行虚拟机的CPU指令

为了在裸机设备上执行I/O操作么,比如存储和网络,CPU必须给物理设备下发特殊的指令并访问特殊的内存区域,比如这个设备被映射到的内存区域

当guest访问这些内存区域的时候,控制面就返回到了执行设备透明模拟的guest的QEMU里

KVM

Kernel-based Virtual Machine(KVM)是一个Linux内置的开源虚拟化技术。它为虚拟化软件提供硬件辅助,利用内置CPU虚拟化技术减少虚拟化开销(缓存、I/O、内存),提高安全性。

使用KVM,QEMU可以创建一个虚拟机,该虚拟机具有处理器识别的虚拟 CPU (vCPU),运行native-speed指令。当特殊的比如需要和设备或者特殊内存交互的命令到达KVM的时候,vCPU会停下来,然后通知QEMU暂停的原因,然后hypervisor就会对这个事件作出反应了。

在常规的KVM操作里,hyervisor会打开/dev/kvm这个设备,然后通过ioctl和他通讯调用它创建虚拟机,增加CPU,增加内存(qemu分配,但是虚拟机看来是物理内存),发送CPU中断(外部设备引发的),等等。举个例子,某一个ioctl的命令运行了KVM vCPU,阻塞了QEMU而且vCPU需要等到它找到了需要硬件辅助的命令。那时ioctl就会返回(这个叫做vmexit)同时QEMU也能知道这个exit的原因(比如offending instruction)。

对一些特别的内存区域,KVM有类似的访问方式,把内存区域标记为只读或者完全不映射,通过KVM_EXIT_MMIO造成一个vmexit。

The virtio specification

Virtio specification: devices and drivers

Virtio是一个虚拟机数据I/O的一个开放规范,提供了简单、有效、标准并且可拓展的虚拟设备机制,而不是固定在在每个环境或每个操作系统的机制。它主要基于guest可以和host共享内存以进行I/O来实现。

virtio规范基于两个元素:设备和驱动。在最经典的实现里,hypervisor通过一系列传输方法试将virtio设备暴露给guest。设计上他们在虚拟机内看起来是物理设备。

嘴常见的传输方法就是PCI或者PCIe总线。然而,这些设备在一些预定义好的guest内存地址是可用的(MMIO transport)。这些设备可以完全在没有物理设备或者是物理的兼容性接口的情况下被虚拟出来。

最典型最简单的暴露virtio设备的方法是通过PCI端口因为我们可以借用PCI已经是一个成熟并且在QEMU和Linux驱动自持的很好的协议。实际的PCI硬件通过特殊的物理内存地址范围和/或特殊的处理器指令暴露配置空间(比如,驱动可以通过这些内存范围读或者写设备的寄存器)。在虚拟机世界里,hypervisor能捕获访问这些内存范围并且执行设备模拟,暴露和真实设备相同的内存布局,并提供相同的返回。virtio标准也定义了PCI配置空间的布局,因此实现它是很简单的。

当guest驱动并使用PCI/PCI自动发现机制的时候,virtio设备通过PCI vendor ID和他们的PCI device ID标识自己。guest kernel通过使用这些标记来知道使用哪些驱动来处理这些设备。特别是,linux内核已经包含了virtio驱动程序。

virtio驱动必须能够分配给hypervisor和设备都能读写的内存区域,比如通过共享内存。我们把数据平面作为使用内存区域进行数据通讯的一个部分,同时控制平面主要是来配置他们。我们会在后续的文章里提供一个更深层次的virtio协议的实现细节以及内存布局。

virtio内核驱动共享了一个通用的传输专用接口(virtio-pci),并被用于实际的传输以及设备实现(比如virtio-net,virtio-scsi)

Virtio specification: virtqueues

Virtqueues是virtio设备的批量数据传输机制。每个设备可以有0个或者多个virtqueue link。它由guest分配的host可以访问并且可以读或者写的缓存队列组成。补充一下,virtio标准也定义了双向的通知:

  • Available Buffer Notification:使用驱动来通知buffer就绪并可以被设备处理
  • Used Buffer Notification:被设备使用来通知已经处理完了一些buffers

在PCI的场景,guest通过写一个特殊的内存地址发送available buffer notification,然后设备(这个场景里是QEMU)使用vCPU中断来发送used buffer notification。

virtio规范也允许notifications动态的启用或者停用。这种情况下设备和驱动可以批量的缓存通知或者主动的向virtqueues请求新的缓存。这个方法更适合高流量的场景。

总结一下,virtio驱动接口暴露了一下内容:

  • Device’s feature bits(设备和guest需要协商的部分)
  • Status bits(状态位)
  • Configuration space(包含设备特殊信息,比如MAC地址)
  • Notification system(配置变更,缓存可用,缓存使用)
  • Zero or more virtqueues
  • Transport specific interface to the device

Networking with virtio: qemu implementation

Figure 1: virtio-net on qemu

virtio网络设备是一个虚拟以太网卡,支持TX/RX(发送,接收)多队列。空的缓存被放在N virtqueues里用来接收数据包,往外送的数据包责备放到另外一个N virtqueues里面等待发送。另一个virtqueue被用于数据面之外的驱动和设备的通讯,比如控制高级过滤特性,设置mac地址,或者是一堆活跃的队列。像物理网卡一样,virtio设备支持很多特性比如offloading,能够让真实的host设备来处理。

为了发送数据包,驱动会发送给设备一个缓存包括metadata信息比如在要发送的packet frame上带有的数据包期望的offloading。驱动能够将这个缓存拆分成不同的条目,比如可以把这个metadata的header从packet frame上分离出来。

这些缓存被驱动管理,被映射给设备。因此这个情况下我们可以说这个设备实际上在hypervisor里(结合前面提到hypervisor)因为qemu能够访问所有的guest内存,所以有能力知道缓存的位置并能够对他们进行读写。

下面的流程图表示了virtio-net设备配置和使用virtio-net驱动发送数据包通过PCI和virtio-net设备通讯。在组装好要发送的数据包之后,出发了一个available buffer notification,把控制返回给QEMU然后它就能够把包通过TAP设备送出去

QEMU然后通知guest这些缓存操作执行完成了(读或者写)并且它通过把这些数据放到virtqueue然后发送一个used notification event来触发guest的vCPU中断。

接收数据包的过程和发送类似。唯一不同的是在接收数据包的场合,空缓存会提前被guest分配出来然后提供给设备一个可用缓存保证它能够把即将收到的数据写进去。

Figure 2: Qemu virtio sending buffer flow diagram

Vhost-net

vhost-net是一个内核驱动,实现了vhost协议的处理侧,用来实现一个高效的数据面。比如数据包转发,在这个实现里,qemu和vhost-net内核驱动(handler)使用ioctls来交换vhost消息和一大批叫做irqfd的类似eventfd的文件描述符然后ioeventfs被用来和guest交换通知

当vhost-net驱动被加载的时候,它会在/dev/vhost-net暴露一个字符设备。当qemu启用了vhost-net并启动后,它会打开这个设备并且一些ioctl调用初始化vhost-net实例。这是把vhost-net和hypervisor联系起来必不可少的步骤,准备virtio特性检查,然后给guest提供映射到vhost-net驱动的内存。

在初始化的过程里vhost-net驱动创建了一个内核线程叫做vhost-$pid这个$pid就是hypervisor(也就是qemu进程)的pid。这个线程也被叫做“vhost worker thread”

tag设备仍然被用于VM和host的通讯但是现在这个worker thread处理了这些I/O事件,比如它会不断poll驱动的通知或者是tap的事件并且做数据的转发。

Qemu分配了一个eventfd然后注册了到了vhost和KVM来实现通知的传递。vhost-pid内核线程poll这个eventfd,当guest写某一个特殊地址的时候KVM则会写这个eventfd。这个机制被叫做ioeventfd。用这个方法,对一个特别的guest内存地址简单的读写操作不需要再穿过钢轨的QEMU进程唤醒同时能够被直接路由到vhost worker thread。这也提供了异步的优势,也不再需要vCPU停下来(因此没必要立刻做上下文切换)

另一方面,qemu分配了另外的eventfd并再次注册他们到KVM和vhost来处理vCPU的直接中断注入。这个机制又叫irqfd,他们允许host通过写irqfd来注入vCPU中断到guest。同时也具有异步特性,不需要立刻做上下文切换

注意这些改动对virtio包处理后端对guest来说是完全透明的,guest仍然使用的是标准的virtio接口

下面的图展示了qemu数据路径的offloading到vhost-net内核驱动:

Figure 3: vhost-net block diagram

Figure 4: vhost-net sending buffer diagram flow

Communication with the outside world

guest能够和host通过tap设备通讯,然而还有一个遗留的问题就是guest如何和同一个host伤的其他vm或者是其他host上的vm通讯(使用internet通讯)

我们可以内核网络协议栈提供的转发和路由的机制,比如标准的Linux bridges。然而一个更加高级的解决方式就是一个全虚拟化的分发,管理交换机比如Open Virtual Switch

就像在总览篇说的一样,OVS的数据路径在这个场景里是作为内核模块运行的,ovs-switchd是一个用户态的控制管理守护进程然后ovsdb-server是一个转发数据库。

就像图上画的一样,OVS的数据路径在kernel运行然后在物理网卡和虚拟TAP设备间做包的forward:

Figure 5: Introduce OVS

总结

在这篇文章里,我们展示了virtio-net的架构是如何工作的,我们对每一个步骤做了详细的解剖并解释了每个组件的功能。

我们开始解释了默认的qemu IO设备如何通过提供给guest一个开放virtio标准的实现来运作的。我们接下来分析了guest如何和这些设备通过virtio驱动能够发送和接收数据包,发送和接收通知的

然后我们评价了在数据路径中有qemu的情况下需要切换上下文,然后展示了如何在host上使用vhost协议通过vhost-net内核驱动offload这些任务。我们也能够覆盖virtio通知在这个新设计下是如何工作的

最后我们展示了如何将VM连接到外部的非自己所在的host的世界。

在接下来的文章里我们将会提供一个使用之前学习到的解决方案里的不同组件完成关于vhost-net/virtio-net的架构实现。

如果你因为什么原因跳过了那篇文章,我们将在下一篇文章介绍一个新的用户态的使用DPDK的vhost处理协议。我们会列举他的优点,使用DPDK和用户态的观点,我们将建立第二个符合这些概念的架构。

Introduction to virtio-networking and vhost-net

背景

因为最近在看vdpa-blk,查资料的时候发现了红帽在19年写了一系列文章来介绍virtio网络入门的感觉和存储的加速思路差不多就拿来整理顺便做一下翻译。原文链接已附在文尾。

Introduction

virtio被开发作为云主机简化访问类似块设备和网络适配器的标准开放api。其中的virtio-net是一个虚拟以太网卡,是目前virtio支持过的最复杂的设备。本文会从virtio-networking的架构提供一个高级的基于建立host kernel和VM guest kernel的接口的解决方案。将介绍包括 KVM、qemu 和 libvirt 在内的基本组件,同时结合virtio spec和vhost协议以及Open vSwitch来讲云主机连接到外部世界。接下来描述的基于vhost-net/virtio-net的架构是许多virtio-networking架构中的第一个,这些架构将在一系列帖子中介绍,这些帖子因其性能、应用程序的易用性和实际部署而异。

读完这篇文章之后你应该能够清楚的了解提到的名词然后能够知道云主机中的应用如何发送数据包到其他云主机运行的应用以及如何发送到外部网络。这些术语也会作为下一片文章的基础。

VIrtio basic building blocks

guest VM或者说guest是被安装,执行并托管在物理机器上的虚拟机。这个提供托管guest VM并提供资源功能的机器就叫做host。guest通过hypervisor拥有分离的运行在host操作系统之上的操作系统。举个例子,host会提供虚拟网卡给虚拟机,guest机器则把它当作一个真实的网卡使用,虽然事实上它只是个虚拟网卡。

下列的组件创建了接下来virtio需要提供链接的虚拟环境

  1. KVM - kernel based virtiual machine允许Linux提供hypervisor的功能支持运行多个隔离的虚拟环境又叫做guests。KVM提供了Linux作为hypervisor的基本功能。比如内存管理,调度,网络栈等都包含在了Linux kernel里。虚拟机通常是通过标准Linux调度去调度的进程,同时上面加在了一些专用的虚拟硬件比如网络适配器。
  2. QEMU - A hosted virtual machine monitor。通过模拟给虚拟机提供一系列硬件和设备模型。QEMU 可以与 KVM 一起使用,利用硬件扩展以接近本机的速度运行虚拟机。guest通过qemu command line interface(CLI)运行。CLI 供了为QEMU指定所有必要配置选项的能力
  3. Libvirt - 一个翻译xml格式配置到qemu CLI调用的接口。它提供了一个admin守护进程来配置子进程比如qemu,因此qemu不需要root特权。例如,当Openstack Nova想要启动一个VM时,它使用libvirt通过为每个VM调用一个qemu进程来为每个VM启动一个qemu进程。

下图显示了这三个组件如何结合在一起:

host和guest都包含了kernel space和user space。可以根据图看到,KVM运行在host的内核态,而libvirt运行在host的用户态

guest虚拟机运行在qemu进程里,只是一个简单的运行在host用户态的继承并且和libvirt(用户态应用)以及KVM(内核态)通信

每一个虚拟机都会创建一个qemu进程,所以如果你创建N个虚拟机,你就会有N个虚拟机qemu进程,并且libvirt会和每一个进程通信

Virtio spec and the vhost protocol

当讨论vritio-networking的时候我们可以分开两层讨论

  1. 控制平面 - 用于host和guest之间能力的协商,用于建立和终止数据平面
  2. 数据平面 - 用于host和guest之间传输实际的数据(packets)

区分清楚这两个层级是很重要的,因为这些层级需要实现的要求不一样(比如性能)并且本文和后续文章也会展示很多不同的实现。

不过在以后的架构里最基础的要求,数据平面要求有尽可能好的性能来快速移动数据包,同时控制平面要求尽可能的有很好的拓展性来支持不同的设备和厂商

就像在开头提到的,virtio是被开发出来作为guest访问host设备的接口的。我们可以把virtio分成两部分:

  1. virtio spec - The virtio spec根据OASIS提到的。定义了如何创建一个介于host和guest之间的控制平面和数据平面。举个例子数据平面需要由缓存和环状布局组成,设置在spec里面详细说明的
  2. vhost protocol - 一个为了提升性能允许virtio数据平面的实现被下放到其他元素(用户态进程或者内核模块)

qemu进程中实现的virtio的控制平面是基于virtio spec的,然而数据平面不是。因此问题转变为了为什么数据平面没有像类似virtio spec中定义的那样实现在qemu进程里的?

答案是:性能问题

如果简单的按照virtio spec在qemu里实现了数据平面,我们就需要为每个从kernel进入guest的包切换上下文了反之亦然。这是一个代价很大的操作,增加了延迟同时也占用了更多的进程时间(提醒一下当前qemu只是一个Linux进程),所以我们想要尽可能的避免这个问题。

这就是为什么vhost protocol出现了,让我们能够去实现一个数据平面直接从host kernel到guest而不经过qemu进程

不过vhost协议本身仅描述了如何建立数据平面。任何实现被要求实现环状布局来描述数据缓存(包括host和guest的)以及真实发送/接受的数据包

就像之后的章节要介绍的内容那样,vhost协议可以被实现成内核态(vhost-net)或者是用户态(vhost-user)。本文描述的vhost-net/virtio-net架构专注于内核态的又被叫做vhost-net的实现

The vhost-net/virtio-net architecture

当我们谈论关于virtio接口的时候我们有一个后端组件和一个前端组件

  • 后端组件是host侧的virtio接口
  • 前端组件是guest侧的virtio接口

在vhost-net/virtio-net架构的组件里是这样的

  • vhost-net是运行在host内核空间
  • virtio-net运行在guest的内核空间

下面的图展示了宏观上virtio的前端和后端是如何对应的:

有一些点需要说明:

  • 因为vhost-net和virtio-net都运行在host和guest的内核空间,我们也叫他们驱动(drivers)所以如果有人“vhost-net-driver”不太要感觉奇怪指的是同一个东西
  • 在后端和前端之间有分离的控制平面和数据平面。就像前面解释过的,控制平面简单给vhost-net内核模块实现了virtio spec同时qemu进程进入guest的通讯最终到了virtio-net。Vhost-net使用了vhost协议建立了框架实现了使用共享内存的数据平面的host-guest内核包的直接转发的功能

实际上数据面通讯 接收 receive(RX)和发送 transmit(TX)是通过vCPU专用队列完成的

每一个guest都能根据vCPU的数量关联起一堆RX/TX队列,这个队列对应每一个CPU。举一个更精确的例子比如有4 vCPUs会像这样(去掉控制平面简化一下这个图):

Virtio-networking and OVS

目前我们已经描述了如何guest能够通过virtio-networking接口把包送到host内核。为了转发这些包到其他运行在同一台host或者其他host的guest上,我们使用OVS

OVS是一个软交换机,能够给提供内核内包转发。它由用户态和内核态两个部分组成

  • 用户空间 - 包括一个数据库(ovsdb-server)和一个OVS deamon来管理和控制交换机(ovs-switched)
  • 内核空间 - 包括ovs内核模块主要负责数据通路和转发平面

OVS controller同时和数据库服务以及内核转发面通讯。为了使得包能够出入OVS我们使用了Linux端口。在我们的场景里,我们使用了一个端口连接OVS内核转发平面到一个物理网卡同时另外一个端口连接到vhost-net的后端。

注意我们是在描述简化过的场景。实际上可能会有多个网卡连接到OVS多个端口和虚拟机运行,。因此也需要很多端口连接到vhost-net的后端。

接着讲virtio-networking下面的图展示了OVS如何连接到virtio:

请注意所提到的用于将OVS连接到主机外部和vhost-net以及从vhost-net连接到virtio-ne 和在VM中运行的应用程序的端口。

这个总结了vhost-net/virio-net的基于host内核,guest内核和内核态OVS的架构的大概内容

Summary

在这篇文章中,我们已经触及了 virtio-networking 生态系统的表面,向您介绍了 virtio-networking 使用的虚拟化和网络的基本构建组件。我们简短的涵盖了virtio spec和vhost protocol,回顾了用于实现 virtio 接口的前端和后端架构,并带您了解了 vhost-net(host内核)与 virtio-net(guest内核)通信的 vhost-net/virtio-net 架构。

我们在尝试解释事物时遇到的一个基本挑战是术语历史问题。例如,virtio-net既指virtio规范中的virtio网络设备实现,也指vhost-net/virtio-net架构中描述的guest内核前端。我们试图通过在上下文解释术语并使用virtio-net仅描述guest内核前端来解决这个问题。

正如将在后面的文章中解释的那样,基于使用DPDK和不同硬件offload技术的virtio规范网络设备还有其他实现,这些都在virtio-networking的保护伞下。

接下来的两篇文章会提供一个更加深入的关于vhost-net/virtio-net架构的理解。一篇文章将面向架构师,提供对vhost-net/virtio-net的技术深入研究,并解释数据平面和控制平面在实践中是如何实现的。面向开发人员的另一篇文章将是一个动手部分,包括Ansible脚本,以实现对vhost-net/virtio-net架构的试验。

附录

原文链接: introduction-virtio-networking-and-vhost-net

QEMU FT方案

背景

因为对qemu的fault tolerance技术颇有兴趣,在查询资料时找到了一个IBM实现的MicroCheckpointing方案实现的ft查看文档并整理了一下这个ft方案的原理

MicroCheckpointing

基本概念 Micro-Checkpoints (MC) 默认工作在QEMU虚拟机热迁移的逻辑上,可以被简单的理解为“在执行不会结束的热迁移”。比如,在一个确定的周期譬如0.01s去的循环里去执行如下逻辑:

1
2
3
4
5
1. 在N ms之后stop vm(paused)
2. 通过调用live migration的逻辑生成一个MC用于标识dirty memory并写入到qemu本地的暂存区
3. Resuse vm
4. 传输MC到目标节点
5. 重复1

当发生failure的情况,目标节点的机器用最新的MC恢复guest

这里涉及的几个问题

MC本身的设备I/O一致性

对qemu guest来说,设备I/O的一致性主要包括网络设备和存储设备

即在这N ms后vm stop,这段时间里,内部往外的网络包都会被cache住,直到MC被传输结束

存储设备则需要保证MC传输结束这段时间磁盘内容也被同步了,如果本来使用的就是共享磁盘则问题不大,同理也是需要磁盘I/O在MC之间也是被cache住的

这里有一点比较奇怪,就是既然vm已经处于stop状态,为什么还需要阻止网络包往外发(实际上应该没有网络包了)不过可以理解的是,这个目的是出于failure后恢复的考虑,这样恢复到目标节点之后,这些网络包都会被回复掉,保证MC的一致性

出于这些一致性考虑,MC的步骤需要作出如更改:

1
2
3
4
5
6
7
8
9
10
11
1. Insert a new Qdisc plug (Buffer A).
2. 在N ms之后stop vm(paused)
3. 通过调用live migration的逻辑生成一个MC用于标识dirty memory并写入到qemu本地的暂存区
4. Insert a *new* Qdisc plug (Buffer B). This buffers all new packets only.
5. 立刻回复VM并继续往下运行保持继续工作 (making use of Buffer B).
6. 传输MC到目标节点
7. 等到传输结果被确认
8. 确认传输成功
9. Release the Qdisc plug for Buffer A.
10. Qdisc Buffer B now becomes (symbolically rename) the most recent Buffer A
11. 继续步骤2

根据以上步骤,我们可以知道一个recent Buffer里面会buffer的数据包是上一次MC创建的时候VM resume开始到下一个MC传输完成的数据包,这个逻辑为什么能够保证网络一致呢

举一个具体的例子 比如当前VM在Buffer A里面保存的数据包P1,然后第一次执行MC得到了MC1,然后VM继续运行,创建了Buffer B来拦截新的数据包,到MC1传输完成之后P1被送出去了,Buffer A被清空,而这段时间里面由Buffer B拦截了P2,这个时候如果发生了Fail,目标的机器会恢复到MC1的状态,而因为Buffer B拦截了P2可知,MC1继续运行会发送P2,因此网络连接可以继续进行

Failure Recovery

基于micro-checkpointing高频率的特性,每秒都会生成多个MC。即使错过了个别MC也没有关系,因为I/O Buffer保证了在下一个MC被传输完成之后才会继续运行。

因此判断出错的有以下两种情况:

  1. MC over TCP/IP: 一旦socket连接断开. 这问题可能出现在传输最后一个MC的时候流量太大或者是确认MC传输成功的请求超时等。

  2. MC over RDMA: 因为无限带宽的逻辑没有提供任何底层的超时机制,这个实现给QEMU的RDMA migration protocol 增加了简单的keep-alive。如果发生了多少次keep-alive消息的丢失则认为发生了fail。

在这两种情况下主备两端都能够通过一样的机制判断对方是不是挂掉了

如果主节点被判断挂掉了,备节点就会使用最近的MC恢复启动

如果备节点被判断挂掉了,就执行和live migrate相同的逻辑重新启动一个备份节点

总结

这个方案是一个很好的利用live migrate实现的ft方案

尤其是里面提到的如何保证网络一致,以及通过Qdisc保证网络包顺序,这个在云主机热迁移本身就有应用,即(paused时作为数据包的buffer)避免出现网络断开的情况

同时也解释了为什么恢复checkpoint之后依旧可以有连续的网络,当然考虑到tcp/udp的时候,这里可能对tcp来说更加友好,对使用udp的应用来说排队和保证顺序意义不那么大了

其次是创建checkpoint的时候需要保证I/O一致性需要不停的paused,并且要保证IO请求都sync,感觉也是很麻烦的事情

之后在抽时间看看Qdisc以及qemu虚拟机的IO请求路径,希望能对虚拟化的原理有更好的认识

参考文献

  1. https://wiki.qemu.org/Features/MicroCheckpointing