返回介绍

2.4 DevOps 工程师的故事:构建运行时的严谨性

发布于 2025-04-22 21:54:05 字数 7206 浏览 0 评论 0 收藏 0

对于 DevOps 工程师来说,微服务的设计关乎在投入生产后如何管理服务。编写代码通常是很简单的,而保持代码运行却是困难的。

虽然 DevOps 是一个丰富而新兴的 IT 领域,在本书后面,读者将基于 4 条原则开始微服务开发工作并根据这些原则去构建。这些原则具体如下。

(1)微服务应该是 独立的可独立部署的 ,多个服务实例可以使用单个软件制品进行启动和拆卸。

(2)微服务应该是 可配置的 。当服务实例启动时,它应该从中央位置读取需要配置其自身的数据,或者让它的配置信息作为环境变量传递。配置服务无需人为干预。

(3)微服务实例需要对客户端是透明的。客户端不应该知道服务的确切位置。相反,微服务客户端应该与服务发现代理通信,该代理将允许应用程序定位微服务的实例,而不必知道微服务的物理位置。

(4)微服务应该 传达 它的健康信息,这是云架构的关键部分。一旦微服务实例无法正常运行,客户端需要绕过不良服务实例。

这 4 条原则揭示了存在于微服务开发中的悖论。微服务在规模和范围上更小,但使用微服务会在应用程序中引入了更多的活动部件,特别是因为微服务在它自己的分布式容器中彼此独立地分布和运行,引入了高度协调性的同时也更容易为应用程序带来故障点。

从 DevOps 的角度来看,必须解决微服务的运维需求,并将这 4 条原则转化为每次构建和部署微服务到环境中时发生的一系列生命周期事件。这 4 条原则可以映射到以下运维生命周期步骤。

  • 服务装配 ——如何打包和部署服务以保证可重复性和一致性,以便相同的服务代码和运行时被完全相同地部署?
  • 服务引导 ——如何将应用程序和环境特定的配置代码与运行时代码分开,以便可以在任何环境中快速启动和部署微服务实例,而无需对配置微服务进行人为干预?
  • 服务注册/发现 ——部署一个新的微服务实例时,如何让新的服务实例可以被其他应用程序客户端发现。
  • 服务监控 ——在微服务环境中,由于高可用性需求,同一服务运行多个实例非常常见。从 DevOps 的角度来看,需要监控微服务实例,并确保绕过微服务中的任何故障,而且状况不佳的服务实例会被拆卸。

图 2-6 展示了这 4 个步骤是如何配合在一起的。

图 2-6 当微服务启动时,它将在其生命周期中经历多个步骤

构建 12-Factor 微服务应用程序

本书最大的希望之一就是读者能意识到成功的微服务架构需要强大的应用程序开发和 DevOps 实践。这些做法中最简明扼要的摘要可以在 Heroku 的 12-Factor 应用程序宣言中找到。此文档提供了 12 种最佳实践,在构建微服务的时候应该始终将它们记在脑海中。在阅读本书时,读者将看到这些实践相互交织成例子。我将其总结如下。

代码库 ——所有应用程序代码和服务器供应信息都应该处于版本控制中。每个微服务都应在源代码控制系统内有自己独立的代码存储库。

依赖 ——通过构建工具,如 Maven(Java),明确地声明应用程序使用的依赖项。应该使用其特定版本号声明第三方 JAR 依赖项,这样能够保证微服务始终使用相同版本的库来构建。

配置 ——将应用程序配置(特别是特定于环境的配置)与代码分开存储。应用程序配置不应与源代码在同一个存储库中。

后端服务 ——微服务通常通过网络与数据库或消息系统进行通信。如果这样做,应该确保随时可以将数据库的实施从内部管理的服务换成第三方服务。第 10 章将演示如何将服务从本地管理的 Postgres 数据库移动到由亚马逊管理的数据库。

构建、发布和运行 ——保持部署的应用程序的构建、发布和运行完全分开。一旦代码被构建,开发人员就不应该在运行时对代码进行更改。任何更改都需要回退到构建过程并重新部署。一个已构建服务是不可变的并且是不能被改变的。

进程 ——微服务应该始终是无状态的。它们可以在任何超时时被杀死和替换,而不用担心一个服务实例的丢失会导致数据丢失。

