searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

COLO介绍

2023-03-21 03:01:20
467
0

关键词

COLO、高可用、FT、PVM、SVM、Failover

相关术语

PVM

Primary VM, 主虚拟机

SVM

Secondary VM, 备虚拟机

FT

Fault Tolerance, 容错机制

COLO

COarse-grained LOck-stepping Virtual Machines for Non-stop Service, 一种基于客户端/服务器系统的粗粒度锁步虚拟机复制方法,实现不间断服务

lock-step

锁步同步虚拟机状态

checkpoint

增加检查点用于虚拟机状态周期性同步

failover

故障切换

Pnode

Primary Node, 主计算节点

Snode

Secondary Node, 备计算节点

 

1.简介

    云计算时代,越来越多的关键应用被部署到云端,虚拟机的可靠性成为衡量云平台的一个重要指标,但现实情况是软硬件故障不可能完全避免,因此容错机制(Fault Tolerance)被引入。
对于虚拟机容错,我们通常理解为:通过创建和维护与主虚拟机相同,并且可在主虚拟机发生故障时立即切换并替换主虚拟机的辅助虚拟机,来确保虚拟机的连续可用性,简单的说就是为虚拟机创建一个完全相同的可替换副本。

虚拟机容错的主要实现技术是虚拟机复制(virtual machine replication), 传统的方案有:锁步复制(Lock-step)和周期性状态复制(checkpoint)。

锁步复制(Lock-step):通过虚拟机锁步技术,实现在每一条指令边界的虚拟机状态复制。对于确定性指令(deterministic instructions),主虚拟机(PVM)和备虚拟机(SVM)可以在没有VMM介入的情况下并行执行。而对于不确定性指令(non-deterministic instructions),通过锁步技术,由虚拟机监视器模拟执行,确保(在指令边界)获得相同的执行结果。这种方法在多处理器情况下遭受非常大的锁步开销,因为多处理器环境下的内存访问结果是不确定的。因此,这种解决方式效率低下。

周期性状态复制(checkpoint):在每一个周期的边界,设置虚拟机状态同步检查点(checkpoint)进行同步。也就是说,周期性的将 PVM 的状态复制到 SVM 上。周期性的虚拟机状态复制方法在一个周期内将输出数据包缓存起来,直到下一个检查点进行同步的时候才发送出去,以便SVM 能在硬件出错的时候成功接管。但是,受限于传输缓存带来的额外的网络延迟和频繁的虚拟机状态同步的开销,周期性的虚拟机状态复制方法的性能一直存在瓶颈,也没有得到广泛使用。

虚拟机锁步和周期性状态复制方法实现了虚拟机状态的完全复制,但其缺点也很明显(开销大、性能差、延迟高等)。对于某些应用场景如C/S系统来说,客户端并不关心服务器内部的某些寄存器值,只关心服务器对请求的响应,只要保证PVM和SVM对客户端请求的响应一致,那就可以认为SVM可以替换PVM,而不需要苛求SVM和PVM完全一致。基于此原理,上海交大和Intel等提出了COLO方案,基于客户端/服务器系统的粗粒度锁步虚拟机复制方法,实现不间断服务。注:粗粒度是相对于细粒度(fine-grained)的; 举个例子,粗粒度是识别出猫和狗,细粒度还需要识别出狗的种类:吉娃娃、萨摩耶还是哈巴狗; 对于c/s系统来说,粗粒度可以进一步为只关心响应包的负载,而不关心比如时间戳等头部信息。

2.原理及架构

COLO (COarse-grained LOck-stepping)是一种高可靠性解决方案,COLO 架构如下图。它由两个网络互通的物理节点组成:Pnode主节点和Snode备节点,主节点运行主虚拟机(PVM),备节点运行备虚拟机(SVM)并作为 PVM 的备份。PVM 和 SVM 独立并行运行,PVM接收到来自Client或外部网络的数据包,然后转发一份给SVM,PVM和SVM根据该请求各自产生响应数据包。PVM截获SVM的响应数据包并与自己的对比,如果相同,则直接发送响应数据包给Client,如果不同,则表明SVM已经开始不能替换PVM,需要将PVM同步到SVM,同步完成后再将PVM的响应数据包发送给Client。

COLO主要由以下4个重要组件构成:
COLO framework:COLO框架,实现连续迁移功能(保证SVM和PVM状态一致)和故障切换控制。
COLO Proxy:负责将Client请求发送给PVM和SVM,然后接收两者的响应并对比,根据结果确定是否需要进行同步。
COLO Block Replication:磁盘复制功能,当PVM把数据写入磁盘时,需要同时把数据发送到SVM,以便保证SVM和PVM磁盘数据一致;SVM侧则需要将PVM的磁盘数据缓存起来,在checkpoint或failover时合并数据。
HeartBeat:心跳功能,在Pnode和Snode上运行,周期性检查系统可用性,如果Pnode或PVM出现故障,则Snode会触发failover,以便SVM接替PVM提供服务。但目前qemu colo心跳功能仍待开发,因此需要调用x-colo-lost-heartbeat手动触发。

COLO Block Replication

  • primary disk:primary vm disk
  • secondary disk:secondary vm disk,size必须和primary disk相同,一般是primary disk的拷贝。
  • hiddent-disk:用于缓存Secondary disk中被nbd client修改前的数据,必须是qcow2格式,size必须与primary disk相同。
  • active-disk:用于缓存SVM写入磁盘的数据,必须是qcow2格式,size必须与primary disk相同。
  • quorum:一种分布式投票机制,colo中用于处理PVM 2镜像副本(本机镜像和nbd镜像)的读写,其读写的副本以child的形式存在;写数据时会写入所有child,如果一个child写入成功则认为写操作成功;读数据时优先读取第一个child(即本机镜像),失败后尝试后续child。PVM和SVM均有使用quorum driver,PVM上需要处理本机镜像和nbd镜像的读写,SVM上则没有是实际作用,主要是因为当SVM替换PVM后需要quorum。
  • replication:用于控制镜像复制,分为primary和secondary模式,PVM上使用primary模式,SVM上使用secondary模式,其主要逻辑在于secondary模式,secondary模式下会缓存PVM和SVM各自写入磁盘的数据,在checkpoint完成时丢弃SVM写入磁盘的数据,在SVM failover时合入SVM的数据。
  • 对于二级镜像base <--- top,如果base中的某个cluster,在top中也存在,则从top读取时只能读取到top中的数据,base中的数据被屏蔽掉了。基于此原理,如果我们修改了base,而不希望镜像的使用者读取到该变化,可以将base中被修改前的数据拷贝到top中,这样top中的数据就会优先被读取到;drive-backup sync=none的作用便在于此。如果需要使用者读取到base的修改,则只需要清空top即可;如果需要永久保留top的数据,将top中的数据commit到base即可;colo使用该方式来缓存磁盘数据。

