天天看点

《微服务架构设计模式》十二:部署微服务应用

作者:SRE实战
《微服务架构设计模式》十二:部署微服务应用

导读

四个关键部署模式,如何工作以及他们的优缺点

  • 编程语言特定的发布包格式
  • 将服务部署为虚拟机
  • 将服务部署为容器
  • Serverless部署

使用Kubernetes部署

使用服务网格把服务发布环节与服务部署环节分开

使用AWS Lambda部署服务

选择部署模式

从20世纪90年代末开始开发企业应用以来,部署的流程和架构都发生了根本性的变化。早先开发人员将代码扔给运维人员进行手动部署的历史已经一去不复返了,生产环境的部署过程已经变得高度自动化。如图12-1所示,之前由物理机组成的生产环境已被越来越多轻量级和短生命周期的计算基础设施所取代。

《微服务架构设计模式》十二:部署微服务应用

回到20世纪90年代,那时如果你想将应用程序部署到生产环境中,第一步就是将应用程序与一组部署指南交给运维团队。例如,你可能会提交部署工单,要求运维人员部署应用程序。接下来发生的事情完全是运维的职责,除非他们遇到问题需要你帮助来进行修复。通常运维部门购买并安装昂贵且重量级的应用服务器,如WebLogic或WebSphere。然后,他们将登录到应用程序服务器的控制台,并部署你的应用程序。他们会非常关心这些机器,就好像对待宠物一样,定期为它们安装补丁并更新软件。

在21世纪头10年中期,昂贵的应用程序服务器被替换为开源的轻量级Web容器,如Apache Tomcaat和Jetty。你仍然可以在每个Web容器上运行多个应用程序,但同时也可以让每个Web容器只运行一个应用程序。此外,虚拟机开始取代物理机。但机器仍然被视为心爱的宠物,部署仍然基本上是手动的。

今天的部署模式则完全不同。采纳DevOps意味着开发团队需要肩负部署应用程序或者服务的职责,而不是将代码交给单独的运维团队。在某些组织中,运维人员为开发人员提供了用于部署代码的控制台,或者,更好的是一旦通过测试,部署流水线就会自动将代码部署到生产环境中。

生产环境中使用的计算资源也随着物理机器的抽象而发生了根本性的变化。在高度自动化的云上运行虚拟机已经取代了长生命周期、宠物般的物理机和虚拟机。今天的虚拟机是不可变的(immutable),被视为一次性的资源而不再是宠物,使用过后就被丢弃或者重建,而不是重新配置。容器是虚拟机之上的一个更轻量级的抽象层,是一种越来越流行的部署应用程序的方式。对于许多场景,你甚至可以使用更轻量级额的Serverless部署平台。

部署流程和架构的更新换代,与微服务架构的日益普及几乎同时发生,这绝非一个巧合。应用程序可能有数十种或数百种以各种语言和框架编写的服务。因为每个服务都是一个应用程序都是一个小应用程序,这意味着你在生产环境中有数十或数百个应用程序。因此让系统管理员手动配置服务器和服务已经不再可行。如果要大规模部署微服务,则需要高度自动化的部署流程和基础设施。

图12-2显示了一个生产环境的抽象视图。生产环境使开发人员能够配置和管理他们的服务、使用部署流水线部署新版本的服务,以及用户访问这些服务实现的功能。

《微服务架构设计模式》十二:部署微服务应用

生产环境必须实现四个关键功能:

服务管理接口:使开发人员能够创建、更新和配置服务。理想情况下,这个接口是个可供命令行和图形部署工具调用的 REST API

运行时服务管理:确保始终运行着所需数量的服务实例。如果服务实例崩溃或由于某种原因无法处理请求,则生产环境必须重新启动它。如果运行服务的主机发生崩溃则必须在其他主机上重新启动这些服务实例。

监控:让开发人员深入了解服务正在做什么,包括日志文件和各种应用指标。如果出现问题,必须提醒开发人员。

请求路由:将用户的请求路由到服务。

部署模式:编程语言特定的发布包格式

假设你要部署一个基于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都运行个服务实例。

《微服务架构设计模式》十二:部署微服务应用

某些语言还允许在单个进程中运行多个服务实例。例如,如图12-5所示,可以在单个 Apache Tomcat上运行多个Java服务。

《微服务架构设计模式》十二:部署微服务应用