端口绑定 ——微服务在打包的时候应该是完全独立的,可运行的微服务中要包含一个运行时引擎。运行服务时不需要单独的 Web 或应用程序服务器。服务应该在命令行上自行启动,并通过公开的 HTTP 端口立即访问。

并发 ——需要扩大时,不要依赖单个服务中的线程模型。相反,要启动更多的微服务实例并水平伸缩。这并不妨碍在微服务中使用线程,但不要将其作为伸缩的唯一机制。横向扩展而不是纵向扩展。

可任意处置 ——微服务是可任意处置的,可以根据需要启动和停止。应该最小化启动时间,当从操作系统收到 kill 信号时,进程应该正常关闭。

开发环境与生产环境等同 ——最小化服务运行的所有环境(包括开发人员的台式机)之间存在的差距。开发人员应该在本地开发时使用与微服务运行相同的基础设施。这也意味着服务在环境之间部署的时间应该是数小时,而不是数周。代码被提交后,应该被测试,然后尽快从测试环境一直提升到生产环境。

日志 ——日志是一个事件流。当日志被写出时,它们应该可以流式传输到诸如 Splunk 或 Fluentd 这样的工具,这些工具将整理日志并将它们写入中央位置。微服务不应该关心这种情况发生的机制,开发人员应该在它们被写出来的时候通过标准输出直观地查看日志。

管理进程 ——开发人员通常不得不针对他们的服务执行管理任务(数据移植或转换)。这些任务不应该是临时指定的,而应该通过源代码存储库管理和维护的脚本来完成。这些脚本应该是可重复的,并且在每个运行的环境中都是不可变的(脚本代码不会针对每个环境进行修改)。

2.4.1 服务装配:打包和部署微服务

从 DevOps 的角度来看,微服务架构背后的一个关键概念是可以快速部署微服务的多个实例,以应对变化的应用程序环境(如用户请求的突然涌入、基础设施内部的问题等)。

为了实现这一点,微服务需要作为带有所有依赖项的单个制品进行打包和安装,然后可以将这个制品部署到安装了 Java JDK 的任何服务器上。这些依赖项还包括承载微服务的运行时引擎(如 HTTP 服务器或应用程序容器)。

这种持续构建、打包和部署的过程就是服务装配(图 2-6 中的步骤 1)。图 2-7 展示了有关服务装配步骤的其他详细信息。

图 2-7 在服务装配步骤中,源代码与其运行时引擎一起被编译和打包

幸运的是,几乎所有的 Java 微服务框架都包含可以使用代码进行打包和部署的运行时引擎。例如,在图 2-7 中的 Spring Boot 示例中,可以使用 Maven 和 Spring Boot 构建一个可执行的 Java JAR 文件,该文件具有嵌入式的 Tomcat 引擎内置于其中。以下命令行示例将构建许可证服务作为可执行 JAR,然后从命令行启动 JAR 文件:

mvn clean package && java ¨Cjar target/licensing-service-0.0.1-SNAPSHOT.jar

对某些运维团队来说,将运行时环境嵌入 JAR 文件中的理念是他们在部署应用程序时的重大转变。在传统的 J2EE 企业组织中,应用程序是被部署到应用程序服务器的。该模型意味着应用程序服务器本身是一个实体,并且通常由一个系统管理员团队进行管理,这些管理员管理服务器的配置,而与被部署的应用程序无关。

在部署过程中,应用程序服务器的配置与应用程序之间的分离可能会引入故障点,因为在许多组织中,应用程序服务器的配置不受源控制,并且通过用户界面和本地管理脚本组合的方式进行管理。这非常容易在应用程序服务器环境中发生配置漂移,并突然导致表面上看起来是随机中断的情况。

将运行时引擎嵌入可部署制品中的做法消除了许多配置漂移的可能性。它还允许将整个制品置于源代码控制之下,并允许应用程序团队更好地思考他们的应用程序是如何构建和部署的。

2.4.2 服务引导:管理微服务的配置

服务引导(图 2-6 中的步骤 2)发生在微服务首次启动并需要加载其应用程序配置信息的时候。图 2-8 为引导处理提供了更多的上下文。

图 2-8 服务启动(引导)时,它会从中央存储库读取其配置

任何应用程序开发人员都知道,有时需要使应用程序的运行时行为可配置。通常这涉及从应用程序部署的属性文件读取应用程序的配置数据,或从数据存储区(如关系数据库)读取数据。