主要流程:

  1. SVM在secondary disk上启动nbd server,PVM通过nbd client连接SVM secondary disk,并将其作为quorum的child 1。
  2. PVM guest将数据写入virtio-blk,virtio-blk通过quorum将磁盘数据写入primary disk,同时通过nbd发送一份到SVM。
  3. 在SVM将数据写入secondary disk前,drive-backup把secondary disk中即将被修改的数据拷贝到hidden-disk中,然后再将来自PVM的数据写入secondary disk,同时缓存SVM写入磁盘的数据到active-disk中。
  4. 在checkpoint完成时丢弃active-disk&hidden-disk中的数据,则SVM会读取到base镜像secondary disk中的数据,而secondary disk与primary disk数据一致。
  5. 在PVM挂掉,SVM failover时,将active-disk&hidden-disk中的数据commit到secondary disk中,此时该checkpoint周期内PVM写入的磁盘数据被丢弃,SVM写入磁盘的数据被保留,磁盘数据平滑切换到SVM上。

COLO Proxy

上述流程大概介绍:
  虚拟机接收包:

主: tap → mirror  结果是主备虚拟机都收到请求包

备:mirror → redirector → tcp rewriter  这儿如果是tcp包会调整ack和checksum,然后发送到备虚拟机,否则直接发送到虚拟机

虚拟机发送包:

主:guest → redirector → colo-compare 然后主等待备包到来并比较,如果不同,触发同步,否则→ 另一个rediector → tap

备:guest → tcp rewriter 如果是tcp包,调整seq和checksum → rediector → primary

 

流程中涉及到的几个子模块介绍:

filter-mirror:将主虚拟机收到的请求包复制到备虚拟机;

filter-rediector:

对备虚拟机来说:1. 将请求包重定向到tcp-rewriter, 2. 将响应包重定向到主的colo compare模块;

对于主虚拟机来说:1. 将响应包重定向到colo compare模块,2. 将比较结果发送到tap设备;

tcp-rewriter:只在备上有; 目的是对备上tcp包进行重写(ack, seq, checksum)以使备能同客户端保持连接。

colo-compare:对主备的响应包进行比较,如果响应包相同,则丢弃备的包,将主包发送到客户端,否则触发同步;当前主要支持tcp, udp, icmp的比较,不支持vlan和ipv6包。 具体还有下面这些特点:

针对每个连接保存一个比较队列,包到来时会排序插入到对应的队列中以提高比较效率;

队列中的包有超时时间,以应对备包没来的情况;

当前比较是对payload的比较,跳过了header。

对于同一个tcp响应包,主备包的组成个数可能不同,这种情况有单独处理;即主的响应包是| header |1|2|3|和| header |4|5|6|7|8|9|0|,而备的响应包是| header |1|2|3|4|5|6|7|8|9|0|)。

其中filter-mirror, filter-redirector, filter-rewriter是qemu netfilter的插件,qemu netfilter则能在收发包时对包进行预处理;它们都是通过chardev对应的socket来完成的。

COLO Checkpoint

除了响应包不同会触发checkpoint以外,colo还会定期同步, 默认是20s; 可以通过命令设置,比如:'{ "execute": "migrate-set-parameters" , "arguments":{ "x-checkpoint-delay": 2000 } }'

定期checkpoint是由主虚拟机发起,交互过程如下:

3.使用

3.1 版本及编译

  qemu自v2.8开始支持colo,并不断完善,早期版本性能和稳定性均不足,建议使用开源最新版本(本次测试使用了5.2.x版本)。

  编译:
     --enable-replication   打开replication。
     --block-drv-rw-whitelist=xx,quorum,xx 如果打开了block-drv-rw-writelist,需要包含quorum。

3.2手动切换(无心跳功能)

  1. 准备工作
    计算节点x2: Pnode test-cluster-01 192.168.220.244
    Snode test-cluster-02 192.168.220.245
    虚拟机镜像:Pnode  /mnt/centos.qcow2 20G  #建议虚拟机只使用一个磁盘,size根据需要确定
    Snode  /mnt/centos.qcow2 20G  #centos.qcow2为Pnode centos.qcow2的拷贝
    tcp端口:4个空闲的tcp端口,如9000~90003
    网桥:virbr0   ifup脚本,内容如下:

#!/bin/bash

ip link set $1 up

brctl addif virbr0 $1

  1. 运行PVM

# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 4G -smp 4 \

   -chardev socket,id=mon0,path=/tmp/primary.monitor,server,nowait -mon chardev=mon0,mode=control \

   -device piix3-usb-uhci -device usb-tablet -name primary \

   -netdev tap,id=hn0,vhost=off,br=virbr0,script=./ifup,downscript=no \

   -device virtio-net-pci,id=e0,netdev=hn0,mac=52:54:00:12:34:78 \

   -chardev socket,id=mirror0,host=0.0.0.0,port=9003,server=on,wait=off \

   -chardev socket,id=compare1,host=0.0.0.0,port=9004,server=on,wait=on \

   -chardev socket,id=compare0,host=127.0.0.1,port=9001,server=on,wait=off \

   -chardev socket,id=compare0-0,host=127.0.0.1,port=9001 \

   -chardev socket,id=compare_out,host=127.0.0.1,port=9005,server=on,wait=off \

   -chardev socket,id=compare_out0,host=127.0.0.1,port=9005 \

   -object filter-mirror,id=m0,netdev=hn0,queue=tx,outdev=mirror0 \

   -object filter-redirector,netdev=hn0,id=redire0,queue=rx,indev=compare_out \

   -object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0 \

   -object iothread,id=iothread1 \

   -object colo-compare,id=comp0,primary_in=compare0-0,secondary_in=compare1,\

outdev=compare_out0,iothread=iothread1 \

   -drive if=none,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\

children.0.file.filename=/mnt/centos.qcow2,children.0.driver=qcow2 \

   -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x8,drive=colo-disk0,id=virtio-disk0,write-cache=off -S

注: tx表示是tap → guest; rx表示是guest → tap; tx和index/outdev会决定netfilter的处理方向及是否传递给下一个filter;这个对象的定义顺序也是处理的顺序,tx是正向,rx是反向;
比如-object filter-mirror,id=m0,netdev=hn0,queue=tx,outdev=mirror0 说明这个mirror对象是处理从tap到guest的包,处理后会从mirror这个socket出去,之后不再传给下一个filter;
-object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0表示这个是从虚拟机出来的包,处理后传到compare0这个socket,即colo-compare,之后不传递给下一个filter;
compare后包会到compare_out0,最终到compare_out,即:-object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0,这个处理后下一个filter是-object filter-redirector,netdev=hn0,id=redire0,queue=rx,indev=compare_out,但由于它是tx,所以直接跳过,就到了tap

  1. 运行SVM

# qemu-img create -f qcow2 /mnt/secondary-active.qcow2 20G     #在Snode上创建secondary-active镜像,size必须与centos.qcow2一致

# qemu-img create -f qcow2 /mnt/secondary-hidden.qcow2 20G   #在Snode上创建secondary-hidden镜像,size必须与centos.qcow2一致

# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 4G -smp 4 \

   -chardev socket,id=mon0,path=/tmp/secondary.monitor,server,nowait -mon chardev=mon0,mode=control \

   -device piix3-usb-uhci -device usb-tablet -name secondary \

   -netdev tap,id=hn0,vhost=off,br=virbr0,script=./ifup,downscript=no \

   -device virtio-net-pci,id=e0,netdev=hn0,mac=52:54:00:12:34:78 \

   -chardev socket,id=red0,host=192.168.220.244,port=9003,reconnect=1 \

   -chardev socket,id=red1,host=192.168.220.244,port=9004,reconnect=1 \

   -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \

   -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \

   -object filter-rewriter,id=rew0,netdev=hn0,queue=all \

   -drive if=none,id=parent0,file.filename=/mnt/centos.qcow2,driver=qcow2 \

   -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\

top-id=colo-disk0,file.file.filename=/mnt/secondary-active.qcow2,\

file.backing.driver=qcow2,file.backing.file.filename=/mnt/secondary-hidden.qcow2,\

file.backing.backing=parent0 \

   -drive if=none,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\

children.0=childs0 \

   -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x8,drive=colo-disk0,id=virtio-disk0,write-cache=off \

   -incoming tcp:0.0.0.0:9001

  1. 启动COLO

SVM:

{'execute':'qmp_capabilities'}

{'execute': 'nbd-server-start', 'arguments': {'addr': {'type': 'inet', 'data': {'host': '0.0.0.0', 'port': '9000'} } } }

{'execute': 'nbd-server-add', 'arguments': {'device': 'parent0', 'writable': true } }

PVM:

{'execute':'qmp_capabilities'}

{'execute': 'human-monitor-command', 'arguments': {'command-line': 'drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=192.168.220.245,file.port=9000,file.export=parent0,node-name=replication0'}}

{'execute': 'x-blockdev-change', 'arguments':{'parent': 'colo-disk0', 'node': 'replication0' } }

{'execute': 'migrate-set-capabilities', 'arguments': {'capabilities': [ {'capability': 'x-colo', 'state': true } ] } }

{'execute': 'migrate', 'arguments': {'uri': 'tcp:192.168.220.245:9001' } }

  1. failover
    A. PVM failover(SVM died)

{'execute': 'x-blockdev-change', 'arguments':{ 'parent': 'colo-disk0', 'child': 'children.1'} }

{'execute': 'human-monitor-command', 'arguments':{ 'command-line': 'drive_del replication0' } }

{'execute': 'object-del', 'arguments':{ 'id': 'comp0' } }

{'execute': 'object-del', 'arguments':{ 'id': 'iothread1' } }

{'execute': 'object-del', 'arguments':{ 'id': 'm0' } }

{'execute': 'object-del', 'arguments':{ 'id': 'redire0' } }

{'execute': 'object-del', 'arguments':{ 'id': 'redire1' } }

{'execute': 'x-colo-lost-heartbeat' }


B.SVM failover(PVM died)

{'execute': 'nbd-server-stop'}

{'execute': 'x-colo-lost-heartbeat'}

{'execute': 'object-del', 'arguments':{ 'id': 'f2' } }

{'execute': 'object-del', 'arguments':{ 'id': 'f1' } }

{'execute': 'chardev-remove', 'arguments':{ 'id': 'red1' } }

{'execute': 'chardev-remove', 'arguments':{ 'id': 'red0' } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'mirror0', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '0.0.0.0', 'port': '9003' } }, 'server': true } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare1', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '0.0.0.0', 'port': '9004' } }, 'server': true } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare0', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '127.0.0.1', 'port': '9001' } }, 'server': true } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare0-0', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '127.0.0.1', 'port': '9001' } }, 'server': false } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare_out', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '127.0.0.1', 'port': '9005' } }, 'server': true } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare_out0', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '127.0.0.1', 'port': '9005' } }, 'server': false } } } }

恢复
A.SVM died
run new SVM:

# qemu-img create -f qcow2 /mnt/secondary-active.qcow2 20G

# qemu-img create -f qcow2 /mnt/secondary-hidden.qcow2 20G

# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 4G -smp 4 \

   -chardev socket,id=mon0,path=/tmp/secondary.monitor,server,nowait -mon chardev=mon0,mode=control \

   -device piix3-usb-uhci -device usb-tablet -name secondary \

   -netdev tap,id=hn0,vhost=off,br=virbr0,script=./ifup,downscript=no \

   -device virtio-net-pci,id=e0,netdev=hn0,mac=52:54:00:12:34:78 \

   -chardev socket,id=red0,host=192.168.220.244,port=9003,reconnect=1 \

   -chardev socket,id=red1,host=192.168.220.244,port=9004,reconnect=1 \

   -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \

   -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \

   -object filter-rewriter,id=rew0,netdev=hn0,queue=all \

   -drive if=none,id=parent0,file.filename=/mnt/centos.qcow2,driver=qcow2 \

   -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\

top-id=colo-disk0,file.file.filename=/mnt/secondary-active.qcow2,\

file.backing.driver=qcow2,file.backing.file.filename=/mnt/secondary-hidden.qcow2,\

file.backing.backing=parent0 \

   -drive if=none,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\

children.0=childs0 \

   -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x8,drive=colo-disk0,id=virtio-disk0,write-cache=off \

   -incoming tcp:0.0.0.0:9001

new SVM:

{'execute':'qmp_capabilities'}

{'execute': 'nbd-server-start', 'arguments': {'addr': {'type': 'inet', 'data': {'host': '0.0.0.0', 'port': '9000'} } } }

{'execute': 'nbd-server-add', 'arguments': {'device': 'parent0', 'writable': true } }

PVM:

{'execute': 'drive-mirror', 'arguments':{ 'device': 'colo-disk0', 'job-id': 'resync', 'target': 'nbd://192.168.220.245:9000/parent0', 'mode': 'existing', 'format': 'raw', 'sync': 'full'} }

Wait until disk is synced, then:

{'execute': 'stop'}

{'execute': 'block-job-cancel', 'arguments':{ 'device': 'resync'} }

{'execute': 'human-monitor-command', 'arguments':{ 'command-line': 'drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=192.168.220.245,file.port=9000,file.export=parent0,node-name=replication0'}}

