【编者的话】本文是Google容器技术系列博客的第二篇,第一篇中大致介绍了容器,Docker,以及Kubernetes的基本概念,这篇文章中对Kubernetes进行了相对深入的介绍,作者从Kubernetes中的一些核心概念入手,介绍了Google在构建容器集群管理系统中的一些核心要素。
在上周,来自“Google 云平台全球解决方案小组”的专家MilesWard为我们做了关于容器技术的系列博客的开篇,在前一篇博客中MilesWard大体介绍了关于容器,Docker以及Kubernetes的一些基本概念。如果你还没有阅读先前的文章,建议你先进行一些了解,这样而可以补充一些相关的基础知识,也将会帮助你更好的理解本文中介绍的内容。
这周,我们请来了Google的高级工程师,同时也是Kubernetes项目的核心成员Joe Beda。他将从更深的层面上为我们介绍Google在容器技术使用过程中的一些核心技术概念。这些概念也是Kubernetes创建基的础,理解这些概念也可以帮助我们更好地理解这一系列博客的后续文章。
最近一段时间,容器系统相关技术迅速崛起并受到了广泛关注(比如Docker)。容器技术已经带给我们很多令人兴奋的实践。容器的打包,迁移,并且在不同的环境中运行服务的能力,可以方便地让我们管理自己的服务,从另一个角度上,这也帮助我们提高了服务的“流动性”。但是,随着用户不断将他们的服务迁移到生产环境中,新的问题也随之出现,比如具体哪个容器运行在哪台服务器上,怎样同时运行大量数目的容器,容器之间如何方便地进行跨主机通信等等,正是这些问题的出现,促使我们构建了Kubernetes。Kubernetes是一个来自Google的开源的工具包,可以帮助我们解决这些新出现的问题。
正像我们在上一篇文章中所讨论的那样,我们认为Kubernetes是一个“容器集群管理器”。许多技术人员习惯把这个领域的项目称之为“编排系统(orchestration systems)”,他们或许是想将集群的管理工作比作是交响乐的编曲。但我从不这样理解,交响乐(Orchestral music)编曲工作通常是提前根据旋律和配乐被细致地编排好,并且在表演之前,每个表演者的任务已经被明确指定好。而Kubernetes集群的管理过程,更像是一个升级版的爵士乐即兴表演。它是一个动态的系统,可以根据输入的信息和当前系统的运行环境实时做出反应。
所以,我们不禁要问,到底是哪些因素帮助我们构建了一个容器集群? 是否可以这样描述一个集群系统:这是一个动态的系统,在系统中可以放置多个容器,这些容器的状态以及容器之间的通信都可以被系统所监控。事实的确如此,一个容器集群正是由一个监控系统和一系列计算结点(不论是物理服务器或者是虚拟机)所组成的。在这篇文章的剩余部分,我们会着重探讨三方面的话题:容器集群由什么组成,容器集群应该怎样应用到我们的实际工作中,以及组成容器集群的各个要素又是怎样在一起发生作用的。此外,基于我们已有的经验,一个容器集群还应该包含一个管理层,我们将继续探索这个管理层是如何实现的。
在Google,我们所构建的容器集群需要符合一系列常见的要求:集群总是可用的,可以被打补丁并且被升级,集群按需扩展,集群相关指标容易被测量(easily instrumented)和监控等等。根据容器本身的特性,服务可以通过快速、容易地方式进行部署,并且还可以将整个服务分成许多小的部分,以进行更加细粒度的操作。虽然容器化的操作一定程度上为我们提供了方便,但是为了满足我们提出的这些目标,我们仍然需要一个系统的解决方案来管理容器集群。
在Google过去的10年间,我们发现一个容器集群管理器就可以满足席上这些需求,并且这个集群管理器还可以为我们提供许多其他的好处:
现在,我们明白了,当前我们所做的事情还是很有意义的,所以让我们一起探索构成一个优秀的集群管理系统到底需要哪些要素,以及如果你希望认识到以集群的方式运行容器的优势,应该对哪些方面进行特别关注。
要素一:动态容器分配想要构建一个成功的容器集群,你需要一点点“jazz即兴表演技巧”。你需要将你的工作任务任务打包成一个容器镜像并且明确地说明你的意图,说明要如何运行容器以及将要在哪里运行容器。集群管理系统最后会决定到底你的工作任务在哪里运行,我们把这个过程称为“集群调度”。
这并不是意味着工作任务会被随机地分配在计算结点上。正相反,在工作量被分配的时候,需要遵循一系列严格的限制,从计算机科学的角度来将,这会使得集群调度变成一个有趣而又困难的问题(注释1)。当需要调度的时候,调度器确定要把你的工作量放到一个有足够剩余空间(例如CPU,RAM,I/O,存储)的虚拟机或者是物理服务器上。但是,为了满足可靠性的目标,调度器可能需要把一系列的任务以跨主机的形式进行分配或者按一定的顺序来排列(racks in order),以此来减少相关运行时发生故障的可能性。或者一些特殊的任务会被分配在一些有某些特殊的硬件(比如GPU,本地的SSD等等)的机器上。调度器也会根据不断变化的运行环境作出反应。并且应该在任务运行失败的时候重新对任务进行调度,增加/缩小集群规模以提高效率。为了实现这个目的,我们鼓励用户避免将一个容器固定在一个服务器上。有些时候你可能需要指定“我想让某个容器在某个机器上运行”但这种情况应该比较少见。
下一个问题是:我们进行调度操作的具体对象是什么?最简单的答案就是使用单独的容器。但是在某些时候,你希望有一系列的容器在一个主机上以合作的方式在运行。例如一个数据加载器,需要一个数据库服务一起运行或者是一个log compressor/saver进程同样需要与一个服务来搭配运行。运行这些服务的容器通常需要被放在一起,并且你需要确保它们在动态配置的过程中并没有被分离开。为了实现这个目的,我们在Kubernetes中引入个一个概念:pod。一个pod是一系列容器的集合,这些容器在一起构成一个单元在服务器(也可以被称作Kubernetes结点)上被配置和调度。为了使得每次可以配置多个pod,Kubernetes采用一种可靠的方式将许多工作打包在一个结点上。