微服务通常会遇到相同类型的配置需求。不同之处在于,在云上运行的微服务应用程序中,可能会运行数百甚至数千个微服务实例。更为复杂的是,这些服务可能分散在全球。由于存在大量的地理位置分散的服务,重新部署服务以获取新的配置数据变得难以实施。

将数据存储在服务器外部的数据存储中解决了这个问题,但云上的微服务提出了一系列独特的挑战。

(1)配置数据的结构往往是简单的,通常读取频繁但不经常写入。在这种情况下,使用关系数据库就是“杀鸡用牛刀”,因为关系数据库旨在管理比一组简单的键值对更复杂的数据模型。

(2)因为数据是定期访问的,但是很少更改,所以数据必须具有低延迟的可读性。

(3)数据存储必须具有高可用性,并且靠近读取数据的服务。配置数据存储不能完全关闭,否则它将成为应用程序的单点故障。

在第 3 章中,将介绍如何使用简单的键值数据存储之类的工具来管理微服务应用程序配置数据。

2.4.3 服务注册和发现:客户端如何与微服务通信

从微服务消费者的角度来看,微服务应该是位置透明的,因为在基于云的环境中,服务器是短暂的。短暂意味着承载服务的服务器通常比在企业数据中心运行的服务的寿命更短。可以通过分配给运行服务的服务器的全新 IP 地址来快速启动和拆除基于云的服务。

通过坚持将服务视为短暂的可自由处理的对象,微服务架构可以通过运行多个服务实例来实现高度的可伸缩性和可用性。服务需求和弹性可以在需要的情况下尽快进行管理。每个服务都有一个分配给它的唯一和非永久的 IP 地址。短暂服务的缺点是,随着服务的不断出现和消失,手动或手工管理大量的短暂服务容易造成运行中断。

微服务实例需要向第三方代理注册。此注册过程称为服务发现(见图 2-6 中的步骤 3,以及图 2-9 中有关此过程的详细信息)。当微服务实例使用服务发现代理进行注册时,微服务实例将告诉发现代理两件事情:服务实例的物理 IP 地址或域名地址,以及应用程序可以用来查找服务的逻辑名称。某些服务发现代理还要求能访问到注册服务的 URL,服务发现代理可以使用此 URL 来执行健康检查。

图 2-9 服务发现代理抽象出服务的物理位置

然后,服务客户端与发现代理进行通信以查找服务的位置。

2.4.4 传达微服务的“健康状况”

服务发现代理不只是扮演了一名引导客户端到服务位置的交通警察的角色。在基于云的微服务应用程序中,通常会有多个服务实例运行,其中某些服务实例迟早会出现一些问题。服务发现代理监视其注册的每个服务实例的健康状况,并从其路由表中移除有问题的服务实例,以确保客户端不会访问已经发生故障的服务实例。

在发现微服务后,服务发现代理将继续监视和 ping 健康检查接口,以确保该服务可用。这是图 2-6 中的步骤 4。图 2-10 提供了此步骤的上下文。

图 2-10 服务发现代理使用公开的健康状况 URL 来检查微服务的“健康状况”

通过构建一致的健康检查接口,我们可以使用基于云的监控工具来检测问题并对其进行适当的响应。

如果服务发现代理发现服务实例存在问题,则可以采取纠正措施,如关闭出现故障的实例或启动另外的服务实例。

在使用 REST 的微服务环境中,构建健康检查接口的最简单的方法是公开可返回 JSON 净荷和 HTTP 状态码的 HTTP 端点。在基于非 Spring Boot 的微服务中,开发人员通常需要编写一个返回服务健康状况的端点。

在 Spring Boot 中,公开一个端点是很简单的,只涉及修改 Maven 构建文件以包含 Spring Actuator 模块。Spring Actuator 提供了开箱即用的运维端点,可帮助用户了解和管理服务的健康状况。要使用 Spring Actuator,需要确保在 Maven 构建文件中包含以下依赖项:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

如果访问许可证服务上的 http://localhost:8080/health 端点,则应该会看到返回的健康状况数据。图 2-11 提供了返回数据的示例。

图 2-11 服务实例的健康状况检查使监视工具能确定服务实例是否正在运行

如图 2-11 所示,健康状况检查不仅仅是微服务是否在运行的指示器,它还可以提供有关运行微服务实例的服务器状态的信息,这样可以获得更丰富的监控体验。 [2]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。