{'execute': 'x-blockdev-change', 'arguments':{ 'parent': 'colo-disk0', 'node': 'replication0' } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-mirror', 'id': 'm0', 'props': { 'netdev': 'hn0', 'queue': 'tx', 'outdev': 'mirror0' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-redirector', 'id': 'redire0', 'props': { 'netdev': 'hn0', 'queue': 'rx', 'indev': 'compare_out' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-redirector', 'id': 'redire1', 'props': { 'netdev': 'hn0', 'queue': 'rx', 'outdev': 'compare0' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'iothread', 'id': 'iothread1' } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'colo-compare', 'id': 'comp0', 'props': { 'primary_in': 'compare0-0', 'secondary_in': 'compare1', 'outdev': 'compare_out0', 'iothread': 'iothread1' } } }

{'execute': 'migrate-set-capabilities', 'arguments':{ 'capabilities': [ {'capability': 'x-colo', 'state': true } ] } }

{'execute': 'migrate', 'arguments':{ 'uri': 'tcp:192.168.220.245:9001' } }

B.PVM died

run new SVM:

# qemu-img create -f qcow2 /mnt/secondary-active.qcow2 20G

# qemu-img create -f qcow2 /mnt/secondary-hidden.qcow2 20G

# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 4G -smp 4 \

   -chardev socket,id=mon0,path=/tmp/secondary.monitor,server,nowait -mon chardev=mon0,mode=control \

   -device piix3-usb-uhci -device usb-tablet -name secondary \

   -netdev tap,id=hn0,vhost=off,br=virbr0,script=./ifup,downscript=no \

   -device virtio-net-pci,id=e0,netdev=hn0,mac=52:54:00:12:34:78 \

   -chardev socket,id=red0,host=192.168.220.245,port=9003,reconnect=1 \

   -chardev socket,id=red1,host=192.168.220.245,port=9004,reconnect=1 \

   -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \

   -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \

   -object filter-rewriter,id=rew0,netdev=hn0,queue=all \

   -drive if=none,id=parent0,file.filename=/mnt/centos.qcow2,driver=qcow2 \

   -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\

top-id=colo-disk0,file.file.filename=/mnt/secondary-active.qcow2,\

file.backing.driver=qcow2,file.backing.file.filename=/mnt/secondary-hidden.qcow2,\

file.backing.backing=parent0 \

   -drive if=none,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\

children.0=childs0 \

   -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x8,drive=colo-disk0,id=virtio-disk0,write-cache=off \

   -incoming tcp:0.0.0.0:9001

new SVM:

{'execute':'qmp_capabilities'}

{'execute': 'nbd-server-start', 'arguments': {'addr': {'type': 'inet', 'data': {'host': '0.0.0.0', 'port': '9000'} } } }

{'execute': 'nbd-server-add', 'arguments': {'device': 'parent0', 'writable': true } }

old SVM(new PVM):

{'execute': 'drive-mirror', 'arguments':{ 'device': 'colo-disk0', 'job-id': 'resync', 'target': 'nbd://192.168.220.244:9000/parent0', 'mode': 'existing', 'format': 'raw', 'sync': 'full'} }

Wait until disk is synced, then:

{'execute': 'stop'}

{'execute': 'block-job-cancel', 'arguments':{ 'device': 'resync' } }

{'execute': 'human-monitor-command', 'arguments':{ 'command-line': 'drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=127.0.0.1,file.port=9999,file.export=parent0,node-name=replication0'}}

{'execute': 'x-blockdev-change', 'arguments':{ 'parent': 'colo-disk0', 'node': 'replication0' } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-mirror', 'id': 'm0', 'props': { 'insert': 'before', 'position': 'id=rew0', 'netdev': 'hn0', 'queue': 'tx', 'outdev': 'mirror0' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-redirector', 'id': 'redire0', 'props': { 'insert': 'before', 'position': 'id=rew0', 'netdev': 'hn0', 'queue': 'rx', 'indev': 'compare_out' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-redirector', 'id': 'redire1', 'props': { 'insert': 'before', 'position': 'id=rew0', 'netdev': 'hn0', 'queue': 'rx', 'outdev': 'compare0' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'iothread', 'id': 'iothread1' } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'colo-compare', 'id': 'comp0', 'props': { 'primary_in': 'compare0-0', 'secondary_in': 'compare1', 'outdev': 'compare_out0', 'iothread': 'iothread1' } } }

{'execute': 'migrate-set-capabilities', 'arguments':{ 'capabilities': [ {'capability': 'x-colo', 'state': true } ] } }

{'execute': 'migrate', 'arguments':{ 'uri': 'tcp:192.168.220.244:9001' } }

4.性能

4.1网络性能

总体来说,virtio的情况下不同的协议影响比较大:

下载:scp基本有非colo的80%以上, http只有一半左右,ftp则只有20%左右

上传:scp和ftp在50%以下;

udp:在客户端1Mbits/s的发送带宽下,基本不会丢包,但延迟抖动相比非colo要大些,在20-40倍左右; 比如colo的jitter是0.205ms, 非colo在0.008ms

ping:延迟大50%左右

4.2磁盘性能

磁盘读性能几乎不受影响,rbd磁盘写性能约为单机的30%~40%,本地磁盘写性能约为单机的15%~30%。

4.3 官方测试结果(同非colo相比)

总带宽和并发连接数

基本无影响

sysbench cpu和内存

性能基本接近

内核编译

80% (原因是读写多,网络包少)

FTP

get:97%; PUT: 50%

web服务器:apache(服务端) + web bench(客户端)

不同并发请求数:主要开销是脏页传输
1  - 100%
4  - 99%
16 - 89%
64 - 63%
256 - 42%

PostgreSQL

82.4% - 85.5%, 同web服务器一样,主要也是脏页的开销

客户机多cpu

基本无影响

5.受限功能

  1. vhost-net和直通网卡
    状态:不支持
    原因:colo proxy需要截获虚拟网卡的收发报文(以便转发或比较),但colo proxy实现在用户态,vhost-net实现在内核态,数据不经过用户态,用户态proxy无法截获内核态vhost的数据包,因此无法支持vhost。
    解决方案:
  2. 热拔插
    状态:不支持
    原因:colo本质上是热迁移,热迁移过程中不允许热插拔。
    解决方案:
  3. 快照
    状态:不支持
    原因:快照本身对虚拟机本身没有影响,但快照不会在主备间同步。

6.建议的场景

从实际测试来看,使用colo后性能还是有一定的下降,failover后可能出现业务无法自动恢复的情况;且colo有额外的开销,比如colo本身占用更多的内存,磁盘,迁移时的cpu,网络等开销,因此建议在部署了colo的主机上不要部署太多的其他虚拟机;基于这些,建议在没有其他高可用方案或者代价较高的情况下使用colo,也不建议作为长期方案使用;建议在使用时间比较短,要求业务恢复比较快的情况下使用colo。

参考资料:

【1】https://wiki.qemu.org/Features/COLO  

【2】https://wiki.qemu.org/Features/BlockReplication  

【3】https://wiki.qemu.org/Features/COLO/Managed_HOWTO 

【4】https://wiki.qemu.org/Features/COLO/Manual_HOWTO 

【5】https://wiki.qemu.org/Features/FaultTolerance  

【6】http://www.linux-kvm.org/images/1/1d/Kvm-forum-2013-COLO.pdf 

【7】http://www.linux-kvm.org/images/0/01/01x07-Hongyang_Yang-Status_update_on_KVM-COLO.pdf 

【8】http://www.linux-kvm.org/images/a/af/03x08B-Hailang_Zhang-Status_Update_on_KVM-COLO_FT.pdf  

【9】https://www.linux-kvm.org/images/0/0d/0.5.kemari-kvm-forum-2010.pdf 

【10】https://www.ccf.org.cn/ccf/contentcore/resource/download?ID=48660 

【11】a3-dong.pdf 

【12】https://git.qemu.org/?p=qemu.git;a=blob;f=docs/COLO-FT.txt 

0条评论
0 / 1000
袁****浩
3文章数
0粉丝数
袁****浩
3 文章 | 0 粉丝
袁****浩
3文章数
0粉丝数
袁****浩
3 文章 | 0 粉丝
原创

COLO介绍

2023-03-21 03:01:20
467
0

关键词

COLO、高可用、FT、PVM、SVM、Failover

相关术语

PVM

Primary VM, 主虚拟机

SVM

Secondary VM, 备虚拟机

FT

Fault Tolerance, 容错机制

COLO

COarse-grained LOck-stepping Virtual Machines for Non-stop Service, 一种基于客户端/服务器系统的粗粒度锁步虚拟机复制方法,实现不间断服务

lock-step

锁步同步虚拟机状态

checkpoint

增加检查点用于虚拟机状态周期性同步

failover

故障切换

Pnode

Primary Node, 主计算节点

Snode

Secondary Node, 备计算节点

 

1.简介

    云计算时代,越来越多的关键应用被部署到云端,虚拟机的可靠性成为衡量云平台的一个重要指标,但现实情况是软硬件故障不可能完全避免,因此容错机制(Fault Tolerance)被引入。
对于虚拟机容错,我们通常理解为:通过创建和维护与主虚拟机相同,并且可在主虚拟机发生故障时立即切换并替换主虚拟机的辅助虚拟机,来确保虚拟机的连续可用性,简单的说就是为虚拟机创建一个完全相同的可替换副本。

虚拟机容错的主要实现技术是虚拟机复制(virtual machine replication), 传统的方案有:锁步复制(Lock-step)和周期性状态复制(checkpoint)。

锁步复制(Lock-step):通过虚拟机锁步技术,实现在每一条指令边界的虚拟机状态复制。对于确定性指令(deterministic instructions),主虚拟机(PVM)和备虚拟机(SVM)可以在没有VMM介入的情况下并行执行。而对于不确定性指令(non-deterministic instructions),通过锁步技术,由虚拟机监视器模拟执行,确保(在指令边界)获得相同的执行结果。这种方法在多处理器情况下遭受非常大的锁步开销,因为多处理器环境下的内存访问结果是不确定的。因此,这种解决方式效率低下。

周期性状态复制(checkpoint):在每一个周期的边界,设置虚拟机状态同步检查点(checkpoint)进行同步。也就是说,周期性的将 PVM 的状态复制到 SVM 上。周期性的虚拟机状态复制方法在一个周期内将输出数据包缓存起来,直到下一个检查点进行同步的时候才发送出去,以便SVM 能在硬件出错的时候成功接管。但是,受限于传输缓存带来的额外的网络延迟和频繁的虚拟机状态同步的开销,周期性的虚拟机状态复制方法的性能一直存在瓶颈,也没有得到广泛使用。

虚拟机锁步和周期性状态复制方法实现了虚拟机状态的完全复制,但其缺点也很明显(开销大、性能差、延迟高等)。对于某些应用场景如C/S系统来说,客户端并不关心服务器内部的某些寄存器值,只关心服务器对请求的响应,只要保证PVM和SVM对客户端请求的响应一致,那就可以认为SVM可以替换PVM,而不需要苛求SVM和PVM完全一致。基于此原理,上海交大和Intel等提出了COLO方案,基于客户端/服务器系统的粗粒度锁步虚拟机复制方法,实现不间断服务。注:粗粒度是相对于细粒度(fine-grained)的; 举个例子,粗粒度是识别出猫和狗,细粒度还需要识别出狗的种类:吉娃娃、萨摩耶还是哈巴狗; 对于c/s系统来说,粗粒度可以进一步为只关心响应包的负载,而不关心比如时间戳等头部信息。

2.原理及架构

COLO (COarse-grained LOck-stepping)是一种高可靠性解决方案,COLO 架构如下图。它由两个网络互通的物理节点组成:Pnode主节点和Snode备节点,主节点运行主虚拟机(PVM),备节点运行备虚拟机(SVM)并作为 PVM 的备份。PVM 和 SVM 独立并行运行,PVM接收到来自Client或外部网络的数据包,然后转发一份给SVM,PVM和SVM根据该请求各自产生响应数据包。PVM截获SVM的响应数据包并与自己的对比,如果相同,则直接发送响应数据包给Client,如果不同,则表明SVM已经开始不能替换PVM,需要将PVM同步到SVM,同步完成后再将PVM的响应数据包发送给Client。

COLO主要由以下4个重要组件构成:
COLO framework:COLO框架,实现连续迁移功能(保证SVM和PVM状态一致)和故障切换控制。
COLO Proxy:负责将Client请求发送给PVM和SVM,然后接收两者的响应并对比,根据结果确定是否需要进行同步。
COLO Block Replication:磁盘复制功能,当PVM把数据写入磁盘时,需要同时把数据发送到SVM,以便保证SVM和PVM磁盘数据一致;SVM侧则需要将PVM的磁盘数据缓存起来,在checkpoint或failover时合并数据。
HeartBeat:心跳功能,在Pnode和Snode上运行,周期性检查系统可用性,如果Pnode或PVM出现故障,则Snode会触发failover,以便SVM接替PVM提供服务。但目前qemu colo心跳功能仍待开发,因此需要调用x-colo-lost-heartbeat手动触发。

COLO Block Replication

  • primary disk:primary vm disk
  • secondary disk:secondary vm disk,size必须和primary disk相同,一般是primary disk的拷贝。
  • hiddent-disk:用于缓存Secondary disk中被nbd client修改前的数据,必须是qcow2格式,size必须与primary disk相同。
  • active-disk:用于缓存SVM写入磁盘的数据,必须是qcow2格式,size必须与primary disk相同。
  • quorum:一种分布式投票机制,colo中用于处理PVM 2镜像副本(本机镜像和nbd镜像)的读写,其读写的副本以child的形式存在;写数据时会写入所有child,如果一个child写入成功则认为写操作成功;读数据时优先读取第一个child(即本机镜像),失败后尝试后续child。PVM和SVM均有使用quorum driver,PVM上需要处理本机镜像和nbd镜像的读写,SVM上则没有是实际作用,主要是因为当SVM替换PVM后需要quorum。
  • replication:用于控制镜像复制,分为primary和secondary模式,PVM上使用primary模式,SVM上使用secondary模式,其主要逻辑在于secondary模式,secondary模式下会缓存PVM和SVM各自写入磁盘的数据,在checkpoint完成时丢弃SVM写入磁盘的数据,在SVM failover时合入SVM的数据。
  • 对于二级镜像base <--- top,如果base中的某个cluster,在top中也存在,则从top读取时只能读取到top中的数据,base中的数据被屏蔽掉了。基于此原理,如果我们修改了base,而不希望镜像的使用者读取到该变化,可以将base中被修改前的数据拷贝到top中,这样top中的数据就会优先被读取到;drive-backup sync=none的作用便在于此。如果需要使用者读取到base的修改,则只需要清空top即可;如果需要永久保留top的数据,将top中的数据commit到base即可;colo使用该方式来缓存磁盘数据。

主要流程:

  1. SVM在secondary disk上启动nbd server,PVM通过nbd client连接SVM secondary disk,并将其作为quorum的child 1。
  2. PVM guest将数据写入virtio-blk,virtio-blk通过quorum将磁盘数据写入primary disk,同时通过nbd发送一份到SVM。
  3. 在SVM将数据写入secondary disk前,drive-backup把secondary disk中即将被修改的数据拷贝到hidden-disk中,然后再将来自PVM的数据写入secondary disk,同时缓存SVM写入磁盘的数据到active-disk中。
  4. 在checkpoint完成时丢弃active-disk&hidden-disk中的数据,则SVM会读取到base镜像secondary disk中的数据,而secondary disk与primary disk数据一致。
  5. 在PVM挂掉,SVM failover时,将active-disk&hidden-disk中的数据commit到secondary disk中,此时该checkpoint周期内PVM写入的磁盘数据被丢弃,SVM写入磁盘的数据被保留,磁盘数据平滑切换到SVM上。

COLO Proxy

上述流程大概介绍:
  虚拟机接收包:

主: tap → mirror  结果是主备虚拟机都收到请求包

备:mirror → redirector → tcp rewriter  这儿如果是tcp包会调整ack和checksum,然后发送到备虚拟机,否则直接发送到虚拟机

虚拟机发送包:

主:guest → redirector → colo-compare 然后主等待备包到来并比较,如果不同,触发同步,否则→ 另一个rediector → tap

备:guest → tcp rewriter 如果是tcp包,调整seq和checksum → rediector → primary

 

流程中涉及到的几个子模块介绍:

filter-mirror:将主虚拟机收到的请求包复制到备虚拟机;

filter-rediector:

对备虚拟机来说:1. 将请求包重定向到tcp-rewriter, 2. 将响应包重定向到主的colo compare模块;

对于主虚拟机来说:1. 将响应包重定向到colo compare模块,2. 将比较结果发送到tap设备;

tcp-rewriter:只在备上有; 目的是对备上tcp包进行重写(ack, seq, checksum)以使备能同客户端保持连接。

colo-compare:对主备的响应包进行比较,如果响应包相同,则丢弃备的包,将主包发送到客户端,否则触发同步;当前主要支持tcp, udp, icmp的比较,不支持vlan和ipv6包。 具体还有下面这些特点:

针对每个连接保存一个比较队列,包到来时会排序插入到对应的队列中以提高比较效率;

队列中的包有超时时间,以应对备包没来的情况;

当前比较是对payload的比较,跳过了header。

对于同一个tcp响应包,主备包的组成个数可能不同,这种情况有单独处理;即主的响应包是| header |1|2|3|和| header |4|5|6|7|8|9|0|,而备的响应包是| header |1|2|3|4|5|6|7|8|9|0|)。

其中filter-mirror, filter-redirector, filter-rewriter是qemu netfilter的插件,qemu netfilter则能在收发包时对包进行预处理;它们都是通过chardev对应的socket来完成的。

COLO Checkpoint

除了响应包不同会触发checkpoint以外,colo还会定期同步, 默认是20s; 可以通过命令设置,比如:'{ "execute": "migrate-set-parameters" , "arguments":{ "x-checkpoint-delay": 2000 } }'

定期checkpoint是由主虚拟机发起,交互过程如下:

3.使用

3.1 版本及编译

  qemu自v2.8开始支持colo,并不断完善,早期版本性能和稳定性均不足,建议使用开源最新版本(本次测试使用了5.2.x版本)。

  编译:
     --enable-replication   打开replication。
     --block-drv-rw-whitelist=xx,quorum,xx 如果打开了block-drv-rw-writelist,需要包含quorum。

3.2手动切换(无心跳功能)

  1. 准备工作
    计算节点x2: Pnode test-cluster-01 192.168.220.244
    Snode test-cluster-02 192.168.220.245
    虚拟机镜像:Pnode  /mnt/centos.qcow2 20G  #建议虚拟机只使用一个磁盘,size根据需要确定
    Snode  /mnt/centos.qcow2 20G  #centos.qcow2为Pnode centos.qcow2的拷贝
    tcp端口:4个空闲的tcp端口,如9000~90003
    网桥:virbr0   ifup脚本,内容如下:

#!/bin/bash

ip link set $1 up

brctl addif virbr0 $1

  1. 运行PVM

# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 4G -smp 4 \

   -chardev socket,id=mon0,path=/tmp/primary.monitor,server,nowait -mon chardev=mon0,mode=control \

   -device piix3-usb-uhci -device usb-tablet -name primary \

   -netdev tap,id=hn0,vhost=off,br=virbr0,script=./ifup,downscript=no \

   -device virtio-net-pci,id=e0,netdev=hn0,mac=52:54:00:12:34:78 \

   -chardev socket,id=mirror0,host=0.0.0.0,port=9003,server=on,wait=off \

   -chardev socket,id=compare1,host=0.0.0.0,port=9004,server=on,wait=on \

   -chardev socket,id=compare0,host=127.0.0.1,port=9001,server=on,wait=off \

   -chardev socket,id=compare0-0,host=127.0.0.1,port=9001 \

   -chardev socket,id=compare_out,host=127.0.0.1,port=9005,server=on,wait=off \

   -chardev socket,id=compare_out0,host=127.0.0.1,port=9005 \

   -object filter-mirror,id=m0,netdev=hn0,queue=tx,outdev=mirror0 \

   -object filter-redirector,netdev=hn0,id=redire0,queue=rx,indev=compare_out \

   -object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0 \

   -object iothread,id=iothread1 \

   -object colo-compare,id=comp0,primary_in=compare0-0,secondary_in=compare1,\

outdev=compare_out0,iothread=iothread1 \

   -drive if=none,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\

children.0.file.filename=/mnt/centos.qcow2,children.0.driver=qcow2 \

   -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x8,drive=colo-disk0,id=virtio-disk0,write-cache=off -S

注: tx表示是tap → guest; rx表示是guest → tap; tx和index/outdev会决定netfilter的处理方向及是否传递给下一个filter;这个对象的定义顺序也是处理的顺序,tx是正向,rx是反向;
比如-object filter-mirror,id=m0,netdev=hn0,queue=tx,outdev=mirror0 说明这个mirror对象是处理从tap到guest的包,处理后会从mirror这个socket出去,之后不再传给下一个filter;
-object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0表示这个是从虚拟机出来的包,处理后传到compare0这个socket,即colo-compare,之后不传递给下一个filter;
compare后包会到compare_out0,最终到compare_out,即:-object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0,这个处理后下一个filter是-object filter-redirector,netdev=hn0,id=redire0,queue=rx,indev=compare_out,但由于它是tx,所以直接跳过,就到了tap

  1. 运行SVM

# qemu-img create -f qcow2 /mnt/secondary-active.qcow2 20G     #在Snode上创建secondary-active镜像,size必须与centos.qcow2一致

# qemu-img create -f qcow2 /mnt/secondary-hidden.qcow2 20G   #在Snode上创建secondary-hidden镜像,size必须与centos.qcow2一致

# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 4G -smp 4 \

   -chardev socket,id=mon0,path=/tmp/secondary.monitor,server,nowait -mon chardev=mon0,mode=control \

   -device piix3-usb-uhci -device usb-tablet -name secondary \

   -netdev tap,id=hn0,vhost=off,br=virbr0,script=./ifup,downscript=no \

   -device virtio-net-pci,id=e0,netdev=hn0,mac=52:54:00:12:34:78 \

   -chardev socket,id=red0,host=192.168.220.244,port=9003,reconnect=1 \

   -chardev socket,id=red1,host=192.168.220.244,port=9004,reconnect=1 \

   -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \

   -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \

   -object filter-rewriter,id=rew0,netdev=hn0,queue=all \

   -drive if=none,id=parent0,file.filename=/mnt/centos.qcow2,driver=qcow2 \

   -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\

top-id=colo-disk0,file.file.filename=/mnt/secondary-active.qcow2,\

file.backing.driver=qcow2,file.backing.file.filename=/mnt/secondary-hidden.qcow2,\

file.backing.backing=parent0 \

   -drive if=none,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\

children.0=childs0 \

   -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x8,drive=colo-disk0,id=virtio-disk0,write-cache=off \

   -incoming tcp:0.0.0.0:9001

  1. 启动COLO

SVM:

{'execute':'qmp_capabilities'}

{'execute': 'nbd-server-start', 'arguments': {'addr': {'type': 'inet', 'data': {'host': '0.0.0.0', 'port': '9000'} } } }

{'execute': 'nbd-server-add', 'arguments': {'device': 'parent0', 'writable': true } }

PVM:

{'execute':'qmp_capabilities'}

{'execute': 'human-monitor-command', 'arguments': {'command-line': 'drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=192.168.220.245,file.port=9000,file.export=parent0,node-name=replication0'}}

{'execute': 'x-blockdev-change', 'arguments':{'parent': 'colo-disk0', 'node': 'replication0' } }

{'execute': 'migrate-set-capabilities', 'arguments': {'capabilities': [ {'capability': 'x-colo', 'state': true } ] } }

{'execute': 'migrate', 'arguments': {'uri': 'tcp:192.168.220.245:9001' } }

  1. failover
    A. PVM failover(SVM died)

{'execute': 'x-blockdev-change', 'arguments':{ 'parent': 'colo-disk0', 'child': 'children.1'} }

{'execute': 'human-monitor-command', 'arguments':{ 'command-line': 'drive_del replication0' } }

{'execute': 'object-del', 'arguments':{ 'id': 'comp0' } }

{'execute': 'object-del', 'arguments':{ 'id': 'iothread1' } }

{'execute': 'object-del', 'arguments':{ 'id': 'm0' } }

{'execute': 'object-del', 'arguments':{ 'id': 'redire0' } }

{'execute': 'object-del', 'arguments':{ 'id': 'redire1' } }

{'execute': 'x-colo-lost-heartbeat' }


B.SVM failover(PVM died)

{'execute': 'nbd-server-stop'}

{'execute': 'x-colo-lost-heartbeat'}

{'execute': 'object-del', 'arguments':{ 'id': 'f2' } }

{'execute': 'object-del', 'arguments':{ 'id': 'f1' } }

{'execute': 'chardev-remove', 'arguments':{ 'id': 'red1' } }

{'execute': 'chardev-remove', 'arguments':{ 'id': 'red0' } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'mirror0', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '0.0.0.0', 'port': '9003' } }, 'server': true } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare1', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '0.0.0.0', 'port': '9004' } }, 'server': true } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare0', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '127.0.0.1', 'port': '9001' } }, 'server': true } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare0-0', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '127.0.0.1', 'port': '9001' } }, 'server': false } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare_out', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '127.0.0.1', 'port': '9005' } }, 'server': true } } } }

