企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持知识库和私有化部署方案 广告
# 第13章 调度 到目前为止,我们已经看了很多在单台主机上运行Docker的案例。然而,你的基础设施必定会在某一刻增长到单台宿主机不足以承载其服务的程度或者说你想要在一些高可用的环境下运行Docker容器。这时候就需要将它扩展到多台机器上。这会带来很多的挑战,不仅在于多宿主机的Docker网络配置,更重要的是容器的编排和[集群](https://en.wikipedia.org/wiki/Computer_cluster)的管理。 如果没有合适的集群和编排的管理工具,你会发现要么是自己被容器基础设施所绑架,深陷于繁琐的日常事务中,而不是管理和操纵它,要么便是重复造轮子,最终写了很多自己的容器管理工具。事实上,Docker已经意识到了这方面的需求并投入了巨大的精力到它的[网络模型](https://blog.docker.com/2015/06/networking-receives-an-upgrade/)的改造当中,而且还创建了一个名为[Swarm](https://github.com/docker/swarm/)的Docker原生集群管理工具。 关于集群管理这个话题,如果要深入讲解的话可以单独写一本书了。这里面包含了大量需要关注的内容:如何增删宿主机,如何确保它们的健康状态,以及如何伸缩扩展来管理负载等。在本章中,我们将把重点放在集群管理和容器编排中一个特别关键的部分:**调度**。 作业调度的基本理念很简单,而且它的历史可以追溯到大型机与高性能计算(HPC)。与其把计算机的资源投入到各自特定的工作负载,还不如将整个数据中心看做是一个巨大的计算和存储的资源池,然后只需要在上面设置需要运行的计算作业即可,就像它是一台巨大的计算机一样。事实上,Mesosphere作为Docker领域里基础设施的一员,它们的主打产品正是“数据中心操作系统”。 现在,让我们先把具体实现方面的内容放一放,把重点放在根本问题上。为了更好的理解Docker主机集群里容器调度问题的本质,我们需要先做一些铺垫工作,并且了解一下调度的真正意义。 如果给调度问题作一个30秒的简单概括,我们就可以说调度问题是为一些计算任务分配一组硬件资源(如CPU、内存、存储和网络容量),在满足任务需求的同时完成这些任务。 可以影响调度决策的因素有很多。执行成本显然是需要考虑进去的,但是我们也需要考量延迟、吞吐量、错误率、完成时间以及服务的交付质量等因素。因此需要根据重要程度(面向用户)为任务排优先级,或者在调度一些带有明显负载波动的关键作业时安排较少的时间窗口。 每个任务可能由多个进程组成,对应的是不同的数据存储和网络需求,网络需求也许还有一定的网络带宽或者地域要求,因此用户得把它们放在靠近彼此的区域,由于可能还会有一些冗余的要求,所以用户还得在其他地方存放几个冗余的副本,如放到另一个数据中心或者可用区。 应用所需的资源量在不同时间点会因为负载的不同发生变化,由于它们做过扩容和缩容,因此需要根据其扩容或缩容做重新调度。此外,随着时间的推移,新的应用也将被部署到环境里,因此必须定期重新考虑调度策略。有些任务可能只是临时执行的,而有些则是长期运行的,如果基于生命周期来分配资源也能产生一定的收益。 度量和监控是关键:这是吞吐量及延迟与需求之间的博弈,资源利用率和容量的规划很重要,但是同样也得考虑成本和预算,还有那些即将产生的价值。当用户把调优作为最终目标(如最大化吞吐量、最低延迟等)的时候,调度问题本身就成了一类[调优问题](https://en.wikipedia.org/wiki/Program_optimization)。 通过几十年来的不断优化,操作系统这一层的调度已经做得相当出色了,因此在绝大多数情况下,我们所做的调度都是基于单台主机的。就目前而言,跨主机的调度还远未成熟。 与虚拟机(VM)的调度不同,容器通常都不固定大小,它们随时可以修改自己的内存使用量。这使得容器的调度比将一些固定大小的虚拟机装配到物理裸机的[**装箱问题**](https://en.wikipedia.org/wiki/Bin_packing_problem)要困难得多。 最优调度是一个非常棘手的问题,只有在一个非常大的规模下投入巨大的工程人力才可能看到收益。 正如我们之前提到的那样,与单台主机的调度问题相比,跨集群之间的调度会更加复杂。单台主机的调度重点关注少数CPU上运行大量线程和进程的问题,其目的在于避免资源分配不均,保证单个进程不会运行太长的时间,并且确保交互进程可以在超时之前命中资源。除此之外,它还主张一些资源利用方面的公平调度,如引入IO带宽等概念。在[NUMA](https://en.wikipedia.org/wiki/Non-uniform_memory_access)系统中,隔离性也会被考虑进去,但它不是主要的驱动因素。 在单个或者多个数据中心中,乃至于在云环境下,该问题的规模将会扩大好几个数量级。延迟会变得至关重要,主机在执行作业时会出现异常,并且每个任务的时间跨度都会相应变长。此外,任务的负载强度方面会有很大的差异,有Hadoop的[MapReduce](https://en.wikipedia.org/wiki/MapReduce)作业,其工作量一般覆盖到整个集群;也有[MPI](https://en.wikipedia.org/wiki/Message_Passing_Interface)类型的作业,它们需要在任务开始之前准备好相应的可用资源;传统的Web应用,这些通常只要提供的容量超过它们所需即可。 因此,一个集群里容器的调度真的是一个很令人头疼的问题,这涉及到了异构环境和不同的工作负载两方面。这个世界不存在“一刀切”的完美方案,因此各类机构创建了各式各样一整套的调度工具,它们之中的绝大多数均是从自身的角度出发致力于解决一类特定的负载问题。 [Mesos](http://mesos.apache.org)是由[加利福尼亚大学伯克利实验室](http://www.berkeley.edu)的研究人员开发的一款特别有意思的解决方案,该实验室给业界创造了太多的惊喜,而这次它依然没有让业界失望。Mesos以一个高级抽象的概念实现了所谓的二级调度,即Mesos本身在计算集群里提供了一层对底层计算资源的抽象,随后再把它们提供\[或者说调度\]给任意数量的在它之上运行的\[异构\]框架服务。然后每个框架再根据它自己特定的应用场景实现属于它自己的一套任务调度策略。 这意味着Mesos只维护集群的资源,然后基于优势资源[公平算法](https://www.cs.berkeley.edu/~alig/papers/drf.pdf)决定把哪些资源分配给哪个框架服务。重要的是Mesos将实际的任务调度委托给了特定的框架服务,就这一点来说自有其利弊。 这一模型为用户提供了很大的灵活性,用户无需再操心底层的计算资源的处理,而可以根据特定的应用场景专心地编写属于自己的、合适的任务调度器。从另一方面来说,用户无法看到整个集群状态的简要概况。除此之外,在Mesos认为资源分配是相对公平的情况下,用户也无法在自己需要它们的时候强制让Mesos为用户分配更多的资源。Mesos评估资源公平的标准是基于框架启动时提交给Mesos资源分配器的一些指标。 当用户运行了大量长期运行的任务时事情可能会变得比较麻烦,它们可能会占用大量的计算资源并因此导致那些尚未调度的任务因为没有资源消费而阻塞。换句话说:Mesos在用于同构的批量处理型应用时表现得最好,这一点已经成功地被像[Apache Spark](https://spark.apache.org)这样的项目所验证,它在Mesos之上实现了一个异常快速的数据处理框架。 运行在Mesos之上的框架服务,它们所调度的计算任务实际上是由Mesos称之为容器类[\[1\]](part0019.xhtml#anchor131)(Containerizer)的进程来调度的。Mesos自0.20版本起,便在容器类的选择方面加入了对Docker的支持。如今Mesos仍然还没有完全实现对Docker的全面支持,但是该版本实现的功能已经足以用来调度一些复杂的Docker任务。 [Twitter](https://twitter.com)证明了Mesos可以成功驾驭大规模基础设施下的异构应用。他们已经为长期运行的服务开发了一个叫[Aurora](http://aurora.apache.org)的框架,它支持资源抢占和其他许多实用的功能。业界另一个很受欢迎的框架是由[Mesosphere](https://mesosphere.com)开发的[Marathon](https://mesosphere.github.io/marathon/),它提供了一个远程的REST API来方便用户编排。 如果想了解有关Mesos的更多详细内容,不妨去读一读它的这篇[白皮书](http://mesos.berkeley.edu/mesos_tech_report.pdf)。 与Mesos相比,Kubernetes算是一个非常年轻的产品,它2014年6月才开始启动,在2015年7月发布了1.0版本。然而,它有一段很长的发展历史,实际上它是Google自Omega和之后的Borg这样的内部调度系统创建的一个开源版本。 这篇由Google发表的关于[Borg](http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43438.pdf)的论文简要讲述了该产品宏大的发展历程,还有Google是如何在内部基于容器的架构来完成调度的。凭借如此宏大的背景,Kubernetes自然也引起了很多人的注意,而如今它已经被CoreOS和Red Hat这样的企业开发和采纳。 Kubernetes覆盖了应用部署涉及的整个范畴:调度、更新、维护和扩展。它支持Docker容器,并且将它们归类到名为“pod”的一个个小集群的组,“pod”共享一个网络命名空间,如此一来它们之间的网络通信就变得简单很多;这也就意味着一个小组的容器可以共同组成一个单独的应用,如Web应用和Redis存储。 使用Kubernetes的API可以管理容器、pod、备份、卷、私密信息、元数据以及所有其他组件,而且支持多方面的用途,这一点也正是Kubernetes的主打功能。由于该API的设计原型源自于Google的早期项目,因此可以说它已经经受了很好的实际考验。 Kubernetes目前还不是一个易于安装的组件。业界比较清晰的教程当属Red Hat的Kubernetes[初学向导](https://access.redhat.com/articles/1353773)了,然而这里面仍然有很多手动配置的步骤。引入Docker可能会减轻必要的配置管理的工作量,但是为它构建一个完整的调度环境仍然需要花费很大力气。 到目前为止,最容易上手的体验方案当然还是使用Google容器引擎的实现。有一篇必看的[Wordpress入门教程](https://cloud.google.com/container-engine/docs/tutorials/hello-wordpress),快速、简单、易于上手,在部署完成时它将会为用户安装和配置好Kubernetes标准的“kubectl”命令行工具,如此一来用户便可以在集群里轻松运行属于自己的Docker容器了。 调度系统可以说是平台即服务(PaaS)的一块基石,而Kubernetes正成为这一领域里的核心成员。 Red Hat的[OpenShift](https://www.openshift.com)是一个开源(并且已经托管了)的平台即服务的产品。在它发行版本3之前它可以说是一个相当于标准的开源版的“Heroku”PaaS产品,但是版本3是在[Atomic项目](http://www.projectatomic.io),特别是Docker和Kubernetes的基础之上做了一次[**完全重写**](https://blog.openshift.com/openshift-v3-platform-combines-docker-kubernetes-atomic-and-more/),Atomic项目本身是Red Hat应对CoreOS的举措,它是其他一大堆开源项目的载体。 OpenShift于2015年6月发布后全面上市,当你希望得到的是一个完全成熟的PaaS时,它会是一个很好的切入Kubernetes的方法。 OpenShift是怎样适配容器的?为了获得一个完整的了解,我们专门采访了Red Hat公司OpenShift项目的首席工程师Clayton Coleman先生。 **记者**:“OpenShift v3是否真的是一款自吹自擂的产品又或者说是一堆开源项目打包在一起的可以随时被替换掉的‘杰作’?显然在某种程度上来说它是的,但是我更感兴趣的是它是如何在面向你的客户的产品以及面向你的开发过程的工具两者间协调工作的。” **Clayton Coleman**:“OpenShift是一系列建立在Kubernetes之上的部署、构建和应用生命周期管理工具的集合。对于每个单独的组件,我们尽量不去采用那些明摆着非常偏门的技术。例如,针对边缘路由[\[2\]](part0019.xhtml#anchor132)和代理我们写了一个通用的模式,它兼容了Apache、Nginx、F5和许多其他的可配置的负载均衡器,但是在默认情况下我们还是采用最常见的HAProxy来配置。我们自然希望可以从多方途径获取支持,但是我们目前主要还是采用Git。Kubernetes正在加入对Rocket容器引擎的支持,因此OpenShift可以利用这一点,也许最终可以替代Docker。我们试图成为众多不同的概念和开发工作流之间关联的纽带,而不是假定所有应用都是一个自顶向下的生命周期。” **记者**:“PaaS如何才能在企业里真正落地?谁是最早的先驱,在此过程中又作了哪些决策?容器和Docker的加入会加快推进的步伐吗?显然就目前而言,它是一个很大的革新,并且这中间变化的过程也相当有趣。” **Clayton Coleman**:“企业内的PaaS一般都是由大量的内部应用的部署成本所驱动,他们希望借助PaaS可以将部署模式和流程标准化,并且希望可以一定程度地提高基础设施和工具的灵活性。我想Docker带来的更多的是自下而上的变革,原因在于它是一项低门槛的技术,对于一些简单的任务它可以完成的很好。我们已经看到许多企业将Docker应用到了平常频繁接触的操作流程里,在这里运维和开发团队在以镜像为原子单位的应用部署中受益。当然,它也有不利的一面。在Docker引入容器带来巨大的提升的同时仍然需要实施大量的定制/脚本化的流程,相比之下我们认为引入更大规模的集群管理模式(通过集群管理/PaaS工具)可以带来更大的收益(一个是呈倍数级的改善,一个则是附加增量形式的迭代)。” **记者**:“你把Kubernetes里的pod模型描述成一个微型的虚拟机,例如,一组应用通常是通过磁盘和Unix套接字在本地进行通信来协同工作的,而不是通过远程的网络IO传输。有没有什么工具可以用来做这种结构的现有应用的迁移,将它们按照现有的[Red Hat容器安全标准](https://securityblog.redhat.com/2015/04/29/container-security-just-the-good-parts/)的规格改造成具备更高安全性的标准结构?所以,你有没有看到过一个现有应用的实际迁移的案例,或者说业界有没有大规模在PaaS上运行新的应用的案例?” **Clayton Coleman**:“如今我们所看到的结构是新旧模式之间[\[3\]](part0019.xhtml#anchor133)的分裂,因此选择其中任何一种应用模式都会趋向于在某方面设计的过分简单化,这就导致它在另外一种模式下很难正常运转。一个好应用的组成应该比‘仅仅只是一个容器’要复杂得多,并且它们通常需要抽象的是共享的磁盘资源、本地网络,或者是不应该耦合到主容器里的‘侧舱’形式的客户端。因此从第一天萌生组建它的想法时你就需要建立一个本地聚合的概念来帮助将新旧应用模型有机的结合到一起。” **记者**:“不可变基础架构和Atomic项目都是时下非常新兴和热门的,在你看来,对不可变基础架构的市场需求有多大?你如何看待这个趋势?” **Clayton Coleman**:“为了使得基础架构的不可变性真正落地,你必须以一种可重复的方式来不断地实施变更。新的不可变镜像的流动完全取决于一个自动化的且易于理解的构建流。不可变基础架构的优点是减少了重现问题或者跟踪故障时需要理解的变量的数量,但是反过来,不可变基础架构本身的成本在于将一切事物自动化。” **记者**:“其他的一些厂商似乎在推容器遗留的一些东西,即在一个完全可变的系统里运行多个应用,而Red Hat似乎是想推动一个应用对应一个容器的结构,必要的话再围绕着构建一系列的pod。这样的描述正确吗?这是否是由客户需求驱动的?” **Clayton Coleman**:“有时候,构建一些较复杂的容器是很有必要并且也很有价值的。一般来说,减少复杂度是一个很合理的做法,但是常常会有一些实际的需求必须通过一些复杂的容器来满足,而我们仍然可以从围绕着它们所构建的弹性基础设施中受益。我认为容器化的基础设施所取得的收益在很大程度上取决于你所处的环境将责任域切分成多个子组件的实现能力(这便是人们提倡的SOA,或者说最近比较热门的微服务概念)。但是这仍然不能排除不存在大的、可变的,或者复杂的容器。” 在第14章里,我们将介绍Docker中的服务发现,它可以使一个计算机网络上的任意服务都可以找到它需要通信的其他服务。 - - - - - - [\[1\]](part0019.xhtml#ac131) 它是Mesos Slave节点的一部分,可以将其理解为是Mesos中任务的容器。——译者注 [\[2\]](part0019.xhtml#ac132) 将客户连接到因特网的路由器被称为边缘路由器。——译者注 [\[3\]](part0019.xhtml#ac133) 即把Docker当做简单的微型虚拟机和完全容器化的应用架构两套模式。——译者注