我们与Docker编排的故事

jopen 8年前
 

两周前,在 nanit.com ,我们面临一个困难的选择。我们已经知道我们的基础设施在未来会严重的依赖Docker,但是仍然没有决定选择什么工具来进行容器编排。

编排是一个大词,对于在之前没有听过Docker容器的人来说,可能听起来十分模糊,所以让我们来阐释在Docker的世界里,编排到底是什么意思。

运营的改革

所以我们为什么需要编排?编排到底指的是什么东西?这是一个随着Docker的发展而出现的一个新理念,还是在这之前已经存在但只是到了现在才开始变得必要?

在Docker之前的日子里,我们有物理的或者虚拟的机器。每一个机器(物理的或者虚拟的)经常只负责一个功能。比如,让我们看一个WEB站点的典型基础设施:

我们与Docker编排的故事

[图1:经典的网站架构]

我们这里看到的是一个在处于前端的负载均衡器,后面有三个web服务器。服务器与持久化型数据库(MySQL,Postgres等等)和键值型 (KV)存储服务进行通信。这里每一个机器都负责一个单独的,已经定义好的功能。每一个都有一个IP地址和端口,能让我们与运行在上面的服务进行通信。另 外一个值得注意的事情是每一个机器的大小(size):一个键值型的存储可能需要很多内存,较少的CPU,但是我们的WEB服务器可能有更密集的CPU消 耗。为了让各个服务能够以快速且一致的方式对外提供服务,我们需要在每一个机器上进行资源配置。

同样一个网站,在Docker的年代里可能是这样的:

我们与Docker编排的故事

[图2:Docker的网站架构]

我们仍然有相同的组件,但是现在他们组合的方式不一样了。同一个机器上可能存在两个甚至更多,完全不一样的容器。一个容器可能存在于容器1,在之 后会被移动到容器2。机器或者主机的概念再也不存在了。我们不再有一个机器是专门来分配给数据库,或者一个机器分给负载均衡器。我们而是有一个资源池(内 存/cpu/网络),这是一个我们集群所有运行的机器的资源总量。这给我们管理基础设施的方式带来了一些有意思的变化:

  • 1, 服务发现 :如果现在没有机器的概念了,那我们该如何识别服务呢?我们怎么告诉我们的Web容器来连接到可能处于不同位置的DB容器,它可能在同一个机器上,在另外一个机器上,或者不断地在机器之前移动来移动去?在我们不知道负载均衡器处于哪一个机器上的时候,我们 www.nanit.com 的DNS记录该指向哪里呢?
  • 2, 高可用 :我们该如何保证我们的服务,保持运行着并且保持着一定的容量(capacity),比如,我们想我们的容器上至少运行三个web服务器。
  • 3, 资源管理 :我们该怎么保证集群的资源池的使用处于合理利用状态?我们该怎么保证一个即将创建(spawned)的容器有足够的的资源来有效地运行服务?我们也得保证我们没有过载一个单独的机器,同时并且我们没有冷落其他机器?
  • 4, 端口管理 :假如两个需要在同一个主机上占用同一个端口会怎样?比如:Web服务器和负载均衡器都需要占用端口80。我们需要保证我们不在同一个机器上同时运行两个服务。或者,我们可以找到另外一个解决方案,可以让门在同一个机器上和谐共处,即使他们需要占用同一个端口?

我想简单的讨论一下这些话题,并且讲下对Docker的影响是什么,和可用的解决方案。

服务发现

服务发现在Docker早出现之前就已经有现成的解决方案。它们主要是基于机器/主机的范式。如果我们需要一个服务(比如Redis)有一个不变的端点,我们可以:

给这个实例设置一个不变的IP地址。如果这个实例出现故障那就另起一个新的有相同IP地址的实例。几乎所有的云提供商今天都有这样的解决方案(比 如AWS上的Elastic IP)。这个解决方案在Docker时代的没有关联,因为一个单独的IP或者机器可以服务很多不同的组件。

使用一个外部的服务发现服务,如Consul。每一个机器运行着一个代理,然后有一个Consul服务器负责掌管着每一个服务的状态和端点。服务发现通过对每一个服务进行DNS解析来完成。有很多这种方法的改良来和Docker集成,但是要达到完美运行需要大量的的设置。

每一个服务使用一个不变的主机名,或者IP,然后将相关的运行着服务的服务放在其后。每一个启动的机器向合适的负载均衡器注册。一个例子是AWS的Auto Scaling Groups,其正是完全按照这个方案运作的。

正如我们所见,服务发现已经有一些知名的解决方案,但是没有一个对于Docker可以拿来即用。AWS的Auto Scaling Groups针对的是虚拟机,而不是Docker容器,然而虽然Consul可以和Docker一起运行,但是给人感觉从一开始就不是设计来这样玩的。

今天的Docker编排工具通常附带了一些服务发现的能力。

高可用