{'execute': 'chardev-add', 'arguments':{ 'id': 'compare_out0', 'backend': {'type': 'socket', 'data': {'addr': { 'type': 'inet', 'data': { 'host': '127.0.0.1', 'port': '9005' } }, 'server': false } } } }

恢复
A.SVM died
run new SVM:

# qemu-img create -f qcow2 /mnt/secondary-active.qcow2 20G

# qemu-img create -f qcow2 /mnt/secondary-hidden.qcow2 20G

# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 4G -smp 4 \

   -chardev socket,id=mon0,path=/tmp/secondary.monitor,server,nowait -mon chardev=mon0,mode=control \

   -device piix3-usb-uhci -device usb-tablet -name secondary \

   -netdev tap,id=hn0,vhost=off,br=virbr0,script=./ifup,downscript=no \

   -device virtio-net-pci,id=e0,netdev=hn0,mac=52:54:00:12:34:78 \

   -chardev socket,id=red0,host=192.168.220.244,port=9003,reconnect=1 \

   -chardev socket,id=red1,host=192.168.220.244,port=9004,reconnect=1 \

   -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \

   -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \

   -object filter-rewriter,id=rew0,netdev=hn0,queue=all \

   -drive if=none,id=parent0,file.filename=/mnt/centos.qcow2,driver=qcow2 \

   -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\

