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

被称为“十二要素应用程序”的指南于十多年前首次发布。从那时起,几乎所有强制实践都已成为编写和部署 Web 应用程序的事实上的标准方式。尽管面对应用程序组织和部署方式的变化,它们仍然适用,但在某些情况下,需要额外的细微差别来了解这些实践如何应用于开发和部署应用程序的微服务模式。

本博客重点介绍因素 3,即在环境中存储配置,其中指出:

  • 配置是部署环境(十二因素应用称为部署)之间变化的所有内容。
  • 配置必须与应用代码严格分开 – 否则它怎么会因部署而异?
  • 配置数据存储在环境变量中。

当您进入微服务时,您仍然可以遵守这些准则,但并不总是以映射前的方式这实际上是十二因素应用程序的字面解释。一些准则(例如提供配置数据作为环境变量)可以很好地延续。其他常见的微服务实践在尊重十二因素应用程序的核心原则的同时,更像是它的扩展。在这篇文章中,我们将通过因素 3 的视角来探讨微服务配置管理的三个核心概念:

  • 明确定义您的服务配置
  • 如何向服务提供配置
  • 使服务可作为配置使用

关键微服务术语和概念

在开始讨论针对微服务调整因素 3 之前,了解一些关键术语和概念会很有帮助。

  • 单体应用架构 – 一种传统的架构模型,将应用功能分为组件模块,但将所有模块包含在一个代码库中。
  • 麦克罗斯服务应用程序架构 – 一种架构模型,它从多个小组件构建大型复杂的应用程序,每个组件执行一组范围明确的操作(例如身份验证、通知或支付处理)。 “微服务”也是小组件本身的名称。在实践中,一些“微服务”实际上可能非常大。
  • 服务 – 系统中单个应用或微服务的通用术语。
  • 系统 – 在本博客中,是指一整套微服务和支持基础设施,它们组合在一起可创建组织提供的完整功能。
  • Artifact – 由测试和构建管道创建的对象。它可以采用多种形式,例如包含应用程序代码的 Docker 映像。
  • 部署 – 工件的运行“实例”,在暂存、集成或生产等环境中运行。

微服务ces 与整体架构

通过单体应用程序,组织中的所有团队都在同一应用程序和周围基础设施上工作。尽管整体应用程序通常看起来比纸面上的微服务更简单,但组织决定迁移到微服务有几个常见原因:

  • 团队自治 – 在整体中​​定义功能和子系统的所有权可能很棘手。随着组织的成长和成熟,应用程序功能的责任通常分布在越来越多的团队中。这会在团队之间产生依赖关系,因为拥有一项功能的团队并不拥有整体中的所有相关子系统。
  • 减小“影响范围” – 当大型应用作为一个单元进行开发和部署时,一个子系统中的错误可能会降低整个应用的功能。
  • 独立扩展功能 – 即使只是单声道中的单个模块精简应用程序负载很重,组织必须部署整个应用程序的许多实例,以避免系统故障或性能下降。

当然,微服务也面临着自己的挑战 – 包括复杂性增加、可观察性降低以及需要新的安全模型 – 但许多组织,尤其是大型或快速发展的组织,认为为他们的团队带来这些挑战是值得的在为客户提供的体验创建可靠、稳定的基础方面拥有更大的自主权和灵活性。

微服务架构所需的更改

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

  • 以可预测的方式接受配置更改
  • 以可预测的方式让更广泛的系统了解自己
  • 有详细记录

对于单体应用程序来说,流程中的小不一致和对共享假设的依赖并不重要。与一个然而,许多独立的微服务,这些不一致和假设可能会带来很多痛苦和混乱。您需要对微服务进行的许多更改都是​​技术必需的,但令人惊讶的数量涉及团队内部工作以及与其他团队交互的方式。

