微服务入门系列:微服务的部署策略选择

这是下面七篇系列文章的第六篇:

  1. 微服务入门系列:微服务介绍
  2. 微服务入门系列:使用 API 网关
  3. 微服务入门系列:微服务架构的进程间通信
  4. 微服务入门系列:微服务架构的服务发现
  5. 微服务入门系列:基于事件驱动的数据管理
  6. 微服务入门系列:微服务的部署策略选择(本文)
  7. 微服务入门系列:单体应用的微服务重构

出自微服务部落:https://blog.idevfun.io/deploying-microservices/
原文链接:https://www.nginx.com/blog/deploying-microservices/
作者:Chris Richardson
翻译:董干

这 7 篇文章是很不错的微服务入门系列文章,介绍了微服务所需要的主要技术,是当年(本系列文章写于2015年,但现在仍然不过时)带领译者入门微服务的文章,强烈建议深入微服务架构之前先了解本系列。


本文是关于使用微服务架构构建应用的七篇系列文章的第六篇。第一篇介绍了微服务架构模式,同单体架构模式进行了对比,并讨论了微服务的优势和缺点。接下来的几篇分别从不同角度描述了微服务架构的各个方面:使用 API 网关进程间通信服务发现以及基于事件驱动的数据管理。这篇文章我们看一下部署微服务可选择的策略。

动机

部署 单体应用 意味着运行同一应用程序的通常很大的多份相同的拷贝。你通常开启 N 个(物理的或虚拟的)服务器,并在每个服务器上运行 M 个实例。部署单体应用并不完全总是那么直接,但相对于部署微服务应用来说要简单得多。

微服务应用 由数十个甚至数百个服务组成。服务由各种不同的语言和框架编写。每个服务都是一个需要自己特定部署方式、资源、扩容机制和监控需求的小型应用程序。例如,你需要根据这个服务的需求来确定它所需部署的实例数量。并且,必须给每个服务实例合适的 CPU、内存以及 I/O 资源。更有挑战的是需要在处理这种复杂性的同时还需要快速、可靠并且低成本的方式部署这些服务。

微服务有几种不同的部署方式。我们先来看一下每个主机上部署多个服务的方式。

每个主机部署多个服务方式

部署微服务的一种方式是使用 每个主机多个服务实例模式。在这种方式下,你开启一个或多个物理机或虚拟机,然后在每个机器上运行多个服务实例。从很多方面看,这类似于部署应用的传统方式。每个服务实例在一个或多个机器的固定端口上运行。这些主机通常像 对待宠物 一样被管理。

下图展示了这种模式的结构。

Richardson-microservices-architecture-part6-host

这种模式有几种变种。一种是每个服务实例作为一个进程或进程组。例如,你可以将一个 Java 服务实例作为 web 应用部署在 Apache Tomcat 服务器上。而 Node.js 服务实例可能会包含一个父进程和一个或多个子进程。

另一个变种是在同一个进程或进程组中运行多个服务实例。例如,你可以在同一个 Apache Tomcat 服务器上部署多个 Java web 应用,或者在同一个 OSGI 容器中运行多个 OSGI bundles。

在一个主机上部署多个服务的模式有好处也有坏处。一个主要的好处是资源利用率相对比较高。多个服务实例共享一个服务器和它的操作系统。如果一个进程或进程组运行多个服务实例,那利用率会更高,例如多个 web 应用共享同一个 Apache Tomcat 服务器和 JVM。

这种模式的另一个好处是,部署一个服务实例相对来说更快。你只需将服务拷贝到目标主机上并启动它即可。如果服务是用 Java 开发的,拷贝 JAR 或 WAR 文件即可。对于其他语言,例如 Node.js 或 Ruby,你需要拷贝代码。无论哪种情况,需要通过网络拷贝的字节数相对来说较少。

而且,由于不需要额外的支出,启动一个服务通常非常快。如果服务运行在自己的进程中,简单启动这个进程即可。如果相反,服务是同一个容器进程或进程组中运行的多个实例中的一个,你可以或者动态地将它部署到容器中,或者重启应用容器即可。

