编者的话 |本文来自 Nginx 官方博客,是「Chris Richardson 微服务」系列的第六篇文章。第一篇介绍了微服务架构模式,并且讨论了使用微服务的优缺点。随后的文章讨论了微服务的不同方面,包括使用 API 网关、进程间通讯、服务发现和事件驱动的数据管理。这篇文章将深入讨论部署微服务的策略。
作者介绍:Chris Richardson,是世界著名的软件大师,经典技术著作《POJOS IN ACTION》一书的作者,也是 cloudfoundry.com 最初的创始人,Chris Richardson 与 Martin Fowler、Sam Newman、Adrian Cockcroft 等并称为世界十大软件架构师。
Chris Richardson 微服务系列全 7 篇:
1. 微服务架构概念解析
2. 构建微服务架构:使用 API Gateway
3. 深入微服务架构的进程间通信
4. 服务发现的可行方案以及实践案例
5. 微服务的事件驱动数据管理
6. 选择微服务部署策略(本篇文章)
7. 将单体应用改造为微服务
诱因
部署单体应用意味着运行大型应用的多个相同副本,通常提供若干台(N)服务器(物理机或虚拟机),在每台服务器上运行若干个(M)应用实例。部署单体应用并不总是简单明了,但还是比部署微服务应用简单。
微服务应用由几十甚至数百个服务组成。服务用不同的语言和框架写成,每个都是一个小应用,包括特定的部署、资源、扩展和监控需求,例如,根据服务需求运行若干数量的服务实例。此外,每个服务实例必须配套提供适当的 CPU、内存 和 I/O 资源。更具挑战性的是,尽管如此复杂,部署服务还必须快速、可靠和性价比高。
微服务部署模式有多个,先从单主机多个服务实例开始讲起。
单主机多服务实例模式
部署微服务的方法之一是使用单主机多服务实例模式。使用此模式,用户要提供一到多台物理或虚拟主机,在每个主机上运行多个服务实例。很多情况下,这是传统的应用部署方法。每个服务实例在一个或多个主机的已知端口上运行,主机通常被看做宠物。
下图展示了这一模式的结构。
这一模式有几个变型。其中一个变型是服务实例作为进程或进程组。例如,在 Apache Tomcat 服务器上部署 Java 服务实例作为网页应用,Node.js 服务实例可能包括一个父进程和一个或多个子进程。
这一模式的另一个变型是在同一进程或进程组中运行多个服务实例。例如,在同一台 Apache Tomcat 服务器上部署多个 Java 网页应用,或者在同一个 OSGI 容器中运行多个 OSGI 捆绑组件。
单主机多服务实例模式有诸多优点。一个主要优点就是资源利用相对高效,多服务实例共享服务器及其操作系统。如果进程或进程组运行多个服务实例,效率更高,比如共享同一个 Apache Tomcat 服务器和 JVM 的多个网页应用。
这个模式的另一大优点是部署服务实例更快,只需将服务复制到主机并启动。如果服务用 Java 写成,复制 JAR 或者 WAR 文件;对于其它语言,如果是 Node.js 或者 Ruby,复制源代码。在这两种情况下,通过网络复制的字节数比较小。
此外,由于没有太多开销,启动服务通常很快。如果服务自包含进程,只需要启动。如果是运行在同一容器进程或进程组的服务实例,则需要动态部署到容器中,或者重启容器。
除了上述吸引力之外,单主机多服务实例模式也有一些显著缺点。主要缺点在于,除非每个服务实例是一个单独的进程,否则会甚少或者没有隔离。虽然能够准确监控每个服务实例的资源利用率,但是并不能限制每个实例使用的资源;很有可能一个异常的服务实例会消耗主机的所有内存和 CPU。
运行在同一进程的多个服务实例没有隔离,所有实例可能共享同一个 JVM 堆。出现异常的服务实例能够轻易中断运行在同一进程中的其它服务。此外,也无法监控每个服务使用的资源。
这种方法的另一显著问题是,部署服务的运维团队需要了解部署的具体细节。服务可能用各种语言和框架写成,因而开发团队必须与运维团队沟通诸多细节。这种复杂性增加了部署中出错的风险。
尽管单主机多服务实例模式颇为友好,但仍有显著缺点。接下来介绍其它的部署方式,能够避免这些缺点。
单主机单服务实例模式
部署微服务的另一种方法是单主机单服务实例模式。在此模式中,每台主机上运行独立的服务实例。这一模式有两种不同实现——单虚拟机单服务实例和单容器单服务实例。
单虚拟机单服务实例模式
使用单虚拟机单服务实例模式时,把每个服务大包围一个虚拟机镜像,类似 Amazon EC2 AMI。每个服务实例就是一台使用此镜像启动的虚拟机,譬如 EC2 实例。下图展示了此模式的结构。
这也是 Netflix 在部署视频流媒体服务时采用的主要方式。Netflix 使用 Aminator 把每个服务实例打包成 EC2 AMI,每个正在运行的服务实例就是一个 EC2 实例。
有多种工具可用来搭建自己的虚拟机。用户能够配置持续集成(CI)服务器(例如 Jenkins)来调用 Aminator,把服务打包为 EC2 AMI。Packer.io 是另一个自动化创建虚拟机镜像的工具。不同于 Aminator,它支持包括 EC2、DigitalOcean、VirtualBox 和 VMware 在内的多种虚拟化技术。
Boxfuse 这家公司使用令人信服的方式构建虚拟机镜像,克服了我上文描述的虚拟机的缺点。Boxfuse 把 Java 应用打包为一个最小的虚拟机镜像。这些镜像能够快速构建、启动,并且由于只暴露了有限的端口,也更安全。
CloudNative 提供 Bakery 这款 SaaS 工具来创建 EC2 AMI。用户的微服务通过测试,能够配置 CI 服务器来调用 Bakery,后者把服务打包为 AMI。使用 Bakery 这样的 SaaS 工具意味着你不需要浪费宝贵的时间来设置创建 AMI 的基础设施。
单虚拟机单服务实例有许多优点。一大好处就是每个服务实例完全隔离运行,每个实例都有固定的 CPU 和内存,不会被别的服务占用资源。
把微服务作为虚拟机部署的另一个优势就是能够充分利用成熟的云基础设施。AWS 这样的云服务提供了负载均衡和自动扩展这样实用的功能。
再一个优势就是封装了服务的实施技术。一旦服务被打包成虚拟机,就变成了黑盒,虚拟机的管理 API 成为部署该服务的 API。部署变得更简单可靠。
单虚拟机单服务实例模式也有缺点。缺点之一就是资源利用率低。每个服务实例占用包括操作系统在内的整个虚拟机的开销。此外,在典型的公有 IaaS,虚拟机资源固定,很难被充分利用。
此外,公有 IaaS 通常依据虚拟机数量收费,不考虑其繁忙与否。AWS 这类的 IaaS 提供了自动扩展,但是很难针对需求快速反应;因而很容易过度配置虚拟机,增加部署成本。
这种方法的另一个缺点是,部署服务的新版本通常很缓慢。由于体积较大,虚拟机镜像构建缓慢。同样,由于体积较大,虚拟机的实例化也比较缓慢。此外,操作系统也需要时间启动。然而,鉴于存在由 Boxfuse 构建的轻量级的虚拟机,这一规律也并非普遍适用。
单虚拟机单服务实例的另一个缺点就是,用户或组织中的其他人要负责大量无差别的沉重的工作。除非使用 Boxfuse 这样的工具来解决构建和管理虚拟机的开销,否则这种必要且耗时的工作会占用你处理核心业务的时间。
下面来了解另一种部署微服务的方法,它比虚拟机轻量,但具有其优点。
单容器单服务实例模式
使用单容器单服务实例模式,每个服务实例运行在自有容器中。容器是操作系统级别的虚拟化机制。每个容器包含一个或多个运行在沙盒中的进程。从进程的角度看,它们有着自己的端口命名空间和根文件系统。用户能够限制容器的内存和 CPU 资源,有些容器还能限制 I/O 速率。容器技术的代表包括 Docker 和 Solaris Zone。
下图显示了这种模式的结构:
使用这一模式时,用户将服务打包为容器镜像。每个容器镜像就是一个文件系统镜像,由应用和运行服务所需的库构成。有的容器镜像还包括完整的 Linux 根文件系统,有的则更轻量。以部署 Java 服务为例,构建的容器镜像包括 Java 运行时、Apache Tomcat 服务器、以及编译好的 Java 应用。
一旦将服务打包为容器镜像,就启动一到多个容器。通常每个物理机或虚拟主机上会运行多个容器,会用到 Kubernetes 或 Marathon 这样的集群管理工具来管理容器。集群管理工具把主机看做资源池,根据每个容器需要的资源和每个主机上可用的资源来调度容器。
单容器单服务实例模式有缺点兼备。容器的优点与虚拟机类似,服务实例之间完全隔离,也能轻松监控每个容器的资源消耗。此外,与虚拟机相似,容器能够封装执行服务的技术。容器管理 API 也可用作管理服务的 API。
不同于虚拟机,容器技术更为轻量,容器镜像构建速度更快。只用短短五秒就可以在笔记本电脑上把 Spring Boot 应用打包为 Docker 容器。由于没有冗长的操作系统启动机制,容器启动也非常迅速。容器启动,服务立刻运行。
使用容器也有一些缺点。虽然容器架构迅速成长,然而并不如虚拟机架构那般成熟。此外,由于容器之间共享主机操作系统的内核,因而也没有虚拟机安全。
容器的另一个缺点是,管理容器镜像是一项无差别的繁重工作。除非能使用 Google Container Engine 或 Amazon EC2 容器服务(ECS),否则需要同时管理容器基础设施和虚拟机基础设施。
此外,容器通常部署在以每台虚拟机定价的基础设施上,为了处理负载高峰,你可能会过度配置虚拟机,带来额外的成本。
有趣的是,容器和虚拟机之间的区别并非泾渭分明。如前文所述,Boxfuse 能够快速构建和启动虚拟机,Clear Container 项目则致力于创建轻量级的虚拟机,此外 unikernel 技术也引起大家注意。Docker 近期(注:2016 年 1 月 21 日)收购了 Unikernel Systems。
无服务器部署的概念也崭露头角,日渐流行。无服务器部署不需要选择将服务部署在容器还是服务器。
无服务器部署
AWS Lambda 就是无服务器部署的例子。它支持 Java、Node.js 和 Python 服务。要部署微服务,把服务打包为 ZIP 文件并上传到 AWS Lambda。用户也可以提供元数据,指定函数名称,后者被用于处理请求(即事件)。
Lambda 函数是无状态的服务,通过调用 AWS 服务处理请求。例如,当镜像被上传到 S3 存储桶,Lambda 函数被调用,在 DynamoDB 镜像中插入一项条目,并向 Kinesis 流发布消息,触发镜像处理。Lambda 函数也可以调用第三方网页服务。
有以下四种方法来调用 Lambda 函数:
- 直接调用,直接使用网页服务请求
- 自动调用,自动响应由 S3、DynamoDB、Knesis、或 Simple Email Service 等 AWS 服务生成的事件
- 自动调用,自动通过 AWS API 网关处理来自应用客户端的 HTTP 请求
- 定期调用,通过类似 Cron 的定时任务实现
可以看出,AWS Lambda 是部署微服务的便捷途径。基于请求的定价方式意味着用户只需要为服务实际运行的业务付费。此外,由于无需考虑 IT 基础设施,用户也能专注于应用开发。
然而,AWS Lambda 也有一些明显的局限。它并不适合被用来部署长期运行的服务,比如读取来自第三方消息代理的信息。请求需要在 300 秒内完成。由于 AWS Lambda 理论上能够针对每个请求运行单独的实例,因此服务必须保持无状态。此外,它们还必须用某一种支持的语言完成。服务也需要快速启动,然而有时候会有超时和停止。
总结
部署微服务应用充满挑战。几十个甚至上百个服务用不同的语言和框架写成,每个服务都是一个自带特定部署、资源、扩展和监控需求的微型应用。微服务部署的模式有多种,包括单虚拟机单服务实例和单容器单服务实例。另一个让人倍感兴趣的微服务部署方法则是 AWS Lambda,无服务器部署方案的代表。在本系列的最后一篇,我们将分析如何将单体应用迁移到微服务架构。