很早之前,就有关于“每台机器(machine)应该有多少个服务”的讨论。在我们继续之 前,应该找一个比“机器”更好的术语。在前虚拟化时代,单个运行操作系统的主机与底 层物理基础设施之间的映射形式有很多种。因此,我倾向于使用“主机”(host)这个词来 做通用的隔离单元,也就是能够运行服务的一个操作系统。如果你直接在物理机上部署, 那么一台物理机映射到一台主机(在当前上下文中,这个词可能不完全正确,但确实也找 不到更好的了)。如果你使用了虚拟化,单个物理机会映射到多个独立的主机,并且每个 都可以包含一个或者多个服务。
所以在考虑不同的部署模型时,我会使用主机这个词。那么每台主机应该有多少个服 务呢?
我有自己倾向的模型,但要考虑多个因素,来决定哪个模型最适合你。需要注意的一点 是:某些决定会限制可用的部署方式。
6.9.1单主机多服务
如图6-6所示,在每个主机上部署多个服务是很有吸引力的。首先,从主机管理的角度来 看它更简单。在一个团队管理基础设施,另一个团队管理软件的模式下,管理基础设施团 队的工作量通常与所要管理的主机量成正比。如果单个主机包含更多的服务,那么主机管 理的工作量不会随着服务数量的增加而增加。其次是关于成本。即使你有一个能够提供一 些配置和更改虚拟主机大小等服务的虚拟化平台,虚拟化的基础设施本身也会占用一部分 资源,从而减少服务可用的资源。在我看来,上述这些问题都可以使用一些新的技术和实 践来解决,后面马上会讨论到。
在单个主机上部署多个服务,会增加对单个服务进行扩展的复杂性。如果一个微服务处理 的数据很敏感,底层主机的配置也可能会有所不同,或者干脆把这台主机放置在不同的网 络中。把所有东西放在一台主机上意味着,即使每个服务的需求是不一样的,我们也不得不对它们一视同仁。
我的同事Neal Ford提到过,很多关于部署和主机管理的工作实践都是为了优化稀缺资源 的利用。在过去,如果我想要加一台主机,唯一的选择就是买或者租一台物理机。但采购 的时间通常都比较长,而且财务上的投入也比较大。我的很多客户都是两三年才添加并配 置一次机器,在这个时间点之外想要添置新机器是很困难的。但是按需计算平台的出现大 大降低了计算资源的成本,而虚拟化技术的革新,也使得内部基础设施的搭建更加灵活。
6.9.2应用程序容器
如果你对基于IIS的.NET应用程序部署,或者基于servlet容器的Java应用程序部署比较 熟悉的话,那么应该非常了解把不同的服务放在同一个容器中,再把容器放置到单台主机 上的模式,如图6-7所示。这么做的初衷是使用容器来简化管理,比如对多实例提供集群 支持、监控等。
第一个缺点是,它会不可避免地限制技术栈的选择(只能选择这种应用服务器)。你只能使用一种技术栈。除此之外, 它还会限制自动化和系统管理技术的选择。后面马上会提到,管理多台主机最常用的方式 就是自动化,所以这种选择上的限制会造成双倍的伤害。
我对某些容器提供的特性也有质疑。它们中的很多实现,都在兜售通过集群管理来支持内 存中的共享会话状态的能力,而这无论如何都是应该避免的方式,因为它会影响-服务的可 伸缩性。当我们考虑在微服务世界中使用的聚合监控时,它们提供的监控能力又难以支 撑,第8章会对此做更多讨论。其中的很多容器启动时间也特别fe,这会影响开发人员的 反馈周期。
除此之外还存在一些其他问题。试图在类似于JVM这样的平台上,做一些应用程序的生 命周期管理是很困难的,这比简单地重启一下JVM要复杂得多。因为你的多个应用程序 都处在同一个进程中,所以分析资源的使用和线程也非常复杂。记住,即使你真的从某个 特定技术的容器中获得了一些好处,它们也不是免费的。先不说它们中的大多数都是付费 软件这一,点,它们在资源上的额外开销也特别值得考虑。
从根本上来说,这个方法还是想要试图优化资源的使用,但在如今的环境下已经没有必要 这么做了。无论你最终是否使用将多个服务放在一个主机中的部署模型,我都会强烈建议 你看看自包含的微服务构建物。对于.NET来说,可能是类似Nancy这样的东西,而Java 很多年前就已经支持了。举个例子,令人敬仰的Jetty嵌人式容器中,使用了一个非常轻量 级的自包含HTTP服务器,而它正是Dropwizard技术栈的核心。Google非常广泛地采用 了嵌入式Jetty容器来直接服务静态内容,所以这种做法的伸缩性肯定是没有问题的。
6.9.3每个主机一个服务
图6-8显示的是每个主机一个服务的模型,这种模型避免了单主机多服务的问题,并简化 了监控和错误恢复。这种方式也可以减少潜在的单点故障。一台主机宕机只会影响一个服 务,虽然在虚拟化平台上不一定真的是这样。第11章会做更多关干伸缩和故障方面的讨 论。我们也可以独立干其他服务很容易地对某一个服务进行扩展,安全性措施也可以更有 目的性地在更小范围内进行。
图6-8:每个主机一个微服务
更重要的是,这样做之后我们才有可能采用一些不同的部署技术,比如前面提到的基于镜 像的部署或者不可变服务器模式。 、
我们已经为引入微服务架构带来了很多复杂性。接下来要做的事情就是,寻找更多复杂性 的根源。在我看来,如果没有一个可用的PaaS平台,那么这个模型从整体上可以很好地 降低系统的复杂性。单主机单服务的模型会大大简化问题的排查。如果你还没有使用这个 模型,我也不会说微服务就一定不适合你。但我会建议你,将其看作减小微服务复杂性的一个方法。
但主机数量的增加也可能是个问题。管理更多的服务器,运行更多不同的主机也会引入很 多的隐式代价。尽管存在这些问题,但我仍然认为在使用微服务架构时这是比较好的模 型。下面会讲到如何降低管理大量主机带来的额外负担。
6.9.4平台即服务
当使用PaaS (Platform-as-a-Service,平台即服务)时,你工作的抽象层次要比在单个主机 上工作时的高。大多数这样的平台依赖于特定技术的构建物,比如Java WAR包或者Ruby gem等,这些平台还会帮你自动配置机器然后运行。其中一些能够透明地对系统进行伸缩 管理,而更常用的方式(根据我的经验来看,也是更不容易出错的方式)是,允许你控制 运行服务的节点数量,然后平台帮你处理其余的工作。
在编写本书时,就已经出现了很多好用的PaaS平台。Hemku就是一个黄金级的PaaS。它 不仅能够管理服务的运行,还能以非常简单的方式提供数据库等服务。
在这个领域也可以自己管理主机,但相比托管服务来说,还是不够成熟。
当PaaS解决方案正常工作时,它们工作得特别好。但是如果出现问题,你通常没办法通 过操作底层操作系统来修复这些问题。所以这也是你要做的取舍。以我的经验来看,PaaS 平台想要做得越聪明,通常也就可能错得越离谱。我用过的好几个PaaS,都尝试根据应用 程序的使用情况来自动伸缩,但都做得不好。因为平台一般都会尽量去满足一些比较通用 的需求,而非特定用户的特殊需求,所以你的应用程序越不标准,就越难一起和PaaS进 行工作。
好的PaaS解决方案已经为你做了很多,它们能够很好地帮你管理数量众多的组件。尽管 如此,我还是不确定这些模型是否正确,且自管理主机(self-hosted)的选择又很有限,所 以这种方法可能也不适合你。但是在未来的十年,我希望PaaS能够成为部署平台的首选, 而不是自己管理主机及每个服务的部署。
6.10自动化
我们提到的很多问题都可以使用自动化来解决。当机器数量比较少时,手动管理所有的事 情是有可能的。我以前就这么做过。记得当时我管理了少量的生产环境机器,登录到机器 上进行日志收集、软件部署、进程查看等工作。我的生产力似乎仅受能够打开@终端窗口 的数量的限制,所以当我开始使用了第二个显示器时,生产力得到了很大的提高。但是这 种方式很快就不适用了。
单主机单服务的模式会引入很多主机,从而产生很多的管理开销。如果你手动做所有的事
自动化还能够帮助开发人员保持工作效率。自助式配置单个服务或者一组服务的能力,会 大大简化开发人员的工作。理想情况下,幵发人员使用的工具链应该和部署生产环境时使 用的完全一样,这样就可以及早发现问题。本章的很多技术都采纳了这个思想。
使用支持自动化的技术非常重要。让我们从管理主机的工具开始考虑这个问题,你能否 通过写一行代码来启动或者关闭一个虚拟机?你能否自动化部署写好的软件?你能否不 需要手工干预就完成数据库的变更?想要游刃有余地应对复杂的微服务架构,自动化是 必经之路。
6.11从物理机到虚拟机
管理大量主机的关键之一是,找到一些方法把现有的物理机划分成小块。类似于VMWare 这样的传统虚拟化技术或者AWS,大大减少了管理主机的开销。在这个领域也出现了一 些新的值得尝试的技术,它们会开启处理微服务架构的新的可能性。
6.11.1传统的虚拟化技术
为什么拥有多台主机的成本会很高?如果你需要把每个服务部署在单台物理机上,那么答 案是显而易见的。如果你所在的环境就是这样的,那么单主机多服务的模式可能更适合 你。但是就像前面提到的,这可能会引人更多的限制。但是我怀疑你们中的大多数人,其 实多多少少都使用了一些虚拟化技术。虚拟化技术允许我们把一台物理机分成多台独立的 主机,每台主机可以运行不同的东西。所以如果我们想要把每个服务部署在独立的主机 上,为什么不把物理设备划分成小块呢?
对某些人来说,这么做是可行的。但是把机器划分成大量的VM并不是免费的。把物理机 想象成一个装袜子的抽屉,如果你在抽屉里放置了很多木隔板,那么可存放袜子的总量是 多还是少了?答案很明显是少了,因为隔板本身也占空间!管理抽屉是比较简单的,不仅 仅是放袜子,你也可以把T恤放在某个隔间里面,但是更多的隔板意味着更少的总空间。
虚拟化技术中也存在类似袜子抽屉中的隔板这样的东西。为了理解这些额外的开销是从 哪里来的,让我们看看大多数虚拟化技术是怎么做的。图6-9展示了两种虚拟化技术的 对比。左边叫作类型2虚拟化,其中包含了很多层,AWS、VMWare、VSphere、Xen和 KVM都属于这个类型(类型1虚拟化指的是只能运行在裸机之上,而不能运行在操作系 统之上的技术)。在物理基础设施上存在一个主机的操作系统,在这个OS上运行一个叫作 hypervisor的东西,它的任务主要有两个。第一,对CPU和内存等资源做从虚拟主机到物 理主机的映射。第二,给我们提供一个控制虚拟机的层。
图6-9:标准类型2虚拟化和轻量级容器技术的对比
VM中的不同主机看起来完全不同。在不同的虚拟机中可以安装不同的操作系统,并 且有其各自的内核。你可以认为它们就是完全密封的机器,与底层的物理机和同一个 hypervisor之上的其他虚拟机之间都是隔离的。
这里的问题是,hypervisor本身也需要一定的资源来完成自己的工作。它们会占用CPU、I/O和内存等。hypervisor管理的主机越多,占用的资源就越多。在某个点上,这些额外的开销 就会变成继续切分物理机的限制。在实际中,这意味着当你把物理机切分得越来越小时,能 够得到的收益也就越有限,因为hypervisor占用了很多资源。
6.11.2 Vagrant
Vagrant是一个很有用的部署平台,通常在开发和测试环境时使用,而非生产环境。 Vagrant可以在你的笔记本上创建一个虚拟的云。它的底层使用的是标准的虚拟化系统 (通常是VirtualBox,但也可以使用其他平台)。你可以使用文本文件来定义一系列虚拟机, 并且可以在其中定义网络配置及镜像等信息。可以把这个文本文件提交到代码库中,与团 队的其他成员共享。
这些工具能够帮助你在本地机器上轻松地创建出类生产环境。你可以同时创建多个VM, 通过关掉其中的几台来测试故障模式,并且可以把本地目录映射到虚拟机中,这样就可以 在修改完代码之后立即看到效果。即使对于使用类似AWS这样的按需云平台的团队来说, 使用Vagrant带来的快速反馈也能够给他们带来不少好处。
但它的缺点是,开发机上会有很多额外的资源消耗。如果一个服务占用一台虚拟机,你可 能就很难在本地机器上搭建起整个系统。结果就是为了让开发和测试有好的体验,可能需 要把其中一些依赖打桩,从而让事情变得可控一些。
6.11.3 Linux 容器
Linux用户可以使用另外一种虚拟化的替代方案。相比使用hypervisor隔离和控制虚拟主 机的方法来说,Linux容器可以创建一个隔离的进程空间,进而在这个空间中运行其他的 进程。
在Linux上,进程必须由用户来运行,并且根据权限的不同拥有不同的能力。进程可以创 建其他进程。举个例子,如果我在终端启动了一个进程,你可以认为它是终端程序的子进 程。Linux内核的任务就是维护这个进程树。(假设使用的是docker,可以认为创建的linux容器是docker软件的子进程)
Linux容器扩展了这个想法。每个容器就是整个系统进程树的一棵子树。内核已经帮我们 完成了给这些容器分配物理资源的任务。这个通用的方法有很多具体的形式,比如Solaris Zones和OpenVZ,但最流行的还是LXC。基本上所有的现代Linux内核都提供了 LXC。
图6-9显示了一个运行LXC的主机,如果仔细看你会发现一些不同之处。首先,不需要 hypervisor,其次,尽管每个容器可以运行不同的操作系统发行版,但必须共享相同的内核 (因为进程树存在于内核中)。这意味着,我们的主机操作系统可以运行Ubirntu,而在容器 中可以运行CemOS,只要它们的内核相同即可。
我们得到的好处不仅仅是避免了 hypervisor的使用,还可以加快反馈的速度,因为相比完 整的虚拟机,Linux容器可以启动得非常快。对于一台虚拟机来说,花几分钟时间来启动 是很正常的,但是Linux容器通常只要几秒钟就能完成启动。你还可以更好地对容器进行 资源的分配,这样就很容易通过一些调整来充分利用底层的硬件。
由于容器更轻量,所以在相同的硬件上能够运行的容器数量,比能够运行的虚拟机数量要 大得多。如图6-10所示,容器之间有一定的隔离性(虽然并不完美),而且相比每个虚拟 机运行一个服务来说,容器对资源的利用更加高效。
图6-10:在隔离的容器中运行服务
容器也能够很好地与虚拟机一起工作。我见过很多项目都会选择在AWS的EC2上,启动 一个比较大的实例,然后在此之上运行LXC容器。EC2是一个能够提供短暂型按需计算 的平台,容器非常灵活并且速度很快,灵活运用可以很好地利用二者的优势。
但Linux容器也不是没有任何问题的。想象一下,很多微服务运行在一台主机上的不同容 器中。外界如何才能看到它们呢?你需要某种方式把外界的请求路由到内部的容器中。在 虚拟化技术中,大多hypervisor已经帮你做好了这些事情。我曾经见过一些人花了很多时 间使用IPTable来配置端口映射,从而能够直接将容器暴露给外界。另一点需要记住的是, 这些容器并不是彼此完全隔离的,比如,有许多文档和已知的方法介绍了某些容器中的进 程,有可能会跳出该容器与其他容器中的进程,或者与底层主机发生干扰。这些问题有些 是故意这样设计的,有些是bug。但不管怎样,如果你不信任你的代码6,那么就别指望它 能够在容器中安全地运行。如果你想要的是那种隔离,那么需要考虑使用虚拟机。
6.11.4 Docker
Docker是构建在轻量级容器之上的平台。它帮你处理了大多数与容器管理相关的事情。 你可以在Dockei•中创建和部署应用,这些基干容器的应用与VM世界中的镜像很类似。 Docker也能管理容器的配置,并帮你处理一些网络问题,甚至还提供了自己的registry概 念,允许你存储Docker应用程序的版本。
Docker应用抽象对我们来说非常有用,就像使用VM镜像技术时,底层实现服务的技术是 不可见的一样。在服务的构建中可以创建出Docker应用程序,然后把它们存储在Docker registry中,那么就搞定了。
Docker还可以缓解运行过多服务进行本地开发和测试的问题。我们可以在Vagram中启动 单个VM,然后在其中运行多个Docker实例,每个实例中包含一个服务,而非原来的一个 Vagrant虚拟机中包含一个服务。接下来,就可以使用Vagrant来创建和销毁Docker平台 本身,并使用Docker来快速配置每个服务了。
很多与Docker相关的技术,能够帮助我们更好地使用它。CoreOS是一个专门为Docker设 计的操作系统。它是一个经过裁剪的LimixOS,仅提供了有限的功能以保证Docker的运 行。这意味着,它比其他操作系统消耗的资源更少,从而可以把更多的资源留给容器。它 甚至没有类似debs或RPM这样的包管理器,所有的软件都被装在一个独立的Docker应用 程序中,并仅在各自的容器中运行。
Docker本身并不能解决所有的问题,它只是一个在单机上运行的简单的PaaS。你还需要 一些工具,来帮助你跨多台机器管理Docker实例上的服务。调度层的一个关键需求是, 当你向其请求一个容器时会帮你找到相应的容器并运行它。在这个领域,Google最近的开 源工具Kubemetes和CoreOS集群技术能够提供一定的帮助,而且似乎每个月都有新的竞 争者出现。另一个基于Docker的有趣的工具是Deis (http://deis.io/),它试图在Docker之 上,提供一个类似于Heroku那样的PaaS。
前面我提到过PaaS解决方案。让我很纠结的是,这些平台经常搞错抽象级别,并且自 管理主机的解决方案,又远远落后于类似Hemku这样的托管主机服务。但在这些方面, Docker基本上都做对了,该领域持续升温的热度让我不禁推测,在未来的几年,它能够在 各种场景下成为合适的部署平台。在很多情况下,这种Dockei•加上一个合适的调度层的 解决方案介于IaaS和PaaS之间,很多地方使用CaaS (Communications-as-a-Service,容器 即服务)来描述它。
有好几个公司都在生产环境使用了 Docker。它提供了很多轻量级容器的好处,比如快速启 动和配置等,并且使用了一些工具来避免它的缺点。如果你正在寻找不同的部署平台,我 强烈建议你看看Docker。
6.12 —个部署接口
不管用干部署的底层平台和构建物是什么,使用统一接口来部署给定的服务都是一个很关 键的实践。在很多场景下,都有触发部署的需求,从本地开发测试到生产环境部署。这些 不同环境的部署机制应该尽量相似,我可不想因为部署流程不一致,导致一些只能在生产 环境才能发现的问题。
在这个领域工作了这么多年后,我深信,参数化的命令行调用是触发任何部署的最合理的 方式。可以使用CI工具来触发脚本的调用,或者手动键入。我在不同的技术栈下都编写 过包装脚本来完成部署工作,从Windows批处理到bash,再到Python Fabric脚本等。但 所有这些命令行脚本的格式都大同小异。
我们要知道部署的是什么,所以需要提供已知实体的名字,在这里也就是微服务的名字, 并且还需要知道该实体的版本。在不同的情况下可能会有三种不同的答案。当在本地工作 时,使用本地版本即可;进行测试时,需要使用最近通过的构建,也就是在构建物仓库中 最新的构建物;或者当进行测试和定位问题时,需要部署一个确切版本的构建物。
第三件也是最后一件需要我们知道的事情是,应该把微服务部署到哪个环境中。正如前面 讨论过的,我们的微服务拓扑在不同的环境中,可能是不同的,但这些信息应该对我们不 可见。
想象一下,我们创建了一个简单的需要这三个参数的部署脚本。在进行本地开发时,我们 想要把目录服务部署到本地环境。我可能会键入:
$ deploy artifact=catalog environnent=local version=local
代码一旦提交,CI就会进行一次构建,并生成一个新的构建物,其编号为b456。在大多 数CI工具中,这个值都会在整个流水线中传递。到测试阶段后,CI可以按照下面的方式 运行命令:
$ deploy artifact=catalog environment=ci version=b456
同时,QA会想要拉取最新的目录服务到集成测试环境,来进行一些探索性测试,并准备 了一个showcase。这时可以运行:
$ deploy artifact=catalog environnent=integrated_qa version=latest
我使用最多的工具是Fabric,它是一个被设计用来将命令行调用映射到函数的Python库, 同时也能够提供类似SSH这样的机制来控制远程机器。结合Boto这样的AWS客户端来 使用,你就能完全自动化大型AWS环境。对于Ruby来说,Capistrano类似于Fabric。在 Windows上使用PowerShell,可以达到同样的效果。
环境定义
很显然,为了完成上述工作,还需要使用某种方式对环境进行定义,并对服务在环境中的
配置进行描述。你可以把环境定义想象成微服务到计算、网络和存储资源之间的映射。我
以前使用YAML文件进行描述,使用脚本从中获取数据。示例6-1是几年前我在一个使用
AWS的项”目上,做过的一些工作的简化版。
示例6-1:环境定义的例子(服务在环境中的运行时配置-硬件配置和虚拟化配置)
development:
nodes:
-ami_id: ami-elel234
size: tl.micro ①
credentials_name: eu-west-ssh ②
services: [catalog-service]
region: eu-west-1
production:
nodes:
-ami_id: aml-elel234
size: m3.xlarge ①
credentials_nane: prod-credentials ②
services: [catalog-service]
number: 5 ③
①改变实例的大小从而充分利用资源。你不需要一个16核、64GB内存的机器来做探索 性测试!
②能够在不同环境中设置不同凭证(credential)的能力很关键。敏感环境的凭证信息会 被存储在不同的代码库中,这些代码库只有少数人可以访问。
③我们决定默认情况下,如果一个服务有多台节点需要配置,就自动为其创建一个负载 均衡。
为简洁起见,我去掉了一些细节。
目录服务的信息存储到了其他地方。它在不同的环境中是一致的,如示例6-2所示。
示例6-2:环境定义的例子(服务在环境中的应用程序配置)
catalog-service:
puppet_manifest : catalog.pp ①
connectivity:
-protocol: tcp
ports: [ 8080, 8081 ]
allowed: [ WORLD ]
①这是我们运行的Puppet文件的名字。在这个例子中刚好使用的是Puppet solo,但理论 上来说也可以使用其他配置系统。
很显然,这里的很多行为都是基于约定的。举个例子,我们决定规范化服务可以使用的端口,无论它们在哪里运行。当服务有一个以上实例时就会自动配置负载均衡器(AWS的 ELB很容易处理这件事情)。
构建一个类似于这样的系统的工作量很大。这些代价基本上都需要在前期付出,但是做好 之后,它能够很好地管理部署的复杂性。我希望将来你不需要自己做这些事情。Terraform 是来自Hashicorp的一个很新的工具,它就可以帮你做上述事情。一般我不太会在书里提 这么新的工具,因为与其说它是个工具,还不如说只是一个想法而已,但它正在朝着上述 那个方向发展。目前这个工具还在初期,但它的功能似乎非常有趣。它已经支持了多个不 同平台的部署工作,所以在将来,也许它能够很好地胜任这项工作。
原文地址:https://www.cnblogs.com/mongotea/p/11992102.html