微服务架构的多种部署模式
声明:本文主要内容来自《微服务架构设计模式》
部署包含两个互相关联的概念:流程和架构。部署流程包括一些由开发人员和运维人员执行的过程,以便将软件发布到生产环境。部署架构定义了该软件运行的环境结构。
以微服务架构开发的程序,经常涉及到多个服务组成,后端的网关模块,用户模块,日志模块,业务模块等,还有前端Node.js模块。以笔者所在公司为例,每次部署服务时都要互相之间沟通协调好,方便服务之间的调用。一般在开发环境中多以jar形式部署,从本地直接打包发布到服务器,方便快捷,适合与其他业务调试时及时解决修复问题。但这样也存在一定的弊端,由于本地打包都是自己修改后的代码,如果没有及时将更改提交到git上,很容易遗漏其他协作开发的同事的代码,可能自己的代码调试完成,同事的却出现问题。
图中抽象描述微服务架构中多种服务的组成和调用关系
当涉及测试和生产环境的部署时,需要注意的地方更多,打包上传这种方式存在很大的漏洞,不够流程化,而且服务器上可能运行各种语言和框架编写的服务,每个服务都是一个小应用程序,这意味着你在生产环境中有数十或数百个应用程序。因此让系统管理员手动配置服务器和服务已不再可行。如果要大规模部署微服务,则需要高度自动化的部署流程和基础设施。
生产环境必须实现四个关键功能:
服务管理接口: 使开发人员能够创建、更新和配置服务。理想情况下,这个接口是一个可供命令行和图形部署工具调用的REST API。
运行时服务管理:确保始终运行着所需数量的服务实例。如果服务实例崩溃或由于某种原因无法处理请求,则生产环境必须重新启动它。如果运行服务的主机发生崩溃,则必须在其他主机上重新启动这些服务实例。
监控:让开发人员深入了解服务正在做什么,包括日志文件和各种应用指标。如果出现问题,必须提醒开发人员,也称为可观测性。
请求路由:将用户的请求路由到服务。
概述
故本文将阐述三个关键部署模式,并分析它们的好处和弊端。
- 编程语言特定的发布包格式,例如Java JAR或WAR文件。我并不推荐这种做法,之所以介绍这个选项,是因为这个部署方法有各种显著的缺点,会促使你思考和选择其他更为合理和现代化的部署技术。
- 将服务部署为虚拟机,把服务打包为虚拟机镜像,这个镜像封装了服务的技术栈,这样可以简化部署。
- 将服务部署为容器,这些容器比虚拟机更轻量级。
1 编程语言特定的发布包格式
假设你要部署一个基于Spring Boot的Java应用程序。部署此服务的一种方法是使用特定于编程语言的软件包部署服务。使用此模式时,生产环境中部署的内容以及服务运行时管理的内容都是特定于语言的发布包中的服务。在Restaurant service的场景下,它是可执行的JAR文件或WAR文件。对于其他语言,例如Nodejs,服务是源代码和模块的目录。对于某些语言,例如GoLang,服务是特定于操作系统某个路径下的可执行文件。
模式:编程语言特定的发布包格式,使用特定于编程语言的软件发布包将服务部署到生产环境。
要在计算机上部署Java应用程序,首先要安装必要的运行时,在本例中为JDK。如果它是WAR文件,则还需要安装Web容器,例如Apache Tomcat。配置完计算机后,将程序发布包复制到计算机并启动该服务。每个服务实例都作为JVM进程运行。理想情况下,你已经设置好部署流水线,它会自动将服务部署到生产环境。部署流水线构建可执行的JAR文件或WAR文件,然后它们调用生产环境的服务管理接口来部署新版本。
服务实例通常是单个进程,但有时可能是一组进程。例如,Java服务实例是运行JVM的进程。 Node js服务可能会生成多个工作进程,以便同时处理请求。某些语言支持在同一进程中部署多个服务实例。
有时,可以在计算机上部署单个服务实例,同时保留在同一台计算机上部署多个服务实例的选项。例如,如图12-4所示,可以在一台计算机上运行多个JVM每个JVM都运行个服务实例。
图中在同一台计算机上部署多个服务实例它们可能是相同服务的实例,也可能是不同服务的实例。操作系统的开销在服务实例之间共享。每个服务实例都是一个单独的进程,因此它们之间存在一些隔离。
某些语言还允许在单个进程中运行多个服务实例。例如,如图所示,可以在单个 Apache Tomcat上运行多个Java服务。
进程,在同一Web容器或应用程序服务器上部署多个服务实例。它们可能是相同服务的实例,也可能是不同服务的实例。操作系统和运行时的开销在所有服务实例之间共享。但是因为服务实例处于同一个进程中,所以它们之间没有隔离。
将服务作为特定于语言的发布包进行部署的模式有好处也有弊端。我们先来看看好处。
使用编程语言特定的发布包格式进行部署的好处
将服务作为特定于编程语言的发布包进行部署有以下好处:
- 快速部署。
- 高效的资源利用,尤其是在同一台机器上或同一进程中运行多个实例时。
我们来逐一分析。
快速部署
这种模式的一个主要好处是部署服务实例的速度相对较快:将服务复制到主机并启动它。如果服务是用Java编写的,则复制JAR或WAR文件。对于其他语言,例如js或Ruby,可以复制源代码。在任何一种情况下,需要通过网络复制的字节数相对较小。此外,启动服务耗时很短。如果服务运行于自己独占的进程,则启动它。否则,如果服务是在同一Web容器(例如Tomcat)进程中运行的多个实例之一,则可以将其动态部署到Web容器中,也可以重新启动Web容器。由于没有额外的开销,因此启动服务通常很快。
高效的资源利用
这种模式的另一个主要好处是它可以相对高效地使用资源。多个服务实例共享机器及其操作系统。如果多个服务实例在同一进程中运行,则效率更高。例如,多个Web应用程序可以共享相同的 Apache Tomcat服务器和JVM。
使用编程语言特定的发布包格式进行部署的弊端
尽管极具吸引力,但把服务作为特定于编程语言的发布包进行部署的模式有几个显著的
- 缺乏对技术栈的封装。
- 无法约束服务实例消耗的资源。
- 在同一台计算机上运行多个服务实例时缺少隔离。
- 很难自动判定放置服务实例的位置。
我们来逐一分析。
缺乏对技术栈的封装
运维团队必须了解部署每个服务的具体细节每个服务都需要特定版本的运行时。例如, Java Web应用程序需要特定版本的 Apache Tomcat和JDK运维团队必须安装每个所需软件包的正确版本。
更糟糕的是,服务可以用各种语言和框架编写。它们也可能用这些语言和框架的多个版本编写。因此,开发团队必须(以人工的方式)与运维团队分享许多细节。这种沟通的复杂性增加了部署期间出错的风险。例如,机器可能安装错误的语言运行时版本。
无法约束服务实例消耗的资源
另一个缺点是你无法约束服务实例所消耗的资源。一个进程可能会消耗机器的所有CPU或内存,争用其他服务实例和操作系统的资源。例如,出现某个错误,这种情况极有可能会发生。
在同一台计算机上运行多个服务实例时缺少隔离
在同一台计算机上运行多个实例时,问题更严重。缺乏隔离意味着行为不当的服务实例可能会影响其他服务实例。因此,应用程序存在不可靠的风险,尤其是在同一台计算机上运行多个服务实例时。
很难自动判定放置服务实例的位置
在同一台计算机上运行多个服务实例的另一个挑战是确定服务实例的位置。每台机器都有一组固定的资源、CPU、内存等,每个服务实例都需要一定的资源。以一种有效使用机器而不会使它们过载的方式将服务实例分配给机器非常重要。正如我稍后解释的那样,基于虚拟机的云主机和容器编排框架会自动处理这个问题。在本地部署服务时,你可能需要手动确定放置位置。
正如你所看到的,服务作为特定于语言的发布包进行部署的模式具有一些显著的弊端。应该尽量避免使用这种方法,除非所获效率的价值远在其他所有考量之上。
2 将服务部署为虚拟机
模式:将服务部署为虚拟机,将作为虚拟机镜像打包的服务部署到生产环境中。每个服务实例都是一个虚拟机。虚拟机镜像由服务的部署流水线构建。部署流水线运行虚拟机镜像构建器,这个构建器创建包含服务代码和服务运行所需的任何软件的虚拟机镜像。例如,服务的安装JDK和服务的可执行JAR的虚拟机构建器。虚拟机镜像构建器使用 Linux的init系统(如 upstart)将虚拟机镜像配置成在虚拟机引导时运行该应用程序。
部署流水线可以使用各种工具来构建虚拟机镜像。一个早期创建EC2AMI的工具是由 netflix aminator ( https://github. com/netflix/a ) Netflix AWS开发的Aminatorhtp:github.cm/netflix/aminator),netflix使用它在aws上部署其视频流服务 packer ( https://www.packer. io )是一个更现代的虚拟机镜像构建器,与 Aminator不同,它支持各种虚拟化技术,包括EC2 Digital Ocean、 Virtual Box和 VMware要使用 Packer创建AMI,你需要编写一个配置文件,用于指定基础镜像和一组安装软件并配置AMI的配置程序。
关于它的好处和弊端
将服务部署为虚拟机的好处
- 虚拟机镜像封装了技术栈。
- 隔离的服务实例。
- 使用成熟的云计算基础设施。
我们来逐一分析。
虚拟机镜像封装了技术栈
此模式的一个重要好处是虚拟机镜像包含服务及其所有依赖项。它消除了错误来源,确保正确安装和设置服务运行所需的软件。一旦服务被打包为虚拟机,它就会变成一个黑盒子,封装服务的技术栈。虚拟机镜像可以无须修改地部署在任何地方。用于部署服务的API成为虚拟机管理API。部署变得更加简单和可靠。
隔离的服务实例
虚拟机的另一个好处是每个服务实例都以完全隔离的方式运行。毕竟,这是虚拟机技术的主要目标之一。每台虚拟机都有固定数量的CPU和内存,不能从其他服务中窃取资源。
使用成熟的云计算基础设施
将微服务部署为虚拟机时,可以利用成熟且高度自动化的云计算基础设施。AWS等公共云试图以避免机器过载的方式在物理机上调度虚拟机。它们还提供有价值的功能,例如跨虚拟机的流量负载均衡和自动扩展。
将服务部署为虚拟机的弊端
- 资源利用效率较低。
- 部署速度相对较慢。
- 系统管理的额外开销。
资源利用效率较低。
每个服务实例拥有一整台虚拟机的开销,包括其操作系统。此外,典型的公共IaaS虚拟机提供有限的虚拟机配置组合,因此虚拟机可能未得到充分利用。这不太可能成为基于Java的服务的问题,因为它们一般都相对较重。但这种模式可能是部署轻量级 Nodejs和 GoLang服务的低效方式。
部署速度相对较慢
由于虚拟机的大小,构建虚拟机镜像通常需要几分钟。有很多内容要通过网络传输。此外,由于必须通过网络传输完整的虚拟机镜像文件,从镜像实例化虚拟机是非常耗时的。在虚拟机内部运行的操作系统也需要一些时间来启动,尽管慢速是一个相对的术语。这个过程可能需要几分钟,比传统的部署过程要快得多。但它比你即将学习的更轻量级的部署模式要慢得多。
系统管理的额外开销
你不得不担负起给操作系统和运行时打补丁的责任。这对于部署软件时的系统管理似乎是不可避免的,但在后面的12.5节中,我Serverless将描述部署,它消除了这种系统管理的方式。
现在让我们看一下部署微服务更加轻量但仍每一个容器都是隔离进程的沙箱具有虚拟机诸多优点的替代方法。
3 将服务部署为容器
容器是一种更现代、更轻量级的部署机制,是一种操作系统级的虚拟化机制。容器通常包含一个或多个在沙箱中运行的进程,这个沙箱将它们与其他容器隔离。例如,运行Java服务的容器通常由JVM进程组成。
从在容器中运行的进程的角度来看,它就好像在自己的机器上运行一样。它通常有自己的机器IP地址,可以消除端口冲突。例如,所有Java由所有容器共享进程都可以侦听端口8080。每个容器也有自己的根文件系统。容器运行时使用操作系统机制将容器彼此隔离。
模式:将服务部署为容器将作为容器镜像打包的服务部署到生产环境中每个服务实例都是一个容器。
创建容器时,可以指定它的CPU和内存资源,以及依赖于容器实现的I/O资源等。容器运行时强制执行这些限制,并防止容器占用其机器的资源。使用 Docker编排框架(如 Kubernetes)时,指定容器的资源尤为重要。这是因为编排框架使用容器请求的资源来选择运行容器的底层机器,从而确保机器不会过载。
在构建服务时,部署流水线使用容器镜像构建工具,该工具读取服务代码和镜像描述,以创建容器镜像并将其存储在镜像仓库中。在运行时,从镜像仓库中拉取容器镜像,并用于创建容器。
使Docker用部署服务
要将服务部署为容器,必须将其打包为容器镜像。容器镜像是由应用程序和运行服务所需的依赖软件组成的文件系统镜像。它通常是一个完整的 Linux根文件系统,但更轻量级的镜像也可以使用。例如,要部署基于 Spring Boot的服务,需要构建一个容器镜像,其中包含服务的可执行JAR和正确的JDK版本。同样,部署 Java Web应用程序,需要构建一个包含WAR文件 Apache、 Tomcat和JDK的容器镜像。主要分为以下步骤。
- 构建 Docker镜像
- 将Docker镜像推送到镜像仓库
- 运行Docker容器
将服务部署为容器的好处
将服务部署为容器有几个好处。首先,容器具有虚拟机的许多好处:
- 封装技术栈,可以用容器的API实现对服务的管理。
服务实例是隔离的。 - 服务实例的资源受到限制。
但与虚拟机不同,容器是一种轻量级技术容器镜像通常可以很快构建。例如,只需几秒就可以将 Spring Boot应用程序打包为容器镜像。通过网络传输容器镜像也相对较快主要是因为仅传输所需要的镜像层的子集。(这是因为 Docker镜像具有所谓的分层文件系统,使得 Docker只需要通过网络传输部分镜像。镜像的操作系统、Java运行时和应用程序位于不同的层中。 Docker只需要传输镜像仓库中不存在的那些层。因此,当 Docker只需移动应用程序层时,通过网络传输镜像特别快)。容器也可以很快启动,因为没有冗长的操作系统启动过程。当容器启动时,所运行的就是服务。
将服务部署为容器的弊端
容器的一个显著弊端是,你需要承担大量的容器镜像管理工作。你必须负责给操作系统和运行时打补丁。此外,除非使用托管容器解决方案(如 Google Container Engine或awsECS),否则你必须管理容器基础设施以及容器运行可能需要的虚拟机基础设施。