top-id=colo-disk0,file.file.filename=/mnt/secondary-active.qcow2,\

file.backing.driver=qcow2,file.backing.file.filename=/mnt/secondary-hidden.qcow2,\

file.backing.backing=parent0 \

   -drive if=none,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\

children.0=childs0 \

   -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x8,drive=colo-disk0,id=virtio-disk0,write-cache=off \

   -incoming tcp:0.0.0.0:9001

new SVM:

{'execute':'qmp_capabilities'}

{'execute': 'nbd-server-start', 'arguments': {'addr': {'type': 'inet', 'data': {'host': '0.0.0.0', 'port': '9000'} } } }

{'execute': 'nbd-server-add', 'arguments': {'device': 'parent0', 'writable': true } }

PVM:

{'execute': 'drive-mirror', 'arguments':{ 'device': 'colo-disk0', 'job-id': 'resync', 'target': 'nbd://192.168.220.245:9000/parent0', 'mode': 'existing', 'format': 'raw', 'sync': 'full'} }

Wait until disk is synced, then:

{'execute': 'stop'}

{'execute': 'block-job-cancel', 'arguments':{ 'device': 'resync'} }

{'execute': 'human-monitor-command', 'arguments':{ 'command-line': 'drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=192.168.220.245,file.port=9000,file.export=parent0,node-name=replication0'}}

