使用 Clion 查看 DRBD(Kernel Module)代码

因为内核里有很多编译参数,所以需要配置下。

可以参考 http://ybin.cc/tools/clion-for-linux-driver-developer/

我的最终配置是:

...
include_directories(../kernel-3.10.0-327.36.1.el7/linux-3.10.0-327.36.1.el7/include)
include_directories(../kernel-3.10.0-327.36.1.el7/linux-3.10.0-327.36.1.el7/include/linux)
include_directories(../kernel-3.10.0-327.36.1.el7/linux-3.10.0-327.36.1.el7/mm)
include_directories(../kernel-3.10.0-327.36.1.el7/linux-3.10.0-327.36.1.el7/arch/x86/include)
include_directories(../kernel-3.10.0-327.36.1.el7/linux-3.10.0-327.36.1.el7/include/uapi)
include_directories(../kernel-3.10.0-327.36.1.el7/linux-3.10.0-327.36.1.el7/arch/x86/include/uapi)
include_directories(.)
include_directories(drbd)
include_directories(drbd/compat)
include_directories(drbd/linux)

add_definitions(-imacros ../kernel-3.10.0-327.36.1.el7/linux-3.10.0-327.36.1.el7/include/linux/kconfig.h)
add_definitions(-D__KERNEL__)
add_definitions(-DKBUILD_MODNAME)
add_definitions(-DCONFIG_BLOCK)
add_definitions(-DCONFIG_HZ)
add_definitions(-DMODULE)
add_definitions(-std=gnu89)
...

NSDI 2019 Notes

前言

NSDI 2019 里有两篇容器网络相关的话题,这篇还是比较有意思的,the morning paper 也谈到了这篇文章:https://blog.acolyer.org/2019/03/22/slim-os-kernel-support-for-a-low-overhead-container-overlay-network/。原版的视频、Slides、文章在 NSDI 官网都可以看:https://www.usenix.org/conference/nsdi19/presentation/zhuo。同时作者在 Github 上开源了实现:https://github.com/danyangz/Slim

大致思路是容器里的应用的流量送到另一个容器里的应用需要经过四次协议栈。

除了底层物理机的协议栈之外,主要是有一层 network namespace:

因此主要思路就是绕过这一层 stack,其效果还是不错的:

  • memcached 吞吐提高 71%,延迟降低 42%,CPU 占用减少 56%
  • Nginx CPU 占用减少 22-24%
  • PostgreSQL CPU 占用介绍 22%
  • Kafka CPU 占用减少 10%

介绍

容器网络往往使用 overlay 网络,但是 overlay 网络会带来显著地性能影响。测试显示 overlay 网络和 host 网络相比的吞吐会下降 23~48%,每个报文的延迟会增长 34~85%,CPU 占用会提高 93%,现有的加速技术往往是针对虚拟化的,对容器支持不够。

这里的核心问题就是一个包要在一个物理机上穿越两次协议栈,来回就是四次。这种设计显示受虚拟化的影响,因为虚拟机是有自己的协议栈的,宿主机不知道任何 Guest 的协议栈知识,但是容器不然,宿主机知道每个网络连接的完整信息。

因此作者设计了一种容器网络,核心思想就是让一个物理机上报文只经过一次协议栈。

这个设计有几个挑战:

  1. 网络虚拟化不能要求应用作出修改
  2. 需要支持与现在的网络相同的网络策略(network policy)
  3. 支持现在的容器网络相同的安全模型

Slim 的优点在上一节描述过了,缺点也有几个:

  1. 增加了连接建立的复杂性,因此连接建立慢了 106%;
  2. 不支持容器热迁移;
  3. network policy 由 packet-based 转为了 connection-based;
  4. 只支持 TCP 这种状态协议

背景

容器网络一般有几种通信模型:

  • bridge mode
  • host mode
  • macvlan mode
  • overlay mode

image.png-25.1kB

其中:

  • bridge mode 只用于同 host 通信;
  • host mode 性能好,但是管理和部署非常复杂,实际上像 Kubernetes 都不支持这种模型;
  • macvlan mode 类似于硬件机制(例如 SR-IOV),macvlan 让容器 IP 可以在 host 网络路由,但增加了数据中心网络路由的复杂性,因此大部分 cloud provider 已经 block 了 macvlan mode;
  • 所以最终最流行的方案还是 overlay,这个类似于虚拟机,目前有很多这样的实现,例如 Weave、Flannel、Docker Overlay 等。

overlay 网络的核心之一是 vswitch,提供了:

  • network bridging,允许同 host 通信
  • network tunneling,跨物理网络通信

vswitch 一般用 ovs,使用 vxlan 作为 tunneling protocol。

然后就是各种 network policy(例如 access control、rate limit、 QoS 等),具体实现往往通过 iptables、ovs 等。

这种 overlay 网络的问题很明显,就是基于 packet,因此在 network namespace 要经过打包、封包,在 host 上协议栈要再来一次包识别、封包,到了对端则要再解包、识别,再解包再识别,因此 overhead 就很重了。