尽管如此,在一个主机上部署多个服务的模式也有一些重大缺陷。一个主要的问题是除非每个服务实例都是运行在独立的进程内,否则服务实例之间的隔离很少或没有。虽说你可能可以精确地监控每个服务实例的资源利用情况,但是你不能限制每个实例使用的资源。有可能某个异常的服务实例占用了机器上所有的内存或 CPU。

如果多个服务实例运行在同一个进程中,则根本没有隔离。例如,所有的实例可能共享一个 JVM 堆内存。一个异常的服务实例可以很容易地将同一个进程中运行的其他服务影响致死。而且,你没有什么办法监控每个服务实例所各自使用的资源。

这种方式的另一个显著的问题是运维团队在部署一个服务时需要了解细节以确定如何部署。由于服务可能由多种语言和框架所编写,开发团队需要共享很多细节给运维团队。这种复杂性增加了部署时出问题的风险。

如上所述,尽管这种方式较为熟悉,在一个主机上部署多个服务的模式具有一些重大缺陷。我们接下来看其他几种能够避免这些问题的部署微服务的方式。

每个主机部署一个服务实例方式

部署微服务的另一种方式是每个主机一个服务实例。使用这种模式时,每个服务实例运行在独立的主机上。这种模式可以分为两类:每个虚拟机一个服务实例和每个容器一个服务实例。

每个虚拟机部署一个服务实例方式

当使用每个虚拟机一个服务实例的方式时,你将每个服务作为一个 VM 镜像,例如 Amazon EC2 AMI。每个服务实例都是使用这个 VM 镜像启动的一个虚拟机(例如 EC2 实例)。下面画出了这种模式的示意图:

Richardson-microservices-architecture-part6-vm-1

这种方式是 Netflix 部署他们的视频流媒体服务的主要方式。Netflix 使用 Aminator 将他们的每个服务都打包成 EC2 的 AMI 镜像。每个运行的服务实例都是一个 EC2 的实例。

有很多工具可以用来构建自己的镜像。你可以配置持续集成(CI)服务器(例如,Jenkins)来调用 Aminator 来将服务打包为 EC2 的 AMI 镜像。Packer.io 是另一个自动化构建 VM 镜像的可选项。不同于 Aminator,它支持多种虚拟化技术,包括 EC2、DigitalOcean、VirtualBox 和 VMware。

Boxfuse 公司拥有一种很好的方式来构建 VM 镜像,能够解决我下面会提到的 VM 的缺陷。Boxfuse 将你的 Java 应用打包为最小 VM 镜像。这些镜像构建和启动都很快,并且由于暴露出来可用来攻击的东西很少,因此更安全。

CloudNative 公司提供了 Bakery,一个提供构建 EC2 AMI 镜像的 SaaS 服务。你可以配置 CI 服务器在微服务测试通过后调用 Bakery,后者然后将服务打包为 AMI 镜像。使用像 Bakery 这样的 SaaS 服务意味着你不必再将宝贵的时间浪费在搭建创建 AMI 镜像的基础设施上。[译者注:截至本文翻译时,这个服务网站已经跳转到别的产品,可能已经成为过去时。但现在较热的云原生 Cloud Native 基金会组织却是鼎鼎有名,加入其的工具和产品成为新一代微服务的范式。译者认为这应该是两个独立的组织。]

每个虚拟机一个服务实例模式有一系列优势。使用虚拟机的一个主要的优势是每个服务实例都完全运行在隔离的环境中。它们拥有固定量的 CPU 和内存,并且不能从其他服务那里偷取资源。

将微服务部署为 VM 镜像的另一个优势是你可以利用成熟的云计算基础设施。像 AWS 这样的云计算服务商提供例如负载均衡和自动扩缩容这类有用的功能。

将服务部署为 VM 镜像还有一个优势是它能将你的服务的实现技术封装起来。一旦当服务打包为 VM 镜像,它就变成了一个黑盒子。VM 的管理 API 成为部署服务的 API。部署可以变得更为简单可靠。