{'execute': 'x-blockdev-change', 'arguments':{ 'parent': 'colo-disk0', 'node': 'replication0' } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-mirror', 'id': 'm0', 'props': { 'netdev': 'hn0', 'queue': 'tx', 'outdev': 'mirror0' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-redirector', 'id': 'redire0', 'props': { 'netdev': 'hn0', 'queue': 'rx', 'indev': 'compare_out' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-redirector', 'id': 'redire1', 'props': { 'netdev': 'hn0', 'queue': 'rx', 'outdev': 'compare0' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'iothread', 'id': 'iothread1' } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'colo-compare', 'id': 'comp0', 'props': { 'primary_in': 'compare0-0', 'secondary_in': 'compare1', 'outdev': 'compare_out0', 'iothread': 'iothread1' } } }

{'execute': 'migrate-set-capabilities', 'arguments':{ 'capabilities': [ {'capability': 'x-colo', 'state': true } ] } }

{'execute': 'migrate', 'arguments':{ 'uri': 'tcp:192.168.220.245:9001' } }

B.PVM died

run new SVM:

# qemu-img create -f qcow2 /mnt/secondary-active.qcow2 20G

# qemu-img create -f qcow2 /mnt/secondary-hidden.qcow2 20G

# qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 4G -smp 4 \

   -chardev socket,id=mon0,path=/tmp/secondary.monitor,server,nowait -mon chardev=mon0,mode=control \

   -device piix3-usb-uhci -device usb-tablet -name secondary \

   -netdev tap,id=hn0,vhost=off,br=virbr0,script=./ifup,downscript=no \

   -device virtio-net-pci,id=e0,netdev=hn0,mac=52:54:00:12:34:78 \

   -chardev socket,id=red0,host=192.168.220.245,port=9003,reconnect=1 \

   -chardev socket,id=red1,host=192.168.220.245,port=9004,reconnect=1 \

   -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \

   -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \

   -object filter-rewriter,id=rew0,netdev=hn0,queue=all \

   -drive if=none,id=parent0,file.filename=/mnt/centos.qcow2,driver=qcow2 \

   -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\

top-id=colo-disk0,file.file.filename=/mnt/secondary-active.qcow2,\

file.backing.driver=qcow2,file.backing.file.filename=/mnt/secondary-hidden.qcow2,\

file.backing.backing=parent0 \

   -drive if=none,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\

children.0=childs0 \

   -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x8,drive=colo-disk0,id=virtio-disk0,write-cache=off \

   -incoming tcp:0.0.0.0:9001

new SVM:

{'execute':'qmp_capabilities'}

{'execute': 'nbd-server-start', 'arguments': {'addr': {'type': 'inet', 'data': {'host': '0.0.0.0', 'port': '9000'} } } }

{'execute': 'nbd-server-add', 'arguments': {'device': 'parent0', 'writable': true } }

old SVM(new PVM):

{'execute': 'drive-mirror', 'arguments':{ 'device': 'colo-disk0', 'job-id': 'resync', 'target': 'nbd://192.168.220.244:9000/parent0', 'mode': 'existing', 'format': 'raw', 'sync': 'full'} }

Wait until disk is synced, then:

{'execute': 'stop'}

{'execute': 'block-job-cancel', 'arguments':{ 'device': 'resync' } }

{'execute': 'human-monitor-command', 'arguments':{ 'command-line': 'drive_add -n buddy driver=replication,mode=primary,file.driver=nbd,file.host=127.0.0.1,file.port=9999,file.export=parent0,node-name=replication0'}}

{'execute': 'x-blockdev-change', 'arguments':{ 'parent': 'colo-disk0', 'node': 'replication0' } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-mirror', 'id': 'm0', 'props': { 'insert': 'before', 'position': 'id=rew0', 'netdev': 'hn0', 'queue': 'tx', 'outdev': 'mirror0' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-redirector', 'id': 'redire0', 'props': { 'insert': 'before', 'position': 'id=rew0', 'netdev': 'hn0', 'queue': 'rx', 'indev': 'compare_out' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'filter-redirector', 'id': 'redire1', 'props': { 'insert': 'before', 'position': 'id=rew0', 'netdev': 'hn0', 'queue': 'rx', 'outdev': 'compare0' } } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'iothread', 'id': 'iothread1' } }

{'execute': 'object-add', 'arguments':{ 'qom-type': 'colo-compare', 'id': 'comp0', 'props': { 'primary_in': 'compare0-0', 'secondary_in': 'compare1', 'outdev': 'compare_out0', 'iothread': 'iothread1' } } }

{'execute': 'migrate-set-capabilities', 'arguments':{ 'capabilities': [ {'capability': 'x-colo', 'state': true } ] } }

{'execute': 'migrate', 'arguments':{ 'uri': 'tcp:192.168.220.244:9001' } }

4.性能

4.1网络性能

总体来说,virtio的情况下不同的协议影响比较大:

下载:scp基本有非colo的80%以上, http只有一半左右,ftp则只有20%左右

上传:scp和ftp在50%以下;

udp:在客户端1Mbits/s的发送带宽下,基本不会丢包,但延迟抖动相比非colo要大些,在20-40倍左右; 比如colo的jitter是0.205ms, 非colo在0.008ms

ping:延迟大50%左右

4.2磁盘性能

磁盘读性能几乎不受影响,rbd磁盘写性能约为单机的30%~40%,本地磁盘写性能约为单机的15%~30%。

4.3 官方测试结果(同非colo相比)

总带宽和并发连接数

基本无影响

sysbench cpu和内存

性能基本接近

内核编译

80% (原因是读写多,网络包少)

FTP

get:97%; PUT: 50%

web服务器:apache(服务端) + web bench(客户端)

不同并发请求数:主要开销是脏页传输
1  - 100%
4  - 99%
16 - 89%
64 - 63%
256 - 42%

PostgreSQL

82.4% - 85.5%, 同web服务器一样,主要也是脏页的开销

客户机多cpu

基本无影响

5.受限功能

  1. vhost-net和直通网卡
    状态:不支持
    原因:colo proxy需要截获虚拟网卡的收发报文(以便转发或比较),但colo proxy实现在用户态,vhost-net实现在内核态,数据不经过用户态,用户态proxy无法截获内核态vhost的数据包,因此无法支持vhost。
    解决方案:
  2. 热拔插
    状态:不支持
    原因:colo本质上是热迁移,热迁移过程中不允许热插拔。
    解决方案:
  3. 快照
    状态:不支持
    原因:快照本身对虚拟机本身没有影响,但快照不会在主备间同步。

6.建议的场景

从实际测试来看,使用colo后性能还是有一定的下降,failover后可能出现业务无法自动恢复的情况;且colo有额外的开销,比如colo本身占用更多的内存,磁盘,迁移时的cpu,网络等开销,因此建议在部署了colo的主机上不要部署太多的其他虚拟机;基于这些,建议在没有其他高可用方案或者代价较高的情况下使用colo,也不建议作为长期方案使用;建议在使用时间比较短,要求业务恢复比较快的情况下使用colo。

参考资料:

【1】https://wiki.qemu.org/Features/COLO  

【2】https://wiki.qemu.org/Features/BlockReplication  

【3】https://wiki.qemu.org/Features/COLO/Managed_HOWTO 

【4】https://wiki.qemu.org/Features/COLO/Manual_HOWTO 

【5】https://wiki.qemu.org/Features/FaultTolerance  

【6】http://www.linux-kvm.org/images/1/1d/Kvm-forum-2013-COLO.pdf 

【7】http://www.linux-kvm.org/images/0/01/01x07-Hongyang_Yang-Status_update_on_KVM-COLO.pdf 

【8】http://www.linux-kvm.org/images/a/af/03x08B-Hailang_Zhang-Status_Update_on_KVM-COLO_FT.pdf  

【9】https://www.linux-kvm.org/images/0/0d/0.5.kemari-kvm-forum-2010.pdf 

【10】https://www.ccf.org.cn/ccf/contentcore/resource/download?ID=48660 

【11】a3-dong.pdf 

【12】https://git.qemu.org/?p=qemu.git;a=blob;f=docs/COLO-FT.txt 

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0