下面的表格展示了实测中 overlay 带来的性能成本:

WX20190423-125222.png-69.5kB

下图展示了通过 Weave 实测的 CPU 占用成本:

WX20190423-125411.png-52.6kB

可以看到 CPU 主要陈本在 soft irq 上。

当然现在有一些技术来解决这个问题,例如 packet steering,创建多个 queue,对应每个 CPU 核心,用 hash 来 map 报文和 queue,这样跨核心的成本可以下降很多,下面的表格展示了使用 packet steering 带来吞吐和延迟提升:

image.png-60.5kB

packet steering 提升了 91% 的 TCP 吞吐,但并不能缓解延迟,而且 CPU 占用也有影响。

整体来说,这种容器网络,特别是 overlay 这种我们可以统称为 packet-based virtualization,可以用下图概括:

image.png-112.3kB

设计

Slim 通过减少报文进协议栈的次数来优化,其思路可以简称为 connection-based virtualization:

image.png-95.2kB

设计时有几个设计目标:

  1. 方便部署,支持未修改的应用;
  2. flexible,支持各种 network policy;
  3. 安全,容器不能去获得物理网络的信息们不能直接在 host network 上创建连接,或者提高 traffic priority

image.png-37.4kB

上图是 Slim 的整体设计,有三个主要组件:

  1. 用户态有一层叫做 SlimSocket,与应用相连接;
  2. 用户态的 SlimRouter,跑在 namespace 里;
  3. 一个小的可选的内核模块 SlimKernModule,可以实现一些高级功能,例如动态更改 access control rules、安全配置等

这里面 SlimSocket 暴露了 POSIX socket 接口,来 intercept 应用的 socket 相关 sys call。当 SlimSocket 探测到应用尝试建立连接,就发送请求到 SlimRouter,SlimRouter 负责创建连接,再以文件描述符(fd)返回给应用,下面应用就是用这个 fd 来 send/receive 报文。因为 SlimSocket 是 POSIX 接口,Slim 会将 SlimSocket 动态链接到应用,所以应用不需要改动。

下面是解决控制策略、安全策略,如果再回到 packet based 方法的话就太低效了,因此这里的方法是在创建 connection 之前通过 SlimRouter 检查。如果是连接建立起来之后规则改变了,那么 SlimRouter 会扫描所有现有连接然后 remove 不能满足规则的 fd。

这种将 host 的 fd 直接返回给容器内的进程显然会引起很多安全方面的 concern,为此 slim 提供了一个安全模式,当开启安全模式时,SlimKernModule 会限制返回给 container 的 fd 的能力,SlimKernModule 有三个功能:

  1. track fd 在 container 中 propagate;
  2. 根据 SlimRouter 请求 revoke fd;
  3. 禁止这个 fd 的不安全的 sys call(getpeername、connect、setsocket),SlimSocket 会对非恶意的应用的模拟这些 sys call。

下图是 blocking io 为例演示连接建立的整个过程:

image.png-88kB

这部分在 slide 中很好的演示,上图也很清晰,我就不详细介绍了。

现在很多应用都是使用异步 API 了(select、epoll),而不是上面演示的同步 API,以 epoll 为例,epoll_create 会创建一个 meta fd,meta fd 实际表示了一个 fd set,应用使用 epoll_wait 来等待 fd set 的任一事件。在连接建立时,我们需要修改 epoll fd set 中的 fd。SlimSocket 会通过 epoll_ctl track epoll fd 和 epoll fd set 的对应。比如 epoll fd set 中的 fd acceptconnect 时,SlimSocket 会将原 fd 从 set 中移除,并增加 host namespace 的 fd 到 set。

这个方案有一个限制是对现有的 IT 工具不友好(host 上使用的 IT 工具),如果你想用 iptables 什么的搞一些之前的一些 packet-based 的策略的话,就不能用 Slim。

实现

大致上在上一节都提到过了,SlimSocket、SlimRouter、SlimKernModule 分别用了 1184 行 C、1196 行 C++ 和 1438 行 C,SlimSocket 使用 LD_PRELOAD 来动态链接到应用,SlimSocket 和 SlimRouter 之间用 Domain Socket 通信。非安全模式时,SlimRouter 用 sendmsg 直接传递 fd 到 SlimSocket,安全模式则经过 SlimKernModule 通过 fd duplication method。

SlimRouter 通过 JSON 文件获得 access control 的内容,提供一个 CLI 来 reload JSON。rate limit 和 QoS 通过 tc 实现。

SlimRouter 和 SlimKernModule 之间通过 procfs 中的 dummy file 通信。

SlimKernModule 需要替换 sys call table 中的函数指针,用 hash table 保存 taged fd 和 unsafe 的 sys call 列表( MatheMatrix:tagged fd 原文说类似于 OSDI 10′ https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Enck.pdf,是 SlimKernModule 实现里很重要的内容,但我没有具体看)

评估

直接看图了,测试环境是 40G 网络,使用 iperf3、NPtcp、mpstat 这些开源软件评估。RFS 为 weave 打开 rfs 的性能。