不过,每个虚拟机一个服务实例模式也有一些缺点。一个缺陷是资源利用率更小了。每个服务实例需要占一整个 VM 的空间,包括操作系统。而且,在典型的公有云 IaaS 上,VM 都是固定的大小,VM 可能会未充分利用。

还有一点,公有云 IaaS 通常对 VM 进行收费,无论它是在闲置状态还是忙的状态。像 AWS 这样的云服务提供商提供自动伸缩的功能,但很难按需及时响应。因此,你通常需要多预留一些虚拟机,而这会增加部署的成本。

这种方式的另一个坏处是部署服务的新版本通常会比较慢。VM 镜像由于较大,通常构建起来比较慢。同时,由于虚拟机较大,初始化过程通常也比较慢。另外,操作系统需要花一些时间启动。需要说明的是,并不是所有情况都这样,因为像 Boxfuse 提供的一些轻量级虚拟机也是存在的。

每个虚拟机一个服务实例模式的另一个缺点是,通常来说需要你(或者你们组织内的其他人)负责这些基础设施的无差别杂务。除非你使用类似于 Boxfuse 这样的工具来帮你构建和管理虚拟机镜像,否则,这就是你的责任。这些必要但耗时的活动会从核心业务里面分散很多你的精力。

我们再看看另一种更轻量、但仍然具有使用虚拟机的许多优势的部署微服务可选的方式。

每个容器一个服务实例的方式

当使用每个容器一个服务实例的模式时,每个服务实例运行在独自的容器中。容器是操作系统级别的虚拟化机制。一个容器包含一个或多个在沙盒中运行的进程。从进程的角度看,它们拥有自己的端口命名空间和根文件系统。你可以限制容器使用的内存和 CPU 资源。一些容器实现还具有 I/O 速度的限制。容器技术的例子有 DockerSolaris Zones

下图示意了这种模式的结构:

Richardson-microservices-architecture-part6-container

使用这种模式,需将服务打包成容器镜像。容器镜像是由应用程序和运行服务所需要的库组成的文件系统镜像。一些容器镜像是由一个完整的 Linux 根文件系统组成。其他的要轻量得多。例如,要部署一个 Java 服务,你构建一个包含 Java 运行时,可能是 Apache Tomcat 服务器,以及你编译好的 Java 应用程序。

一旦当你将服务打包成镜像,你可以启动一个或多个容器。通常来说你可以在每个物理机或虚拟机上运行多个容器。你可能会使用集群管理系统例如 Kubernetes 或者 Marathon 来管理你的所有容器。集群管理系统将主机作为一个资源池。它基于容器所需要的资源以及每个主机上可用的资源决定如何放置容器。

每个容器一个服务实例模式既有优点也有缺点。使用容器的优势类似于使用虚拟机。它们将服务实例之间隔离开。你可以简单地监控每个容器所消耗的资源。另外,和虚拟机类似,容器将实现服务所需要的技术封装起来。容器管理 API 也将作为管理服务的 API。

然而,不同于虚拟机,容器是种更轻量级的技术。容器镜像构建起来通常非常快速。举个例子,在我的笔记本电脑上,将 Spring Boot 应用打包为 Docker 容器仅仅需要花 5 秒钟。由于没有操作系统冗长的启动机制,容器启动也非常迅速。当容器启动时,运行的就是服务。

使用容器也有一些缺点。尽管容器的基础设施正在迅速地成熟,但还是不如虚拟机基础设施成熟。并且,由于容器和宿主机操作系统共享同一内核,因此不如虚拟机安全。

另一个容器的缺点是,它需要你(或者你们组织内的其他人)负责这些管理容器镜像的基础设施的无差别杂务。除非你使用类似于 Google Container EngineAmazon EC2 容器服务 这样的云服务商,你必须管理容器基础设施以及容器运行的虚拟机的基础设施。

另外,容器通常部署于通过每个虚拟机计费的云基础设施上,因此,和上面描述的一样,你可能需要为处理负载突增所预留的虚拟机而额外付出的机器成本。

