大家好,我是二哥。
今天这篇是我想了很久,一直在写,但时常是敲了几个字又放下了的文章。因为“虚拟化”梳理是一个涉及范围甚广的活,而思考这个词,又容易暴露二哥的肤浅。不过,磕磕绊绊地,我还是将它写完了,厚着脸皮迫不及待地分享给大家,欢迎通过微信或公众号私信和我讨论。
虚拟化将所有的硬件资源放到一个资源池里面,并通过软件抽象的方式来描述、使用和管理。正如现实中诸如土地、林木等物理资源需要先货币化才能交易并资本化一样,计算机相关的物理设备经过了虚拟化后才软件定义化,并促进了云计算的繁荣。
谈到虚拟化,我们很难绕开另外一个令人迷惑的词:软件定义。这是一个在本文中二哥尽量避免使用的,但有时又避不开的词。人们总是把虚拟化和软件定义混用,可实际上它们是两个不同层次的概念。拿storage虚拟化和Software-Defined Storage(SDS)来举例。前者是通过软件的方式对硬件存储进行抽象,也即在硬件和使用者之间插入一个软件层。比如我有5个物理磁盘,每个的大小都是1TB,通过虚拟化的方式,可以对上层虚拟成一个5T的单一卷(volumn),而不是5个独立的disk,可以看到它的叙事焦点为:合并归集底层硬件存储并以单一视角提供给上层。而SDS则聚焦于将functionality和 features从硬件(包括虚拟化后的)层面剥离,由软件来提供类似自动化、标准接口、可扩展、透明化等诸多功能。
软件定义太宏大,容易跑偏,更容易被裹上商业术语的蜜糖,而虚拟化则具体很多。本文二哥聚焦机器算力和网络虚拟化。
机器算力虚拟化
虚拟化意味着可软件定义化,可以控制的粒度变细,同时可弹性扩展的能力变强。我们像切蛋糕一样,按需要切分、使用物理设施。
计算机历史上首个虚拟化技术实现于 1961 年,IBM709 计算机首次将 CPU 占用切分为多个极短 (1/100sec) 时间片,每一个时间片都用来执行着不同的任务。通过对这些时间片的轮询,可以将一个 CPU 虚拟化或者伪装成为多个 CPU,并且让每一颗虚拟 CPU 看起来都是在同时运行。这就是虚拟机的雏形,也是机器算力虚拟化的开始。
在2003-2008年,Xen HyperVisor问世,EMC收购了如日中天的VMware,KVM合并进了Linux Kernel。如下图所示,无论是HyperVisior还是KVM,它们都是Virtual Machine Monitor(VMM),它们存在的作用是将CPU、内存、网卡、磁盘等物理资源进行虚拟化:
- 它们将现有的硬件资源抽象、虚拟、隔离,从而允许我们进行创建、删除、编辑虚拟机等操作。
- 它们服务的对象是虚拟机。虚拟机里装有完整的OS。OS认为自己运行在普通的物理机器上,且OS上运行有各式各样的应用。
- 它们会给虚拟机划分资源,按需分配,按量使用。
- 它们对这些虚拟机之间进行隔离,这些虚拟机互相看不见对方的存在。
图:基于HyperVisor的VMM
图:基于KVM的VMM
经过在21世纪头十年的发展,虚拟机(VM)技术已经相对成熟,人们可以通过虚拟机技术实现跨操作系统的开发。但VM需要对整个操作系统进行封装隔离,占用资源很大,在生产环境中无论是部署还是扩容、缩容都显得太过于笨重。
事实上,从1979年贝尔实验室发明的chroot开始,到2013年Docker的横空出世,再到2014年Kubernetes的诞生,人们一直没有停止追求一种更加轻便的机器算力虚拟化和管理技术。比如:
- 2000年,FreeBSD 推出 FreeBSD Jail,除了对chroot进行支持外,它还支持进程和网络命名空间(namespace)
- 2001年,Linux VServer 诞生
- 2004年,SUN 发布 Solaris Containers
- 2006年,Google 推出 Process Containers,而后更名为 Cgroups并合并进Linux Kernel
- 2008年,Linux 容器工具 LXC 诞生,这便是后来名噪一时的容器技术的雏形和技术基础
2013年是个值得记住的年份。那年尚是无名小辈的PaaS 创业公司dotCloud开源了自家的容器项目Docker,凭借“Build, Ship and Run Any App, Anywhere”对Cloud Foundry、OpenShift和Clodify形成了碾压式胜利,并轻易地征服了无数码农的心。Docker基于namespace cgroup解决了如何以比VM粒度更细的方式隔离和运行软件,而它基于联合文件系统(Union File System)的镜像打包和发布创新一举解决了软件分发的问题,更实现了run anywhere的目标。虽然因为各种原因,它的结局令人唏嘘,但它已经在云计算的长河中留下了浓墨重彩的一笔。
从2017年开始,⾏业开始试⽔serverless实例服务,有人欢喜有人愁,这里二哥就不多展开了。
图:虚拟化的发展让用户可以真正地按需适量地申请资源,聚焦业务自身
从金属裸机到VM到容器再到serverless,⽤户逐渐从维护基础设施的繁重任务中解放出来,真正地实现了按需适量地申请资源,聚焦业务本身。
我记得差不多十年前做项目时,我们总是会购买一些Dell R720,给它装上ESXi,并通过vCenter将多台ESXi聚合后统一管理。一台R720可以同时跑一二十台虚拟机,虽然VM的创建和删除都非常的方便,但还是要花去一些精力去维护这些机器。而如今,谈到workload,我们几乎第一时间想到的是容器,利用K8s的编排和资源管理能力,我们只需在创建workload的描述性文件里面指定所需cpu和memory的数量即可。如下面的示例中,对于容器busybox,我给它申请的cpu配额是200个millicores(1/5个单CPU core的时间),memory则配备了10MB。
代码语言:javascript复制apiVersion: v1
kind: Pod
metadata:
name: requests-pod
spec:
containers:
- image: busybox
command: ["dd", "if=/dev/zero", "of=/dev/null"]
name: main
resources:
requests:
cpu: 200m
memory: 10Mi
1972 年,Edsger Dijkstra 在为图灵奖颁授典礼所写的感言文章中说到:“在没有计算机的时候,也就没有编程问题;当我们有了简单的计算机,编程就变成了小问题;而现在我们有了算力规模庞大的计算机,那编程就成为一个同样复杂的大问题了。”
半个世纪前的这句话,如今来看依旧是那么的富有前瞻性和洞察力。一方面根据摩尔定律,计算机计算能力一路突飞猛进,比如以前⼩型机的典型规格是32路8核CPU,现在⼀台4路PC服务器计算能⼒都超过10多年前的⼩型机服务器。而另一方面,受制于人类的智力因素,系统越复杂、并⾏度越⾼,软件越容易出故障,软件维护代价成指数级增⻓。
软件历史上的两次危机都是因为机器算力的提升和人自身的生理瓶颈之间的冲突所导致。只有将计算力切割成小的模块,小到人类的智力和现有方法论可以控制的程度,才算是让我们找到了一个舒服的姿势。而如今的以容器、微服务为代表的云原生显然使得我们进入了这样的一个舒适区。
聊完机器算力虚拟化,我们再来看看网络虚拟化。
网络虚拟化
Xen HyperVisor、VMWare和KVM标志了虚拟化浪潮的开始。但相较机器算力虚拟化的如火如荼,网络的虚拟化则显得迟钝了很多。
在整个上世纪90年代,提起网络,首先联想到的还是拨号猫、缆线、二三层的交换机等一大堆物理设备。这段期间,唯一的亮点是1999年问世的iptables。
iptables连同2001年发布的第一版OpenBSD3.0 PF(Package Filter)代表了大家开始使用软件防火墙来取代专门的硬件设备,这算是网络虚拟化的一种尝试。
VLAN是从软件的角度对所有连接到二层交换机的设备的完美抽象和虚拟化。如同文首提到的磁盘虚拟化一样,通过将LAN虚拟化后,从逻辑上对连接到交换机上的所有设备进行分类。如下面这张图里,通过VLAN可以将网络使用者分为销售类、人事类、研发类。尽管他们物理上是散落地连接到不同的物理交换机上的,但从逻辑视图上则简单、清晰很多。
图:LAN和VLAN的不同视角示意图
Network namespace用来隔离包括网卡(Network Interface)、回环设备(Loopback Device)、网络栈、IP地址、端口等等在内的网络资源。对于一个进程来说,这些要素,其实就构成了它发起和响应网络请求的基本环境。
veth全称是virtual Ethernet,而bridge则被视为虚拟的二层交换机。
Veth,bridge和network namespace的搭配为在一台主机上搭建不同的子网成为可能。比如下图中,在一台机器上先虚拟出一个二层交换机,再通过veth将位于各个network namespace中的子网络连接到一起。K8s官方内置的CNI插件Flannel,就是完全基于network namespace veth bridge vtep来实现所谓的Overlay模式。
图:基于Network namespace veth bridge所构成的Linux networking
但必须老实说,无论是Docker还是K8s,都没有对网络虚拟化做过多的贡献。下图揭示了这样的残酷事实。
基于iptables欠下了大量的技术债。为了实现service机制,kube-proxy会监控API server上面的service 和 endpoint的修改,并对各个Node上的iptables做相应的修改,以便实现virtual service IP:Port到Pod IP:Port的转变。这样的实现方式使得每个Node上iptables rule的条目会迅速膨胀到上万条,从而导致网络包流经网络栈的时候速度变慢。如果我们将网络栈比作河道,网络包比作水流的话,rule条目的急速增加就像是在本是宽敞的河道里插入了一个又一个拦污网,它们在有效过滤网络包的时候,也显著降低了流水的速度。
我忍不住吐槽一下,每一个Pod的创建和删除操作都会使得kube-proxy在所有的node上更新iptables。K8s的问题列表里面曾经记录了一个问题#44613:在100个Node的K8s集群里,kube-proxy有时会消耗70%的CPU。还有一个更恐怖的对比数据:当K8s里有5k个services(每个service平均需要插入8条rule,一共40k iptables rules)的时候,插入一条新的rule需要11分钟;而当services数量scale out 4倍到20k(160k rules)时,需要花费5个小时,而非44分钟,才能成功加入一条新的rule。可以看到时间消耗呈指数增加,而非线性。
图:K8s默认使用iptables带来的技术债
不过这一困境因为eBPF而出现了转机。2014年,第一个eBPF的patch合进了Linux Kernel。Netflix性能架构师Gregg说eBPF是OS内核近50年来最基础性的改动。以eBPF为核心来布局K8s生态中Networking, Observability和Security等关键设施的初创公司Cilium于2020年11月获得了包括谷歌和思科在内的A轮2900万美元的投资,足以见这样的技术革新意义上的重大。
图:eBPF为核心的Cilium生态
写在最后
如果我们回头看看这么多年IT的发展,会惊奇地发现我们正在努力地将一切物理设施虚拟化、软件定义化。虚拟化无疑是将人和硬件做了解耦合和屏蔽。硬件的突飞猛进如同水流一般不断地注入到底层的资源池子,但又不直接驱动或者影响人类编写软件和使用这些资源的方式方法。
如此,人和硬件的矛盾也就有了缓冲地带。或许人和机器可以从此握手言和,避免第三次软件危机的发生。
借助虚拟化,我们有了软件定义的数据中心、软件定义的网络、软件定义的存储、软件定义的WAN。如今的零信任,更是将传统的企业网络边界推倒,并建构出了软件定义的边界(software-defined perimeter)。
什么,你说元宇宙?哦baby,那更是虚拟化筑构的世界。