在传统的昂贵、重量级的应用程序服务器(如WebLogic和WebSphere)上部署应用程序通常会使用此方法。将服务作为特定于语言的发布包进行部署的模式有好处也有弊端。

我们先来看看好处。

使用编程语言特定的发布包格式进行部署的好处

将服务作为特定于编程语言的发布包进行部署有以下好处:

  • 快速部署。
  • 高效的资源利用,尤其是在同一台机器上或同一进程中运行多个实例时。

我们来逐一分析。

快速部署

这种模式的一个主要好处是部署服务实例的速度相对较快:将服务复制到主机并启动它。如果服务是用Java编写的,则复制JAR或WAR文件。对于其他语言,例如js或Ruby,可以复制源代码。在任何一种情况下,需要通过网络复制的字节数相对较小。此外,启动服务耗时很短。如果服务运行于自己独占的进程,则启动它。否则,如果服务是在同一Web容器(例如Tomcat)进程中运行的多个实例之一,则可以将其动态部署到Web容器中,也可以重新启动Web容器。由于没有额外的开销,因此启动服务通常很快。

高效的资源利用

这种模式的另一个主要好处是它可以相对高效地使用资源。多个服务实例共享机器及其操作系统。如果多个服务实例在同一进程中运行,则效率更高。例如,多个Web应用程序可以共享相同的 Apache Tomcat服务器和JVM。

使用编程语言特定的发布包格式进行部署的弊端

尽管极具吸引力,但把服务作为特定于编程语言的发布包进行部署的模式有几个显著的

  • 缺乏对技术栈的封装。
  • 无法约束服务实例消耗的资源。
  • 在同一台计算机上运行多个服务实例时缺少隔离。
  • 很难自动判定放置服务实例的位置。

我们来逐一分析。

缺乏对技术栈的封装

运维团队必须了解部署每个服务的具体细节每个服务都需要特定版本的运行时。例如, Java Web应用程序需要特定版本的 Apache Tomcat和JDK运维团队必须安装每个所需软件包的正确版本。

更糟糕的是,服务可以用各种语言和框架编写。它们也可能用这些语言和框架的多个版本编写。因此,开发团队必须(以人工的方式)与运维团队分享许多细节。这种沟通的复杂性增加了部署期间出错的风险。例如,机器可能安装错误的语言运行时版本。

无法约束服务实例消耗的资源

另一个缺点是你无法约束服务实例所消耗的资源。一个进程可能会消耗机器的所有CPU或内存,争用其他服务实例和操作系统的资源。例如,出现某个错误,这种情况极有可能会发生。

在同一台计算机上运行多个服务实例时缺少隔离

在同一台计算机上运行多个实例时,问题更严重。缺乏隔离意味着行为不当的服务实例可能会影响其他服务实例。因此,应用程序存在不可靠的风险,尤其是在同一台计算机上运行多个服务实例时。

很难自动判定放置服务实例的位置

在同一台计算机上运行多个服务实例的另一个挑战是确定服务实例的位置。每台机器都有一组固定的资源、CPU、内存等,每个服务实例都需要一定的资源。以一种有效使用机器而不会使它们过载的方式将服务实例分配给机器非常重要。正如我稍后解释的那样,基于虚拟机的云主机和容器编排框架会自动处理这个问题。在本地部署服务时,你可能需要手动确定放置位置。

正如你所看到的,服务作为特定于语言的发布包进行部署的模式具有一些显著的弊端。应该尽量避免使用这种方法,除非所获效率的价值远在其他所有考量之上。

将服务部署为虚拟机

模式:将服务部署为虚拟机

将作为虚拟机镜像打包的服务部署到生产环境中。每个服务实例都是一个虚拟机。

虚拟机镜像由服务的部署流水线构建。部署流水线运行虚拟机镜像构建器,这个构建器创建包含服务代码和服务运行所需的任何软件的虚拟机镜像。例如,服务的安装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将描述部署,它消除了这种系统管理的方式。

现在让我们看一下部署微服务更加轻量但仍每一个容器都是隔离进程的沙箱具有虚拟机诸多优点的替代方法。

将服务部署为容器

容器是一种更现代、更轻量级的部署机制,是一种操作系统级的虚拟化机制。容器通常包含一个或多个在沙箱中运行的进程,这个沙箱将它们与其他容器隔离。例如,运行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),否则你必须管理容器基础设施以及容器运行可能需要的虚拟机基础设施。

使用Kubernetes部署应用程序