有趣的是,容器和虚拟机之间的区别可能会逐渐模糊。就像早些时候提到的,Boxfuse 虚拟机构建和启动都非常迅速。Clear Containers 项目旨在创建轻量级的虚拟机(编者注 - 2017 年 12 月宣布 Clear Containers 项目在开源 Kata Containers 项目中继续)。对于 unikernels 的兴趣也在增长中。Docker 公司近期收购了 Unikernel Systems 公司。

Serverless 这种无服务器部署的新概念现在也正在流行中,这种方式避开了该选择部署服务到容器还是虚拟机上这个问题。我们接下来看下这种方式。

Serverless 部署

AWS Lambda 是 Serverless 部署技术的一个典范。它支持 Java、Node.js 和 Python 服务。部署微服务时,你先将服务打包为 ZIP 文件,然后上传到 AWS Lambda。同时,你还需要提供一些元数据,除了指定其他一些事情外,它规定了接收请求(即事件)的函数名称。AWS Lambda 自动启动足够的微服务实例来处理请求。你仅需要为每个请求所花费的时间和耗费的内存付费。当然,细节处会有坑,稍后你将看到 AWS Lambda 也有限制。但是,不管是作为开发者的你还是组织中的别人都无需再担心服务器、虚拟机或容器这些层面的事情,这个概念令人难以置信的极具吸引力。

一个 Lambda 函数 代表一个无状态的函数。它通常通过调用 AWS 的服务来处理请求。举个例子,可以有一个 Lambda 函数,每当有图片上传到 S3 的某个桶中时插入一条记录到 DynamoDB 的图片表中,然后发布一条消息,通知 Kinesis 以触发图片处理。Lambda 函数也可以调用第三方 web 服务。

调用 Lambda 函数有如下四种方式:

  1. 使用 web 服务直接调用
  2. 对由 AWS 服务产生的事件,如 S3、DynamoDB、Kinesis 或邮件服务等,自动做出响应
  3. 由 AWS 的 API 网关自动处理客户端的 HTTP 请求
  4. 类似 cron 的定时处理任务

如上所述,AWS Lambda 是部署微服务的一种便捷的方式。基于请求数计费意味着你仅需要为服务实际执行的工作量付费。另外,由于不需要再为基础设施操心,你可以将更多的精力放在开发业务应用上面。

但是,它仍然具有一些重大的限制。它不适宜用来部署长期运行的服务,例如消费第三方消息队列服务器事件的服务。请求必须在 300 秒内完成。服务必须是无状态的,因为理论上 AWS Lambda 有可能将每个请求都运行在不同实例上。这些函数必须使用支持的语言开发。服务必须启动迅速,否则,它们可能因为超时而被终止。

总结

部署微服务应用是很有挑战的。几十甚至几百个服务,由各种各样的语言和编程框架开发;其中每个都是一个迷你的应用程序,并且有各自特定的部署、资源、扩容和监控需求。微服务部署的方式包括每个虚拟机一个服务实例和每个容器一个服务实例。还有一种有趣的选项是使用 AWS Lambda 这种 serverless 的方式部署微服务。在本系列的下一篇也是最后一篇文章中,我们继续探讨如何将现有单体应用迁移到微服务架构上来。


这是下面七篇系列文章的第六篇:

  1. 微服务入门系列:微服务介绍
  2. 微服务入门系列:使用 API 网关
  3. 微服务入门系列:微服务架构的进程间通信
  4. 微服务入门系列:微服务架构的服务发现
  5. 微服务入门系列:基于事件驱动的数据管理
  6. 微服务入门系列:微服务的部署策略选择(本文)
  7. 微服务入门系列:单体应用的微服务重构

出自微服务部落:https://blog.idevfun.io/deploying-microservices/
原文链接:https://www.nginx.com/blog/deploying-microservices/
作者:Chris Richardson
翻译:董干

这 7 篇文章是很不错的微服务入门系列文章,介绍了微服务所需要的主要技术,是当年(本系列文章写于2015年,但现在仍然不过时)带领译者入门微服务的文章,强烈建议深入微服务架构之前先了解本系列。

Show Comments

Get the latest posts delivered right to your inbox.