微服务架构带来的显着组织变化包括:

  • 团队不再在同一代码库上一起工作,而是完全独立,每个团队完全负责一项或多项服务。在最常见的微服务实施中,团队也会被重组为“跨职能”的,这意味着他们的成员具备完成团队目标所需的所有能力,并且对其他团队的依赖程度最小。
  • 平台团队(负责系统的整体健康状况)现在必须协调不同团队拥有的多个服务,而不是处理单个应用程序。
  • 工具团队必须重新打造能够为各个服务所有者团队提供工具和指导,帮助他们快速实现目标,同时保持系统稳定。

明确定义您的服务配置

我们需要扩展因素 3 的微服务架构领域涉及需要明确定义有关服务的某些重要信息(包括其配置),并假设与其他服务至少有共享上下文。因素 3 并没有直接解决这个问题,但对于为应用程序功能做出贡献的大量独立微服务而言,这一点尤其重要。

作为微服务架构中的服务所有者,您的团队拥有在整个系统中扮演特定角色的服务。其服务与您的服务交互的其他团队需要访问您的服务的存储库以读取代码和文档以及做出贡献。

此外,这是软件开发中的一个不幸的现实开发领域,团队成员经常发生变化,不仅因为开发人员加入和离开公司,还因为内部重组。此外,特定服务的责任也经常在团队之间转移。

鉴于这些现实,您的代码库和文档需要非常清晰和一致,这是通过以下方式实现的:

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

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

坚固耐用且易于安装iscoverable 架构使您的团队成员和其他人可以轻松自信地与您的服务进行交互。

如何向服务提供配置

明确定义应用程序所需的配置值后,您还需要尊重已部署的微服务应用程序从中提取其配置的两个主要来源之间的重要区别:

  • 显式定义配置设置并随附应用程序源代码的部署脚本
  • 部署时查询外部来源

部署脚本是微服务架构中常见的代码组织模式。由于它们自十二要素应用程序最初发布以来是新的,因此它们必然代表了它的扩展。

模式:应用程序旁边的部署和基础设施配置

近年来,有一个名为基础设施(或某些变量)的文件夹已变得很常见ant)与您的应用程序代码位于同一存储库中。它通常包含:

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

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

事实上,将其放置在您的应用程序旁边意味着基础架构文件夹实际上尊重规则,同时实现有价值的流程改进,这对于在微服务环境中工作的团队至关重要。

这种模式的好处包括:

  • 拥有服务的团队还拥有服务部署和服务特定基础设施(例如数据库)的部署。
  • 所属团队可以确保对任何这些元素的更改都经过其开发流程(代码审查、CI)。
  • 团队可以轻松更改其服务和支持基础架构的部署方式,而无需依赖外部团队为其工作。

请注意,此模式提供的好处增强了各个团队的自主权,同时还确保部署和配置过程更加严格。

哪种类型的配置在哪里?

