BLOG | NGINX

配置微服务应用的最佳实践

NGINX-Part-of-F5-horiz-black-type-RGB
Javier Evans 缩略图
Javier Evans
Published March 02, 2023

《十二要素应用(twelve-factor app)》指南于十多年前首次发布。从那时起,几乎所有其规定的做法都成为了编写和部署网络应用的事实标准。尽管这些做法在应用的组织和部署方式发生变化后仍然适用,但在某些情况下,要理解这些做法如何应用于开发和部署应用的微服务模式,还需要注意一些额外的细微差别。

本文主要讲述要素 3 — 在环境中存储配置,其要义是:

  • 配置因部署环境(《十二要素应用》称之为“部署 (deploys)”)而异。
  • 配置必须与应用的代码严格分离,否则它如何随着部署变化?
  • 配置数据存储在环境变量中。

对于微服务,您仍然可以遵守这些准则,但不一定完全拘泥于《十二要素应用》的字面含义。有些准则,例如将配置数据作为环境变量,完好地延续了下来。而其他常见的微服务实践,虽然遵循《十二要素应用》的核心原则,但更像是对它的扩展。在这篇文章中,我们将从要素 3 的角度来理解微服务配置管理的三个核心概念:

 

关键的微服务术语和概念

在讨论如何根据微服务调整要素 3 之前,了解一些关键术语和概念是很有帮助的。

  • 单体应用架构 — 一种传统的架构模型,将应用功能拆分成多个组件模块,但将所有模块包含在单个代码库中。
  • 微服务应用架构 — 一种利用多个小组件构建大型复杂应用的架构模型,其中每个小组件都执行一组范围明确的操作(如身份验证、通知或支付处理)。“微服务”也是小组件本身的名称。一些“微服务”实际上可能相当大。
  • 服务(service) — 系统中单个应用或微服务的总称。
  • 系统 — 在本文中是指可供企业结合使用以创建完整功能的一整套微服务和支持基础架构。
  • Artifact(构件) — 由测试和构建管道创建的对象。它可以有多种形式,比如包含应用代码的 Docker 镜像。
  • 部署(deployment) — 一个运行的 Artifact(构件)“实例”,它在测试、集成或生产等环境中运行。

微服务与单体应用的比较

在单体应用中,企业中的所有团队都使用同一个应用及其相关基础架构。尽管单体应用通常看起来比微服务简单,但由于以下几个常见原因,企业会决定迁移至微服务:

  • 团队自主权 — 在单体应用中定义功能和子系统的所有权可能很棘手。随着企业的发展和成熟,与应用功能相关的职责往往会被分配给越来越多的团队。这会在团队之间产生依赖关系,因为这些团队只拥有部分功能职责,并不拥有单体应用中所有相关的子系统。
  • 缩小“爆炸半径” — 当一个大型应用作为一个单独的单元开发和部署时,一个子系统中的错误可能会影响整个系统应用的功能。
  • 独立扩展功能 — 即使只是单体应用中的单个模块处于高负载状态,企业也必须部署整个应用的多个实例,以避免系统故障或性能下降。

当然,微服务也有其自身的挑战,包括复杂性的增加、可观测性的降低以及对新安全模型的需求。但许多企业,尤其是大型企业或快速发展的企业,认为这些挑战是值得的,因为微服务可以让他们的团队拥有更高的自主权和灵活性,奠定可靠、稳定的基础以打造出色的客户体验。

微服务架构所需的变更

当您将单体应用重构为微服务时,您的 service 必须:

  • 以可预测的方式接受配置变更
  • 以可预测的方式提供给更广泛的系统
  • 有充分的文件记录

对于单体应用来说,轻微的流程不一致和对共同假设的依赖不算是大问题。但对于许多独立的微服务来说,这些不一致和假设可能会带来很多麻烦和混乱。您对微服务进行的许多更改都是出于技术需要,但还有很多更改涉及到团队内外的工作方式。

微服务架构给企业带来的显著变化包括:

  • 团队不再使用相同代码库,而是各自为营,每个团队全权负责一个或多个服务。在最常见的微服务实施中,团队被重组为“跨职能”团队,这意味着他们的成员具有完成团队目标所需的所有能力,几乎不依赖其他团队。
  • 平台团队(负责系统的整体运行状况)现在必须协调不同团队拥有的多个服务,而不是处理单个应用。
  • 工具团队必须能够为不同的服务所有者团队提供工具和指导,以帮助他们快速完成目标,同时保持系统的稳定性。

 

明确定义您的服务配置

