理解云计算的实质,Kubernetes就好理解了,它提供的是容器云。在私有数据中心,服务运行在物理主机的Host OS上,云计算场景下运行在虚拟机上,k8s场景下应用运行在容器内,容器替代了虚拟机。
在Linux上,容器技术的底层实现机制是Linux Kernel的Control Group(CGroup)。它维护各种各样的名字空间(进程ID,IPC,网络等等),并按照Group管理系统资源分配和释放。可以在每个CGroup上施加限制(CPU、内存、IO等等),确保资源的使用配额。所有的容器可以有自己的资源和文件系统镜像。但是共享内核、容器比虚拟机开销小,性能上占优势,虚拟机比容器隔离性(isolation)好,更容易保证不同租客不相互影响。
容器技术有多种选择,Docker和Singularity是两种比较流行的选择。它们的底层机制是共通的,但是在镜像管理上有差别。Docker通过层(Layer)与层的叠加优化镜像空间的利用率,通过Overlay2文件系统实现只读共享和写时拷贝。Singularity采用不同的文件格式(SIF),通过压缩技术优化空间利用率,镜像主体部分只读共享。无论Docker和Singularity都不适合容器内部大量读写,所以容器需要和外部存储结合使用,容器镜像承载只读的二进制程序和软件包,外部存储承载程序的数据读写。
k8s源于Google的开源项目Kubernetes。尴尬的是Google内部真正使用的系统叫Borg,不是k8s。有一种野史说法是,Google将Borg中的一些边缘技术通过Golang实现,变成了Kubernetes。当然Google的说法是Kubernetes吸取了Borg的优秀理念。Google将一个自己并不使用的技术开源给大家使用,可谓“纯粹的奉献”。
大家了解Google是因为它闻名遐迩的三件套GFS、MapReduce和BigTable。大概从2003年开始,这三件套发表了论文,但没有开放源代码,大家只闻其名,不见其形。后来有位工程师Doug cutting读了论文,用Java实现了它们,就是HDFS、Hadoop和HBase,Hadoop系列从此登上了历史舞台。在大致相同的时间,Google已经不再使用这些旧的三件套,演化成了更高级的系统(Cloud Dataflow),Cloud Dataflow又引发了开源项目Apache Beam。
笔者在百度的内部培训资料和会议中首次听说了Borg。百度挖了Google做基础架构的一些牛人,内部介绍了Google的系统,印象最深的是它的服务质量控制(Qos)。因为Map-Reduce,BigTable或者GFS等这些系统都运行在Borg上,Map-Reduce这种任务是离线计算,网页搜索则是交互式服务。离线追求带宽,搜索追求延迟,这是一对矛盾。带宽对资源的无限制利用必然导致响应变慢。Qos技术的目标就是解决这类问题(当然至今解决的也不太好)。据说Borg在一定程度解决了这个问题。当时Linux内核还没有CGroup和容器,Google自己从一个老的内核版本撸出了同样的东西。笔者对Borg是很佩服的,当然啦,Kubernetes不是Borg,至少没有想一睹为快的Qos。
回过头来问一句:Google开源了什么?答案很尴尬。三件套和Borg都没有开源,Kubernetes是仿品,Tensorflow是社区阉割版,LevelDB不清楚。不得不说,通过开源建立生态,培养用户,吸引企业用户上Google云去获得非阉割版本的性能是很高明的商业策略,这才是真正巨头的玩法。
k8s允许将容器部署作为服务。它提供pod、service等机制,帮助用户构建跨多个容器的应用服务、跨集群调度、扩展这些容器,并长期持续管理这些容器的健康状况。因此k8s天然适合部署有状态的服务。除了容器编排,它的核心功能在于根据丰富的策略自动管理用户容器服务。
作为服务承载平台,这是否就够了呢?答案取决于服务是什么,以及对服务质量的要求多高。举个例子:假设一个数据服务需要访问存储,把服务做成pod通过k8s运行在机器1上。这时候假设探测到机器1掉线,k8s的controller将这个Pod迁移到机器2上运行,继续提供服务。乍一看这个挺好的。做过企业级服务的同志们可能会问一个问题:机器1真的掉线了吗?原来那个pod没有继续修改存储中的数据,把数据写坏吧?单凭k8s是做不到这一点的。首先机器掉线和网络故障是难于区分的,机器1完全可以与controller失联,同时与存储继续交互。新的数据服务启动后,确保旧的服务不再访问数据,从而确保数据完整性,这种机制叫做Fencing。大家可能会说:这有点鸡蛋里挑骨头的意思啊?不好意思,笔者二十年前毕业参与企业级高可用数据存储系统开发的时候,这是一个要求必须考虑的问题。换句话说,这是企业级服务的最基本要求。2B不仅仅是说把产品卖给企业,而是要达到企业级应用的数据保护要求。
HPC类的计算业务往往需要的是运行一个作业,作业结束后容器退出,这和有状态的服务运行机制不一样。HPC一直以来是k8s的短板,用户如果需要在k8s上跑HPC业务,例如传统的SGE(Sun Grid Engine),PBS,SLURM等等,需要将SGE、PBS等本身作为容器运行。这种方式在资源分配上是静态的,对用来讲使用的还是老的工作模式,并没有将用户的程序容器化。华为推出了基于k8s的Volcano系统,声称优化了HPC类业务。笔者研究了一下Volcano运行MPI的方式,用户需要构造两类容器模版:第一类容器模拟传统的计算节点,运行sshd服务。第二类容器运行“mpiexec”命令执行一个MPI程序。
Kubernetes启动指定数量的两类容器,去执行MPI程序。这种方式显得很古怪,它混淆了传统HPC生产过程中的用户和系统管理员这两种不同的角色。传统的HPC计算,系统管理员部署好系统,用户通过“mpiexec”或者“mpirun”交互式运行程序。用户无需知道系统细节。Volcano方式下,用户必须了解kubernetes,而且不是交互式的,有点生搬硬套的感觉。在HPC场景下,k8s本质上只是用容器替代了虚拟机。对用户来说,虚拟机反而更逼真的模拟了以前的计算集群,使用起来反而更自然。
相比网站和简单的企业应用服务,HPC类的计算更专业,它要求更专业的基础架构。
谈到k8s,不得不提云原生这个概念。事实上,云原生没有确切的定义,它的概念一直在发展变化之中,包括概念的提出者Pivotal公司的Matt Stine都换了好几个说法。云原生是一种构建和运行应用程序的方法,是一套技术体系和方法论。
云原生是一个组合词,“云”表示应用程序位于云中,而不是传统的数据中心;“原生”表示应用程序从设计之初即考虑到云的环境,为云而设计,充分利用和发挥云平台的弹性优势。2015年云原生计算基金会(CNCF)成立,把云原生定义为容器化封装+自动化管理+面向微服务。到了2018年,CNCF又更新了云原生的定义,包含了服务网格(Service Mesh)和声明式API。
为什么Kubernetes提出了云原生这个概念呢?因为同最初的Borg一样,k8s设计出来承载的是服务,它暗含有两级调度的理念,即资源管理与应用服务分开。k8s只有资源管理,它需要应用开发者主动去对接它,才能提供完整的端到端的应用服务。一言以蔽之,它希望成为标准。这也可以理解各大云厂商为什么都追捧这个概念。因为k8s架在云上,用户的应用直接在上面跑,它可以卖资源啊。应用对接了这个标准,它就能够以统一的方式处理管理用户应用,承接用户上云的边际成本被极大程度的降低了。
预测未来总是困难的。历史的经验告诉我们,江山代有才人出,各领风骚数百年。弄清技术的来龙去脉,城头变换大王旗的时候,不至于有不切实际的期望。