image.png-144.5kB

下图展示了 network policy 的效果。

image.png-166kB

此外还有一些使用应用的测试结果,有 Memcache、Nginx、PostgreSQL、Kafka

image.png-155.3kB

image.png-279.2kB

image.png-200.5kB

image.png-212.4kB

此外作者还测试了容器迁移,Slim 会轻微的影响迁移性能。

image.png-156.7kB

讨论

  • 连接建立时间。Slim 会比较明显的延长连接建立时间,优化思路吗,就是尽量少关 SlimSocket,都需要一些方法;
  • 容器热迁移。前面虽然测了容器迁移,但其实不是热迁移,Docker 现在有实验项目 criu,但还比较弱,Slim 从设计上将,增加了容器热迁移的难度;
  • UDP。UDP 本质上可以支持,但问题是做网络策略就比较难了,另外就是 UDP 在数据中心的典型使用场景是减少连接建立时间,而 Slim 正好相反;
  • Packet-based 网络策略。这个实在不好做,真的需要的话只能用 overlay 网络;
  • LD_PRELOAD。这个其实对应用有要求,因为一些 system 可能假设了一些应用(例如 Go 编写的一些)是静态链接的;
  • Error code。一些情况下,error code 与 overlay 网络的结果不同,不过这个是可以解决的;
  • SmartNic。SmartNic 对容器网络可能帮助并不大,比如 Catapult 可能需要 Linux 的改变,SR-IOV 有 macvlan 一样的问题,FlexNIC 可能还行,但还是实验阶段。

TLA+ 笔记

很久以前学过一些 Prolog,当时主要是为了学习人工智能和数理逻辑。TLA+ 与之有一点点像,Prolog 可以用来处理各种规划问题、一阶逻辑推理,TLA+ 可以用来设计各种分布式、异步系统,搭配 TLC(model checker) 来做验证——他们的设计目标都不是解决通用的编程问题,而是通过数理逻辑解决一些特定领域问题。

TLA+ Tools 包含很多工具,可以在这里下载,大部分人都是使用这个 Tools:
http://lamport.azurewebsites.net/tla/standalone-tools.html?back-link=tools.html

在里面的链接指向的 github 地址里,有 Windows、Linux、Mac 的二进制版本,良心。

在看 TLA+ Community Meeting 2018 的时候还看到了一个形式验证语言(其实是 Python 的扩展)DistAlgo,整体思路和 Demo 看起来特别棒,就是目前各种材料和介绍还是相对少,所以我没有继续研究下去,有兴趣和时间的话,看看 DistAlgo 也挺好。

下面是笔记。


介绍

分布式系统的正确性特别难验证,所以做出了 TLA+,因为 2015 年 AWS 在 CACM 发了一篇 How Amazon Web Services Uses Formal Methods 引起了很多人注意,年底 TLA+ 作者之一的 Langworthy 和 Lamport 找微软高层推动 TLA+ 在微软落地,一定程度上得益于 AWS 的文章和 Lamport 2015 年拿到图灵奖带来的影响力,Satya 发邮件说 Go go go。

2016 年开始微软在内部办了一共三天的 TLA+ School,教学了很多微软工程师 TLA+,一共运行了三次,到第四次时,Lamport 终于受不了重复给别人讲 TLA+ 基础,于是做了教学视频,没错就是很火的这个视频:http://lamport.azurewebsites.net/video/videos.html

image.png-63.4kB

所以从 2018 年开始,School 变成了 Workshop,目的从基础教育提升到了继续教育,特别是生产实践带来的反馈。

现在 TLA+ 在微软内部应用在 Service Fabric(类似 kubernetes)、Azure Batch、Azure Storeage、Azure Networking、Azure IoT、Cosmos DB。