什么是Kubernetes

Kubernetes是一个容器编排框架。将运维容器的一组计算机视为资源池,只需要告诉它运行你的服务N个实例,它就会自动把其余的事情搞定。主要有三个功能:

  • 资源管理:将一组计算机视为CPU、内存和存储卷构成的资源池,将计算机集群视为一台计算机。
  • 调度:选择要运行容器的机器。默认情况下,调度考虑容器的资源需求和每个节点的可用资源。还可以实现在同一节点上部署具有亲和性的容器,或者确保特定的几个容器分散部署在不同的节点上(反亲和性)。
  • 服务管理:实现命名和版本化服务的概念,这个概念可以直接映射到微服务架构中的具体服务。确保始终运行所需数量的实例,并实现负载均衡,也支持滚动更新和回滚。

Kuberntes的架构

Kubernets如图所示,是一个集群架构,其中分为主节点和普通节点(也称工作节点)。

《微服务架构设计模式》十二:部署微服务应用

主节点运行如下多个组件:

  • API服务器:用于部署和管理服务的REST API,例如被kubectl命令行使用。
  • Etcd:存储集群数据键值的NoSQL数据库。
  • 调度器:选择要运行Pod的节点,Pod是Kubernets的部署单元,由一组容器组成。
  • 控制器:确保集群状态与预期状态匹配。

普通节点运行的组件如下:

  • Kubelet:创建和管理节点上运行的Pod。
  • Kube-Proxy:管理网络,包括跨Pod负载均衡。
  • Pods:应用程序服务。

Kubernetes的关键概念:Pod、Deployment、Service、ConfigMap

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。

Pod(就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个) 容器; 这些容器共享存储、网络、以及怎样运行这些容器的声明。 Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。 Pod 所建模的是特定于应用的 “逻辑主机”,其中包含一个或多个应用容器, 这些容器相对紧密地耦合在一起。 在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。

除了应用容器,Pod 还可以包含在 Pod 启动期间运行的 Init 容器。 你也可以在集群支持临时性容器的情况下, 为调试的目的注入临时性容器。

一个 Deployment 为 Pod 和 ReplicaSet 提供声明式的更新能力。

你负责描述 Deployment 中的 目标状态,而 Deployment 控制器(Controller) 以受控速率更改实际状态, 使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment, 并通过新的 Deployment 收养其资源。

服务(Service)是将运行在一组Pods上的应用程序公开为网络服务的抽象方法。

使用 Kubernetes,你无需修改应用程序去使用不熟悉的服务发现机制。 Kubernetes 为 Pod 提供自己的 IP 地址,并为一组 Pod 提供相同的 DNS 名, 并且可以在它们之间进行负载均衡。

ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。使用时, Pods 可以将其用作环境变量、命令行参数或者存储卷中的配置文件。

ConfigMap 将你的环境配置信息和容器镜像解耦,便于应用配置的修改。

在Kubernetes上部署应用

要在Kubernetes上部署服务,需要定义一个工作负载,例如Deployment对象,最简单的方法是写一个YAML文件:

《微服务架构设计模式》十二:部署微服务应用

容器定义中指定了服务名称、容器镜像和环境变量,以及健康检查。

liveness:是否应终止或重启服务实例。判断服务是否启动成功。

readiness:确定是否将流量路由到服务。等待服务初始化后,再路由流量进入。

创建Deployment后,还需要创建Service对象,为Deployment中部署的服务提供稳定的网络访问端点(具有IP和DNS名称),对该Service的访问将负载均衡到Deployment管理的Pod中。

《微服务架构设计模式》十二:部署微服务应用

kubernetes提供了kubectl命令行工具和声明式风格API,只需要执行下面的命令即可创建Deployment和Service对象:

kubectl apply -f xxx.yaml           

执行后,即可在kubernetes集群内使用http://ftgo-restaurant-service:8080访问REST API。

部署API Gateway

API Gateway的作用是将来自外部世界的流量路由到这个服务,Service默认的ClusterIP类型仅支持集群内访问,不过还有两种类型可以支持外部访问:NodePort和LoadBalancer。

《微服务架构设计模式》十二:部署微服务应用

API Gateway在集群中使用URL http://ftgo-api-gateway:8080,在集群外使用URL http://<node-ip-address>:3000/,其中<node-ip-address>是集群内某个节点的IP。

零停机部署(滚动更新)