实际上,您可以使用存储在基础结构文件夹中的部署脚本来管理脚本本身中显式定义的配置,以及在部署时通过服务的部署脚本从外部源检索配置:

  • 直接定义某些配置值
  • 定义执行部署脚本的进程可以在何处查找所需的配置值n 外部来源
  • 可以直接在基础架构文件夹中的文件中指定特定于服务的特定部署且完全在您的团队控制之下的配置值。一个示例可能是限制应用程序启动的数据库查询运行的时间长度。该值可以通过修改部署文件并重新部署应用程序来更改。

    此方案的一个好处是,对此类配置的更改必须经过代码审查和自动化测试,从而减少错误配置的值导致中断的可能性。在任何给定时间对经过代码审查的值和配置键值所做的更改都可以在源代码管理工具的历史记录中发现。

    应用程序运行所需但不受团队控制的值必须由部署应用程序的环境提供。一个例子是 h服务连接到它所依赖的另一个微服务的 ostname 和端口。

    由于该服务不属于您的团队所有,因此您无法对端口号等值做出假设。此类值可以随时更改,并且在更改时需要向某些中央配置存储进行注册 – 无论更改是手动完成还是通过某些自动过程完成。然后,依赖它们的应用程序可以查询它们。

    我们可以将这些准则总结为微服务配置的两个最佳实践。

    微服务配置不应:依赖硬编码或共同商定的值

    在部署脚本中对某些值进行硬编码似乎最简单,例如与服务交互的服务的位置。实际上,对该类型的配置进行硬编码是危险的,尤其是在服务位置经常变化的现代环境中。而且特别危险如果您没有第二项服务,请联系我们。

    您可能认为您可以依靠自己的努力来更新脚本中的服务位置,或者更糟糕的是,您可以依靠拥有团队在位置更改时通知您。在压力大的时候,勤奋​​往往会减弱,并且依赖于人的严谨性会使您的系统面临毫无预警的失败风险。

    微服务配置的作用:让服务询问“我的数据库在哪里?”

    无论位置信息是否硬编码,您的应用程序都不得依赖于位于特定位置的关键基础设施。相反,新部署的服务需要询问系统内的一些常见问题,例如“我的数据库在哪里?”并收到有关该外部资源当前位置的准确答案。让每个服务在部署时向系统注册,使事情变得更加简单。

    使服务可作为配置使用

    正如系统需要提供的回答“我的数据库在哪里?”的问题以及“我所依赖的‘服务 X’在哪里?”,服务必须以这样的方式向系统公开,以便其他服务可以轻松找到它并与之对话,而无需了解它的部署方式。

    微服务架构中的一个关键配置实践是服务发现:新服务信息的注册以及其他服务访问的该信息的动态更新。在解释了为什么服务发现对于微服务来说是必要的之后,让我们探索一个如何使用 NGINX 开源和 Consul 来实现它的示例。

    同时运行一个服务的多个实例(部署)是常见的做法。这不仅可以处理额外的流量,还可以通过启动新的部署来更新服务,而无需停机。 NGINX 等工具充当反向代理和负载均衡器,处理传入流量并将其路由到最合适的设备安斯。这是一个很好的模式,因为依赖于您的服务的服务仅向 NGINX 发送请求,并且不需要了解有关您的部署的任何信息。

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

    现在,如果您的应用程序变得流行怎么办?这被认为是个好消息,但随后您会注意到,由于流量增加,消息传递实例消耗了大量 CPU,并且需要更长的时间来处理请求,而数据库似乎运行得很好。这表明您也许可以通过部署另一个消息服务实例来解决该问题。

    当您部署消息服务的第二个实例时,NGINX 如何知道它处于活动状态并开始向其发送流量?手动将新实例添加到 NGINX 配置中是一种方法,但随着更多服务的扩展和缩减,它很快就会变得难以管理。

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

    NGINX 可以使用多种方法查询像 Consul 这样的注册表,并相应地调整其路由。回想一下,当 NGINX 充当反向代理或负载均衡器时,会将流量路由到“上游”服务器。考虑这个简单的配置:

    # 定义一个名为“messenger_service”的上游组
    上游messenger_service {
    服务器172.18.0.7:4000;
    服务器172.18.0.8:4000;
    }

    服务器 {
    听80;

    位置/api {
    # 代理 HTTP 流量,路径以“/api”开头
    # 上面的“上游”块。默认的负载均衡算法,
    # 循环,在两个服务器之间交替请求
    # 在块中。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 和微服务 3 月

    无可否认,微服务增加了服务的技术术语和与其他团队的关系的组织术语的复杂性。为了享受微服务架构的好处,重要的是要批判性地重新检查为单体设计的实践,以确保它们在应用于非常不同的环境时仍然提供相同的好处。在本博客中,我们探讨了十二因素应用的因素 3 如何在微服务环境中仍然提供价值,但可以从其具体应用方式的微小变化中受益。

    要详细了解如何将十二要素应用应用到微服务架构,请查看 2023 年 3 月的《微服务》第 1 单元(共同很快就会在博客上出现)。免费注册即可访问有关该主题的网络研讨会和动手实验室。


    评论

    发表回复

    您的电子邮箱地址不会被公开。 必填项已用 * 标注