(本节主要来自 http://tla2018.loria.fr/contrib/langworthy-slides.pdfhttps://www.youtube.com/watch?v=ifFfxRCX_jw

Learn TLA+ 笔记

主要是阅读 Learn TLA+ 的笔记,这个写了很多基础的练习和介绍,先是 PlusCal 的内容,在 TLA+ Tools 上用 ⌘+T 即可翻译成 TLA+,不过 PlusCal 并不能完全等价 TLA+。

Introduction & PlusCal

基本使用流程很简单,先在 modules 里建 module,写 PlusCal,转成 TLA+,再在 models 建 model,写条件,运行检查即可。

PlusCal 的基本语法:

---- MODULE module_name ----
\* TLA+ code

(* --algorithm algorithm_name
begin
\* PlusCal code
end algorithm; *)
====

每个文件里只能有一个 PlusCal 算法。

举个例子:

---- MODULE transfer ----
EXTENDS Naturals, TLC

(* --algorithm transfer
variables alice_account = 10, bob_account = 10, money = 5;

begin
A: alice_account := alice_account - money;
B: bob_account := bob_account + money;

end algorithm *)
====

里边 variable 即变量,声明变量使用 =,算法实现中使用 :=,A、B 是标签,定义了算法步骤,一个标签里的内容被视作一个事务。{a..b} 表示整数 a 到 b 的整数集合,\in 即集合论里的 in。

基本的运算符:

image.png-24.7kB

可以通过 process 来设计一个多线程程序,比如这样:

---- MODULE Transfer ----
EXTENDS Naturals, TLC

(* --algorithm transfer
variables alice_account = 10, bob_account = 10,
          account_total = alice_account + bob_account;

process Transfer \in 1..2
  variable money \in 1..20;
begin
Transfer:
  if alice_account >= money then
    A: alice_account := alice_account - money;
       bob_account := bob_account + money;
end if;
C: assert alice_account >= 0;
end process

end algorithm *)

MoneyNotNegative == money >= 0
MoneyInvariant == alice_account + bob_account = account_total

====

其中通过类似 MoneyNotNegativeMoneyInvariant 这样的语句来帮助添加一个全局的一致性检查 ,invariant 可以在 TLC 里指定,来检查全局的一致性:

image.png-169.8kB

上面的代码在并发时显然是有问题,所以 TLC 可以检查出来它:

image.png-615.8kB

展开 Error Trace 可以看到状态转移记录:

image.png-205kB

可以看到并发执行时,money 同时有两个值,由于转账时没有对账户加全局锁,所以账户总大于零这个断言被 break 了。

这里都是 P-Syntax,PlusCal 还有 C-Syntax 的格式,具体参考 https://lamport.azurewebsites.net/tla/p-manual.pdfhttps://lamport.azurewebsites.net/tla/c-manual.pdf

PlusCal 中是有 print 的,比如下面这个例子:

EXTENDS TLC

(* --algorithm hello_world
variable s \in {"Hello", "World!"};
begin
  A:
    print s;
end algorithm; *)

这里的 User Output 打出了内容:

image.png-419.6kB

PlusCal 的 if、while、goto 都和一般语言很像,就不赘述了。

Model Overview 里的 “no behavior spec” 一般很少用,运行后在 result 页可以使用 “evaluate constant expression”,然后可以写点东西来验证 TLA+ 是如何运行的。

最后是引入 divergent behavior,让系统在一个步骤里可以做不同的事情,对于单进程 PlusCal,可以通过 witheither 来引入。

either 简单地说就是没有条件的 if,可以让 TLC 知道这里有两种路径,都可以执行以下:

variables x = 3, i = 2;
begin
while i > 0 do
  either 
    x := x + 2;
  or 
    x := x * 2;
  end either;
  i := i - 1;
end while

结果是这样的:

image.png-15.2kB

另一种是 With:

with a \in {1, 2, 3} do
  x := x + a
end with;

结果是这样:

image.png-18.9kB

The design of a practical system for fault-tolerant virtual machines

这篇文章是 MIT 6.824 课程安排的一篇阅读材料。

我 Fork 了别人整理的 MIT 6.824 的课程材料,关于这篇文章的内容可以在这里找到:https://github.com/MatheMatrix/MIT-6.824-Distributed-Systems/tree/master/Lectures/LEC04

下面是笔记。


摘要

VMware 在 2010 年发布了这篇文章,主要描述它们在 vShpere 4.0 上实现的虚拟机高可用方案,这是一个商用的、企业级的方案,虚拟机性能下降在 10% 以内,虚拟机同步需要 20M 左右带宽。文章提到让这样一个系统支撑企业应用除了复制虚拟机的指令外,还有很多其他问题。

介绍

实现高可用的基本思路是主备,主备最简单的想法就是复制主的所有状态,包括 CPU、内存、IO。但是这个方案无疑需要非常大的带宽。
另一种方法是复制状态机思路,简单的说,这个思路就是把虚拟机当作一个确定状态机,两边先保持一个一致的初始状态,然后保证它们能够一样的顺序接收一样的指令。因为总有一些操作造成的结果不是确定性的,因此还需要额外的工作来保持同步(主要是内存)。

这个思路在物理机上无疑很难实现,但是在虚拟机上就好做很多,因为虚拟机就是一个定义的很完善的状态机,其所有操作、设备都是虚拟化的。但是相比物理机,虚拟机自己也有一些非确定性操作,例如读取时间和发送中断,这就是为什么我们刚才说需要额外操作来保持同步。

VMware vSphere FT 基于确定性重放(deterministic replay),但是增加了必要的额外协议和功能来保证系统功能完整。到写这篇文章时,FT 生产版本还只能支持单 CPU 虚拟机,因为对多 CPU 来说,几乎每次读写共享内存都是非确定性操作,由此带来巨大的性能损失。

这个系统的设计目标只处理 fail-stop 错误,也就是系统一旦出错则立即 stop,而且正确的服务器立刻知道它 stop 了。(分布式系统中的各种错误可以参考:http://alvaro-videla.com/2013/12/failure-modes-in-distributed-systems.html, fail-stop 几乎是最简单的错误类型)

FT 设计

首先我们将备份虚拟机运行在一个和主虚拟机不同的物理机上,备份虚拟机与主虚拟机保持同步和一致但有一个很小的时间差,这时我们称这两个虚拟机处于 virtual lockstep。
两个虚拟机的虚拟磁盘位于共享存储上(例如 FC 或 iSCSI,后面会讨论非共享存储的场景),只有主虚拟机会在网络上对外通告,所以所有网络输入只会进入主 VM,其他输入例如键盘和鼠标也是一样只到主虚拟机。

所有主虚拟机收到的输入,会通过网络(logging channel)来发到备份虚拟机。VMware 通过特定协议做收到确认,来保证主虚拟机失效时不会有数据丢失。

为了检测主虚拟机或备份虚拟机失效,VMware 会在两个服务器上跑心跳,同事监控 logging channel 的流量。

确定性重放(Deterministic Replay)的实现

虚拟机有大量的输入,包括:

  • 收到网络报文
  • 磁盘读
  • 键盘鼠标输入

还有大量非确定性事件(比如虚拟中断)和非确定性操作(比如读取 CPU 时钟计数器)都会影响虚拟机状态。

难点有三处:

  • 正确捕捉所有的输入和不确定性
  • 正确的在备份虚拟机上应用这些输入和不确定性
  • 确保不太多影响性能

此外,x86 处理器有很多复杂操作会造成未定义的、造成不确定性的副作用。

VMware 确定性重放(deterministic replay)(2007 年的这篇文章更详细的做了介绍:http://www-mount.ece.umn.edu/~jjyi/MoBS/2007/program/01C-Xu.pdf)解决了上述的前两个问题。

确定性重放可以记录虚拟机的所有输入和所有可能的非确定性,并以流的方式记录到一个日志文件里。通过读取这个文件,就可以对虚拟机操作进行重放,同时这个文件还记录了足够的信息来还原非确定操作造成的状态改变和输出。例如定时器、IO 完成中断这些非确定性事件会记录发生在具体哪个指令之后来保证重放时可以让事件发生在相同的位置。

FT 协议

上面说到确定性重发是记录日志的,但很好理解在 FT 实现里,我们不可能这么做,取而代之的是通过 logging channel 来把这些 log entry 发送到备份虚拟机。
备份虚拟机要实时的重放,这里最重要的要求是:

Output Requirement:如果备份虚拟机取代了主虚拟机,那么备份虚拟机要按照之前主虚拟机的输出保持完全一致的继续输出到外界

这里很重要的一点是备份虚拟机需要保持一致的对外输出,而不是一致的运行——一致的运行是不可能的,因为当主节点宕机时,肯定会有一些不确定性事件(比如中断)造成备份虚拟机与主虚拟机运行的不一致,而这些不确定性可能还没来得及同步,所以怎么保证主虚拟机宕机时,备份虚拟机总能够接得住主虚拟机的输出,让外界以为没有发生中断/切换呢?

比较简单的思路就是延迟对外输出(例如网络报文),就像这样:

在主虚拟机对外输出前,必须先要把所有的日志发送到备份虚拟机,备份虚拟机确定可以重放出主虚拟机输出时的状态时,主虚拟机才可以对外输出。这样如果主虚拟机宕机,备份虚拟机可以正确达到对外的一致状态。反之,如果备份虚拟机没有收到足够的日志,那么备份虚拟机会分叉。

延迟输出并没有暂停虚拟机,只需要在发送输出前延迟即可,虚拟机可以继续运行,这里和之前的容错系统是不同的(https://courses.mpi-sws.org/ds-ws18/papers/bressoud-hypervisor.pdf, http://www.cs.utexas.edu/users/lorenzo/corsi/cs380d/papers/ftjvm.pdf

除非主虚拟机输出时引入两阶段提交,否则无法保证 fail-over 时无法保证输出只发生了一次,不过比较幸好的网络或磁盘 IO 上这个不是大问题。

宕机检测与主备切换

当备份虚拟机宕机时,主虚拟机必须立刻退出记录模式,不发送日志(不然会停止输出);主虚拟机宕机时,备份虚拟机则要立刻恢复正常模式,消费完 logging channel 的所有日志然后开始进行执行。此外 VMware FT 还会自动广播 MAC 等来帮助外界知道虚拟机的位置,还需要做一些磁盘 IO(后面描述,主要是磁盘并行和 DMA 的问题)

VMware FT 使用 UDP 来做心跳检测,并且不断监控 logging channel 的流量情况、备份虚拟机的 ack 情况——因为定时器中断的缘故,操作系统应该是总会有日志的。

不过这些检测手段都解决不了脑裂问题,因此 VMware 要求虚拟机一定运行在共享存储上,通过 test-and-set 来防止脑裂。

具体实现

上面只是大概设计,真正将系统可用、鲁棒、自动化还需要很多细节性的工作。

启动和重启备份虚拟机

这里讲 VMware 如何启动一个初始状态一致的虚拟机,对于 VMware 来说可以利用之前的 VMotion。

另一个问题是如何选择物理机来启动备份虚拟机。

管理 Logging Channel

如上图,两边各有一个 log buffer,当备份虚拟机 log buffer 为空时,需要暂停备份虚拟机,反之主虚拟机 log buffer 满时需要暂停主虚拟机。
因此,要非常小心的设计来规避主虚拟机 log buffer 满的情况。为此 VMware 设计了一个机制——当备份虚拟机执行跟不上主虚拟机的速度时,会降低主虚拟机的 CPU 性能,当能跟上时,再慢慢提高主虚拟机的性能,有点像滑动窗口一样。

FT 虚拟机上的控制操作

在 FT 虚拟机上的控制操作也需要考虑,例如所有的资源管理变化(比如调整虚拟机 CPU)都需要同步操作两个虚拟机,因此在 logging channel 里需要加特殊的一些控制 entry。
简单地说,除了虚拟机的 VMotion 之外,所以操作都需要同步。

不过这不意味着 VMotion 不需要修改:

  1. VMotion 时也要注意不能迁移到同一物理机;
  2. VMotion 时,备份虚拟机到主虚拟机会有中断重连发生,需要专门处理,特别是迁移备份虚拟机,因为需要主虚拟机控制对外住 IO。

磁盘 IO 实现上的问题

IO 在实现上有不少问题:

  1. 磁盘操作可能是异步、同步的,这样不同的 IO 可能请求了磁盘的同一位置,造成不确定性;
  2. 因为磁盘操作可能是通过内存 DMA 的,因此内存的相同位置的读写也会带来 IO 的不确定性;
  3. 主虚拟机 IO 发出但未完成时宕机,备份虚拟机升级为主虚拟机后无法知道 IO 究竟是否成功。

解决方案是:

  1. 检测竞争 IO,强行按照主虚拟机的情况顺序化
  2. 对目标磁盘 IO 相关内存做检测和页保护,页保护可以让虚拟机在访问还没完成的 IO 的内存 trap,然后 IO 完成后再恢复虚拟机。但是修改 MMU 的页保护时个非常昂贵的操作,因此 VMware 引入了 bounce buffer。bounce buffer 是一快和大小和所需要磁盘操作的内存大小相同的临时缓存,可以 buffer 住相同位置的 IO 操作。A disk read operation is modified to read the specified data to the bounce buffer, and the data is copied to guest memory only as the IO completion is delivered. Similarly, for a disk write operation, the data to be sent is first copied to the bounce buffer, and the disk write is modified to write data from the bounce buffer;
  3. 返回 IO 失败可能导致 Guest OS 无法正确处理,所以只好在备份虚拟机升级为主虚拟机的过程中重发 IO。

网络 IO 实现上的问题

为了优化性能,网络很多代码都是异步的,这在 FT 中会带来不确定性,所以很多优化诸如异步更新 Ring Buffer、异步收取 transmit queues 这些要禁用掉。
但直接禁用掉性能又肯定不行,为此需要优化中断机制,通过减少中断。
还有之前我们说过主虚拟机 Output 前需要受到备份虚拟机的 Ack,这个也很损耗性能,解决方案是减少这个延时,其中核心解决方案是减少了备份虚拟机收到 log 到回复 ack 的延时。

设计上的一些其他思路

共享磁盘 vs 非共享磁盘

如果是非共享磁盘,那么备份虚拟机将写入到不同的虚拟磁盘里,类似下图这样:

这样 Output Rule 这个设计也需要改变。优点是避免了共享存储的成本、可用性问题,可以想象一下远距离 FT 之类的场景,但是缺点是两个虚拟磁盘必须在刚开始时完全一致(包括内容和行为),而且需要考虑不同步时如何 resync。另外就是脑裂的问题,需要考虑用第三方作为 tiebreaker。

在备份虚拟机上直接读取磁盘

在我们的默认设计里,无论共享磁盘/非共享磁盘,虚备份虚拟机都不会读取自己的虚拟磁盘,因为磁盘读被考虑为一个输入,所以通过 logging channel 来同步。
可以考虑让备份虚拟机执行磁盘读请求,这样就可以减少 logging chennel 的负载,但这样可能降低备份虚拟机的性能,而且要考虑主虚拟机读取成功、备份虚拟机读取失败的场景,最后这里还有主虚拟机、备份虚拟机潜在的竞争问题,总之问题不少。VMware 在测试性能时测试了这个方案,对真实应用来说,会有轻微的性能下降,但会有显著地 logging chennel 带宽的减少。

性能评估

在两个服务器上每个运行了 8核8GB 的内存的虚拟机,服务器用万兆网络直接连起来,不过实际用了远小于 1Gb 的带宽。存储是 EMC Clariion 4 Gb FC 存储。

基本评估

虚拟机空闲时,大概会用 0.5~1.5 Mb 带宽,当有大量网络 IO 时,logging channel 的带宽会显著增大:

网络性能

网络的挑战很大:

  1. 高速网络会带来海量中断
  2. 海量报文都通过 logging channel 同步
  3. 海量报文的 Output Rule 造成大量延迟

具体参考上面的图

相关工作

我这里就略去了,大家可以直接看原文,此外后面 QEMU 发展了 COLO,又诞生了很多文章,可以在网上搜到,还有 VMware 这里依赖了它之前的确定性重放和 VMotion 的技术,感兴趣的话也需要再去查阅文献。

总结

有几点:

  1. 因为 FT 所需要的带宽往往并不那么大,因此长距离 FT 也是可能的,可以通过压缩等方法进一步减少 logging chennel 的带宽,但可能增大 CPU 负载;
  2. 确定性重放的设计只能在单 CPU 虚拟机上保持高效,多处理器是另一个可能的探索方向
  3. 目前针对的 fail 场景是 fail-stop,另一个可能的探索方向就是扩展到部分硬件失效,比如网络失效或者供电冗余失效等等

使用 Linux Bridge 搭建 VXLAN Overlay 网络

前言

使用 Linux Bridge 搭建 VXLAN 网络不是件很难的事,但是目前确实有一些小坑,这里记录一下。

本文会介绍:

  • 如何使用 Linux Bridge 搭建一个 VXLAN Overlay 网络
  • 如何用 Namespace 模拟虚拟机验证通信
  • Linux VXLAN DOVE Extension 带来的新参数

本文不会介绍:

  • VXLAN 是什么
  • 现代 VXLAN 协议的发展与控制平面的演化

命令

没有多大难度,直接介绍使用的命令。

iptables -D INPUT -j REJECT --reject-with icmp-host-prohibited
# 在我的环境里总会有默认的两条 reject 规则,好烦,先干掉

ip link add vxlan21 type vxlan id 100 dev eth0
# 先添加 vxaln 接口,这个是比较简洁的版本(dev eth0 可以去掉),你可以依照喜好添加一些参数,比如:
# local 10.0.121.180,指定一个本地地址,linux 下的 VXLAN 接口可以不指定 IP 地址、网卡,也可以指定,看需要设置;
# group 239.1.1.1 使用组播模式,239.1.1.1 即为组播地址;
# dstport 4789 指定 vxlan 端口,如果不写的或者写 dspport 0 的话系统会自动使用 8472——根据 IANA 的[标准][1]现在应该使用 4789,包括 VMware NSX 6.2.3 开始也默认从 8472 该到了 4789;
# srcport 32768 61000 可以指定源端口的范围;
# 此外还有一些 DOVE Extension 带来的参数,放在后面介绍

ip link set vxlan21 up
# 将接口 UP,系统会起一个 UDP Socket 监听相应端口

brctl addbr lb-int
ip link set dev lb-int up
brctl addif vxlan21
brctl addif lb-int vxlan21
bridge fdb append to 00:00:00:00:00:00 dev vxlan21 dst 10.0.56.18
# 配置一个对端 VTEP

ip link add veth20 type veth peer name veth02
ip link set veth02 up
brctl addif lb-int veth02
ip netns add veth2
# 创建 namespace 和 veth 设备模拟虚拟机

ip link set dev veth20 netns veth2
ip netns exec veth2 ip a add dev veth20 192.168.0.12/24
ip netns exec veth1 ip link set veth20 up
ip netns exec veth2 ping 192.168.0.11
# 如果你在另一台 hypervisor(VTEP)做好了相应操作模拟了 192.168.0.11 地址,此时应该已经可以通讯了

DOVE Extension

DOVE 的全称是 Distributed Overlay Virtual Ethernet,是从 Linux 3.8 开始引入到内核,目的是方便为 Linux VXLAN 接入控制平面,提升效率。

引入 DOVE 后目前在创建 VXLAN 设备时可以添加下面几个参数:

  • l3miss,在 drivers.net.vxlan.vxlan_xmit 中,如果 VXLAN 设备开启了 Proxy,会尝试进行 ARP 压缩(drivers.net.vxlan.arp_reduce,尽量将 ARP 广播范围压缩到本地),如果此时查找不到这个 IP 对应的 ARP 记录的话,就会触发 l3miss,发送一条消息(netlink)到 userspace,这时 agent (userspace 程序,例如 docker overlay)可以直接添加一条 Neighbor 信息,以替代广播;
  • l2miss,VTEP 的 FDB 不存在目的 MAC 所对应的 VTEP IP 时,触发 l2miss,通过 netlink 发送一条消息到 userspace 替代复制到所有 VTEP;
  • proxy,在上面 l3miss 中说过了,用于做 ARP 本地压缩;
  • leraning,学习远端虚拟机的 MAC 地址,就是 VXLAN 的 flood and learn;
  • ageing,上面学习到本地 FDB 的虚拟机的 MAC 的超时时间;
  • maxaddress,FDB 表的最大条目数;
  • rsc,也就是 L3 switching,直接 switch 到目的地址,不需要经过路由(作者本人没有测试过,如果你做了测试欢迎交流)。

当然了,我说的都是错的,我建议你还是直接看代码。

如果觉得看 vxlan.c 比较困难的话,可以看 DOVE 的 Patch:http://lists.openwall.net/netdev/2012/11/15/96。

Reference

  1. Rami Cohen, et al: Distributed Overlay Virtual Ethernet (DOVE) integration with Openstack,
    IP/IEEE International Symposium on Integrated Network Management (IM 2013), 29-
    -May-2013, pp 1088-1089

  2. Etsuji Nakai: How VXLAN works on Linux, https://www.slideshare.net/enakai/how-vxlan-works-on-linux

  3. Joe Julian: How to configure linux vxlans with multiple unicast endpoints, https://joejulian.name/blog/how-to-configure-linux-vxlansith-multiple-unicast-endpoints/

  4. Thomas Richter: Software Defined Networking using VXLAN, LinuxCon Edinburgh 21-Oct-2013

  5. 刘世民: Neutron 理解(14):Neutron ML2 + Linux bridge + VxLAN 组网, http://www.cnblogs.com/sammyliu/p/4985907.html

写给 OpenStacker 的 ZStack 指南(一)

前言

相比 OpenStack 而言,ZStack 全异步、追求高稳定性和安全的代码是相对难读的,所以本文希望能通过一些简单的例子和与 OpenStack 的一些对比,将 ZStack 的特点、代码的原理尽量描述出来,降低 ZStack 的入门门槛。欢迎更多 OpenStacker 参与 ZStack 或从 ZStack 的代码中汲取经验。

从执行一个 API 说起

对一个业内人士,观察在页面上一个指令如何逐步被执行,无疑是最直观深入的了解方式。

第一步 前端

打开 Mevoco/ZStack 的界面,可以发现基本设计思路与其他 IaaS 或 OpenStack 是基本类似的,然而打开开发者面板就会发现大有不同。

OpenStack 的面板一般通过 HTTP 到 Web 后端,可能是一个像 Horizon 的中间件,也可能是直接把请求发到后面的具体服务的 API 服务,例如 nova-api 或 neutron-server。当然中间可能还会有负载均衡器或高可用之类的设施。

image_1b6dgt82i1rl0md5j8t8va1v4m9.png-305.3kB

图1 在OpenStack的Horizon面板上创建虚拟机出发的Post请求

而在 ZStack 的面板中开发者面板是很干净的,打开之后无论什么操作是不会触发 HTTP 请求的,数据和请求都在 WebSocket 传递,比如我们在面板上创建一个虚拟机,可以看到通过 WebSocket 发送一个 frame,一个 org.zstack.header.vm.APICreateVmInstanceMsg 的消息,后面跟着的是创建的参数。

image_1b6dhje891gd7rj61e551r9hv3vt.png-209.3kB
图2 ZStack面板上创建虚拟机发出的WebSocket帧

Continue reading

为什么在 VyOS(Vyatta)中 commit 会很慢

最近就这个问题调查了蛮久,其实原因比较显然,在看到 strace 的结果就已经明白了大半,但是本着求(xia)知(zhe)探(teng)索的想法,仔细验证了代码逻辑和时间(新技能 get!给 C++、Perl 混合代码调试性能),攒出了这篇文章。

ZStack 是一个开源的 IaaS 软件,架构和性能都很优秀,可以在 103 秒内并发创建 1000 台虚拟机,可惜这个数据是在扁平网络下测试得到的,一旦用上云路由网络,单个虚拟机的启动时间会延长到七秒左右。

我们现看下看下实际的占用时间,我在 vyatta 的代码里添加了日志(Repo 是 https://github.com/vyos/vyatta-cfg )综合 zstack、zstack-vyos、vyos 的日志可以看到大概是这样的(注意 zstack 的日志时间精度只提供到秒,没有毫秒):

2017-02-13 13:22:08 start executing flow[NetworkServiceManagerImpl.java:apply-network-service-DHCP]

2017-02-13 13:22:08 DEBUG [RECV] /setsnat
2017-02-13 13:22:09 DEBUG [HTTP POST][ASYNC REPLY TO /setsnat]

2017-02-13 13:22:09 DEBUG [RECV] /adddhcp

# 以下为 vyos 日志
2017-02-13 13:22:09:458 Entered cli_bin::doCommit
2017-02-13 13:22:10:216 Entered commit::doCommit
2017-02-13 13:22:10:901 notify other users in config mode
2017-02-13 13:22:12:260 cs.commitConfig complete
2017-02-13 13:22:12:557 Exit commit::doCommit
2017-02-13 13:22:12:557 Normal exit cli_bin::doCommit
# 退出 vyos 日志

2017-02-13 13:22:12 DEBUG [HTTP POST][ASYNC REPLY TO /adddhcp]

2017-02-13 13:22:12 DEBUG [RESPONSE] to /setdns
2017-02-13 13:22:12 DEBUG [HTTP POST][ASYNC REPLY TO /setdns]

2017-02-13 13:22:12 DEBUG [SimpleFlowChain] (zs-thread-81) [FlowChain: apply-network-service-to-vm-13d1359b8bd34a05a91f61dedb112e96] successfully executed flow[NetworkServiceManagerImpl.java:apply-network-service-DHCP]

可以把时间与调用关系结合起来做成一张图:

VyOS_Commit
Continue reading