在微服务架构中,我们需要扩展要素 3,这就需要明确定义关于服务的某些重要信息,包括其配置,并假设它与其他服务共享极少的上下文。要素 3 并没有直接解决这个问题,但当应用功能涉及许多独立的微服务时,这个问题就显得尤为重要。

作为微服务架构中的服务所有者,您的团队拥有在整个系统中发挥特定作用的服务。当其他团队的服务与您的服务进行交互时,他们需要访问您的服务代码库,以读取代码和文档,并做出贡献。

但不幸的是,在软件开发领域,由于公司开发人员的频繁流动和内部重组,团队成员经常发生变化。另外,特定服务的职责也经常在团队之间转移。

鉴于以上现实,您的代码库和文档需要通过以下方式保持清晰一致:

  • 明确定义每个配置选项的用途
  • 明确定义配置值的预期格式
  • 明确定义应用期望如何提供配置值
  • 将这些信息记录在有限数量的文件中

许多应用框架提供了定义所需配置的方法。例如,Node.js 应用的 convict NPM 包使用存储在单个文件中的完整配置“模式”。它充当了 Node.js 应用运行所需的所有配置的信源。

借助功能强大且易于发现的配置模式,您的团队和其他团队的成员可从容地与您的服务进行交互。

 

如何向服务提供配置

明确定义了应用所需的配置值后,您还需要注意已部署的微服务应用的两个主要配置来源之间的重要区别:

  • 明确定义配置设置并附带应用源代码的部署脚本
  • 部署时查询的外部来源

部署脚本是微服务架构中的一种常见代码组织模式。它们是在《十二要素应用》问世后推出的,因此代表了后者的延伸。

模式:贴近应用的部署和基础架构配置

近年来,在应用代码所在的代码库中通常会发现一个名为“基础架构”(或类似命名)的文件夹。它通常包含:

  • 基础架构即代码(Terraform 是一个常见例子),描述了服务所依赖的基础架构,比如数据库
  • 容器编排系统的配置,如 Helm charts 图表和 Kubernetes 清单
  • 与应用部署有关的任何其他文件

乍一看,这似乎违反了要素 3 关于配置与代码严格分离的规定。

但事实上,贴近应用的部署不仅遵守这一规则,还提供了流程的改进,这对在微服务环境中工作的团队至关重要。

这种模式的优势包括:

  • 拥有服务的团队也拥有服务部署和服务特定基础架构(如数据库)的部署。
  • 该团队可以确保对其中任何元素的更改都经过其开发流程(代码审查、CI)。
  • 团队可以轻松地更改其服务和支持基础架构的部署方式,无需依赖外部团队。

请注意,这种模式可以帮助提高各个团队的自主性,同时也确保了部署和配置过程的严谨性。

各类配置的适用位置

在实践中,您可以使用存储在基础架构文件夹中的部署脚本来管理在脚本中明确定义的配置,并在部署时从外部来源检索配置,方法是使用服务的部署脚本:

  1. 直接定义某些配置值
  2. 定义部署脚本执行流程从外部来源中查找所需配置值的位置

针对特定服务部署并且完全由您的团队控制的配置值可以直接在基础架构文件夹中的文件中进行指定。例如,应用可以预定义数据库查询允许运行时长的上限。这个值可以通过修改部署文件和重新部署应用来更改。

这种方案的一个好处是,对这种配置的更改必须经过代码审查和自动测试,从而降低了配置错误导致停机的可能性。在源代码控制工具的历史记录中,可以看到在任何特定时间对经过代码审查的值和配置密钥的值所做的更改。

应用运行所需但不受团队控制的值,必须由部署应用的环境提供,例如服务连接到其所依赖的另一个微服务的主机名和端口。

因为该服务不属于您的团队,所以您不能对端口号等值进行预设。这些值可以随时更改,并且在更改时需要在一些中央配置存储中进行注册 — 无论是手动更改还是通过一些自动流程进行更改。然后,依赖这些值运行的应用便可以查询这些值。

我们可以将这些指南凝练成微服务配置的两个最佳实践。

微服务配置不应依赖于硬编码或相互商定的值

在部署脚本中对某些值——例如与您的服务交互的服务的位置——进行硬编码看似轻而易举。但事实上,对这种类型的配置进行硬编码是存在危险的,特别是在服务位置经常更改的现代环境中。如果您不拥有这些服务的管理权,那么这种操作格外危险。

您可能会认为,您可以靠自己在脚本中更新服务位置,或者可以让拥有服务的团队在位置更改时通知您。然而忙中难免出纰漏,把系统的可靠性交给人类,依赖人的认真,只会使您的系统无征兆地直接停掉。

微服务配置应当让服务询问“我的数据库在哪里?”