使用Kubernetes更新服务只需要三步:

  1. 构建新的容器镜像并推送至镜像仓库,镜像需要用不同的版本标签标记
  2. 更新Deployment YAML文件,引用新的镜像版本标签
  3. kubectl apply -f xxx.yaml

Kubernetes会自动执行滚动更新过程,利用readinessProbe机制来确保新的Pod就绪后,再停止旧的Pod,确保一直有可用的Pod提供不间断服务。

当一切正常时,经过有限时间(根据配置不同,大约几秒到几分钟)所有Pod将更新到新版本;

当遇到报错,Pod无法启动时,会自动停止更新,这时可以在修复后,再次走一遍新的apply过程,再次更新镜像版本;

如果都启动成功了,但服务有问题,那就需要快速回滚版本:

kubectl rollout undo deployment <name>           

使用服务网格分隔部署和发布流程

为了更好可靠性,我们可以借助服务网格,将部署和发布两个动作分开:

部署:让服务开始在生产环境中运行

发布:使最终用户可以使用新的服务

使用以下详细步骤进行生产环境的部署和发布:

  1. 将新版本部署到生产环境,但不向其路由任何请求
  2. 在生产中测试
  3. 将其发布给少数用户
  4. 逐步将其发布给越来越多的用户,直至处理所有请求
  5. 上述任何环节有问题,立即恢复旧版本

istio服务网格的内容还是比较复杂的,需要后续另开一篇详说~

部署模式:Serviceless部署

之前的三种部署方式的共同点:

  1. 都会存在资源浪费的情况,例如即使处于闲置状态,也需要为容器和虚拟机付费
  2. 需要负责系统管理,必须承担操作系统和软件打补丁的工作

Serverless提供一种受约束的编程模型,以换取最小化的系统管理开销,且不需要担心服务器、容器的任何方面,这一理念非常先进。

以AWS Lambda为例,只需要将应用程序打包为ZIP或者JAR文件,上载到AWS Lamdba,并指定相应请求的函数名称。AWS Lambda会自动运行你的微服务实例来影响请求,只需要为所花费的时间和消耗的内存付费。

但如果需要更加精细化的管理基础设施,那么就不要选择Serverless。

好处:

  • 消除系统管理任务
  • 弹性,不需要预测负载,而判断实例数量。AWS Lamdba会帮忙处理。
  • 基于使用情况定价

弊端:

  • 长尾延迟。AWS需要花费时间来配置应用程序实例和启动应用程序,可能导致某些请求具有高延迟。
  • 基于有限事件与请求的编程模型。不适应长时间运行的服务,例如消息代理服务。

本章小结

  • 你应该选择支持服务要求的最轻量部署模式。按以下顺序评估选项:Serverless、容器、虚拟机和特定于语言的程序包。
  • Serverless部署不适合每项服务,因为长尾延迟和使用基于事件/请求的编程模型的要求。但是,当适合部署应用时,Serverless部署是一个非常有竞争力的选择,因为它消除了管理操作系统和运行时的必要,并提供自动弹性配置和基于请求的定价。
  • Docker 容器是一种轻量级操作系统级虚拟化技术,比Serverless部署更灵活,并且具
  • 有更可预测的延迟。最好使用Docker编排框架,例如Kubernetes,它管理机器集群上的容器。使用容器的缺点是你必须管理操作系统和运行时,并且很可能也需要管理Docker编排框架及其底层运行的虚拟机。
  • 第三个部署选项是将你的服务部署为虚拟机。一方面,虚拟机是重量级的部署选项,因此部署速度较慢,并且很可能比第二个选项使用更多资源。另一方面,AmazonEC2等现代云是高度自动化的,并提供了丰富的功能。因此,使用虚拟机部署小型简单应用程序有时可能比设置Docker编排框架更容易。
  • 除非你只有少量服务,否则通常最好避免将服务部署为特定于编程语言的发布包。例如,如第13章所述,当你开始使用微服务时,你可能会使用与单体应用程序相同的机制来部署服务,而且很可能是此选项。只有开发了一些服务后,你才应该考虑建立一个复杂的部署基础设施,如Kubernetes。
  • 使用服务网格(一种网络层,调节进出服务的所有网络流量)的众多好处之一是,它使你能够在生产环境中部署服务,对其进行测试,然后才将生产流量路由到更新后的服务。将部署与发布隔离可以提高新版本服务的可靠性。

继续阅读