高可用和冗余,和服务发现,也是在Docker时代之前就存在的问题。例如,AWS的Auto Scaling Groups保证你至少有X个机器运行并保持运特定的服务。它甚至可以通过API或者预先定义的规则动态的调整运行机器的数量。

尽管这个问题对于基于机器的范式来讲是已解决问题,Docker让HA和冗余变得有一点更加有意思:我们通过运行更多的Docker容器来进行伸 缩,而不是启动更多的机器。我么恩可以使用已有的同一个资源池来进行伸缩。这意味着我们的服务伸缩和我们的集群资源伸缩分离开了,向上伸缩或者向下伸缩不 会一定暗示调整资源的数量。我们需要分别伸缩服务和资源。

一些Docker编排的工具有基本的容器横向伸缩的特性。伸缩集群 - 调整对于编排器可用的资源 - 和容器管理是分开完成的。

资源管理

在Docker之前的日子里,我们通过启动新的机器来启动新的服务实例。我们有一些映射记录来从每一个服务到它的CPU/内存的需求,因此我们能确切知道我们需要启动那种机器。

有Docker了就不是这种情况了:我们需要把我们的容器调度到已存在的机器上。我们的资源池或多或少的静止的并且编排工具需要把容器集以优化的 方式适应到机器的集合中。这听起来只是一个简单的优化问题,但是实际上不是因为容器集在给定的时间是动态变化的,一方面需要将容器平等地分散开来,一方面 需要留下足够的资源以防我们想启动一个需要大量资源的容器,我们需要对此做出权衡。

让我们把图标2中的基础设施拿来作为例子看一下。假设这是每一个服务所必须的资源表格:

Web - 3

Load Balancer - 1

Persistent DB - 2

KV DB - 1

三个机器的每一个都有完全一样的资源容量,都是6。我们有至少两个明显的方法来布局这些容器:

  • 松散布局(Sparce Layout):这正如图标2展示的一样:
我们与Docker编排的故事

[ 图三:松散布局 ]

在括号里面我们能看到每一个机器的资源使用情况。

这个布局看起来相当好 - 所有的容器都在实例之间分散开来,并且我们不会太拖累一个单独的实例 - 每一个都有空闲的资源。但是这个设置也有一个问题,假如我们想讲我们的web服务伸缩到4个实例应该怎么办呢?我们没有一个哪一个实例有至少3个空闲的资 源。所以可能这对于我们的容器不是最好的布局?

  • 这可能是另一个方法来安排我们的容器:
我们与Docker编排的故事

[ 图四:紧密布局(紧凑布局)]

现在机器1和机器2都满了 - 他们完全没有空闲的资源。这让机器3十分的空闲,可以在需要的时候运行每一个服务。但是像这样布局我们的容器有可能吗?你能看到这里存在的问题么?

端口,当然是这个问题。在机器2上,我们有两个web服务,它们都需要占用80端口。这意味着我们不能将两个web容器放置到同一个机器上?这个问题直接把我们带到下一节,端口管理。

端口管理

不管你怎么努力,同一个端口不能在同一个机器上同时被两个进程占用。当你提前知道需要在机器上运行哪些服务的时候,你只需要保证不会发生端口冲突就行。对于Docker的基础设施,显然你没有这种福利待遇 - 服务会更换机器,并且会一直重新调度。

Docker编排框架通常会选取两种立场之一:第一个是是避免需要占用相同端口的容器调度到相同的机器上。这是一个简单的解决方案,但是我们看到 这意味着这对我们如何布局容器附加了很紧的约束。这可能会带来一个十分棘手的场景,编排器仅仅因为端口的限制而不能调度一个容器。

另外一种容器编排工具采取的方法是指定一个分配好的随机端口到每一个容器上,然后它们自己来路由流量。如果我们将图标4中的机器2拿来做例子,这 意味着每一个web服务有一个端口打开,这会直接路由到web服务实例。主机上的端口3000会到第一个web服务实例,端口3001会到第二个实例。

总结

如我们所见,我们已经在Docker之前做过编排,但是与Docker编排容器不同,我们过去编排的是虚拟机。一些在编排的世界的问题,如服务发 现和高可用,已经有现成的解决方案,我们只需要对其做一些调整就能和容器和谐共处。而其他一些问题,如资源管理和端口管理,在Docker来到后是全新的 问题,这需要一些创新性的解决方案。

所以编排到底做些什么?他会将我们的容器调度到集群中的机器上,保证分配合理,同事保持该可用,并且有对服务发现基本的支持。我们选择的编排框架将会决定我们如何管理我们的集群和如何做运维工作。这实质上是我们所有构建的工作所围绕的核心运维工具。

已经有一些编排的框架了:AWS ECS,Kubernetes,一些Docker原生的解决方案等等。

在下一篇文章里,我们将会深入分析ECS和Kubernetes,并且试图让你理解为什么在nanit.com,我们选择了后者。