无论位置信息是否经过硬编码,应用都不能依赖于关键基础架构存在于某个位置。新部署的服务需要在系统中询问一些常见的源问题,比如“我的数据库在哪里?”,并获得关于外部资源当前位置的准确答案。让每个服务在部署时都在系统中进行注册,这会让事情变得简单得多。

 

将服务作为配置提供

正如系统需要回答“我的数据库在哪里?”和“我使用的 ‘某服务’ 在哪里?”,必须将服务暴露给系统,以便其他服务可以轻松地找到并与它通信,而不需要知道它是如何部署的。

微服务架构中的一个关键配置实践是服务发现:注册新的服务信息,并在其他服务访问时动态更新这些信息。在解释了为何微服务需要服务发现后,下面我们来看一个如何用 NGINX 开源版和 Consul 实现服务发现的示例。

同时运行一个服务的多个实例(部署)很常见。这样不仅可以处理额外的流量,还可以通过启动新的部署在不停机的情况下更新服务。作为反向代理和负载均衡器,NGINX 等工具会处理传入流量并将其路由到最合适的实例。这是一种很好的模式,因为依赖您服务运行的服务只向 NGINX 发送请求,而不需要了解您的部署。

举个例子,假设您有一个名为 messenger 的服务实例在充当反向代理的 NGINX 后面运行。

如果您的应用成为了热门应用,会怎样呢?这算是个好消息,但由于流量增加,信使实例会消耗大量 CPU 并花费更长的时间来处理请求,而数据库似乎运转良好。这表明您可以通过部署信使服务的另一个实例来解决这个问题。

当您部署信使服务的第二个实例时,NGINX 如何知道它当前可用并开始向它发送流量呢?您可以手动向 NGINX 配置中添加新实例,但随着更多服务扩展和缩减,这种方法很快就会变得难以管理。

一种常见的解决方案是使用高度可用的服务注册中心(如 Consul)在系统中跟踪服务。新的服务实例在部署时通过 Consul 进行注册。Consul 通过定期发送健康检查来监控实例的状态。当实例未通过健康检查时,它将从可用服务列表中删除。

NGINX 可以使用各种方法查询 Consul 等注册中心,并相应地调整其路由。当 NGINX 充当反向代理或负载均衡器时,它会将流量路由到“上游(upstream)”服务器。看一下这个简单的配置:

# Define an upstream group called "messenger_service"upstream messenger_service {
    server 172.18.0.7:4000;
    server 172.18.0.8:4000;
}

server {
    listen 80;

    location /api {
        # Proxy HTTP traffic with paths starting with '/api' to the
        # 'upstream' block above. The default load-balancing algorithm, 
        # Round-Robin, alternates requests between the two servers 
        # in the block.
        proxy_pass http://messenger_service;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

默认情况下,NGINX 需要知道每个信使实例的精确 IP 地址和端口,才能将流量路由到它。在本例中,172.18.0.7 和 172.18.0.8 的端口都是 4000。

这就是 Consul 和 Consul 模板 的作用所在。Consul 模板与 NGINX 在同一个容器中运行,并与维护服务注册表的 Consul 客户端通信。

当注册表信息更改时,Consul 模板使用正确的 IP 地址和端口生成新版本的 NGINX 配置文件,将其写入 NGINX 的配置目录,并告诉 NGINX 重新加载其配置。NGINX 重新加载其配置时无需停机,新实例在重新加载完成后可立即开始接收流量。

如果使用反向代理(如这里的 NGINX),则可以在系统中注册单个接触点作为其他服务访问的位置。您的团队可以灵活地管理单个服务实例,而不必担心其他服务失去对整个服务的访问。

 

亲自体验 NGINX 和 Microservices June 微服务之月 2023

无论是从服务的技术角度来看,还是从与其他团队关系的组织角度来看,微服务无疑会增加复杂性。要想享受微服务架构的好处,必须严格地重新审查为单体应用设计的操作实践,以确保它们在应用于截然不同的环境时仍能带来同样的优势。在这篇博客中,我们探讨了《十二要素应用》中的要素 3 如何在微服务环境中继续提供价值,并通过轻微更改具体应用方式而获益。

如欲了解有关将《十二要素应用》应用于微服务架构的更多信息,请参见 Microservices June 微服务之月 2023 第一单元的相关内容。微服务之月是 NGINX 一年一度的免费线上教学项目,成功注册后即可获取更多有关“微服务交付”的学习资源,包括视频录像、动手实验、单元小测以及项目专属答疑群。


"This blog post may reference products that are no longer available and/or no longer supported. For the most current information about available F5 NGINX products and solutions, explore our NGINX product family. NGINX is now part of F5. All previous NGINX.com links will redirect to similar NGINX content on F5.com."