创建云原生应用程序:15个要素

0.背景

应用程序的15个要素是开发人员为构建云原生应用程序而定义的一种方法。其主要原则如下:

  • 使用声明性格式实现设置自动化,从而最大限度地减少新开发人员加入项目的时间和成本;
  • 与底层操作系统有清晰的契约,在执行环境之间提供最大的可移植性;
  • 适合部署在现代云平台上;
  • 最大限度地减少开发和生产之间的差异,实现持续部署,实现最大灵活性;
  • 无需对工具、架构或开发实践进行重大更改即可进行扩展;

该方法论的15个要素分别是:

  • 代码库-- 一个代码库在修订控制中跟踪,多次部署
  • API优先
  • 依赖关系- 明确声明和隔离依赖关系
  • 配置-- 在环境中存储配置
  • 支持服务-- 将支持服务视为附加资源
  • 构建、发布、运行------严格分离构建和运行阶段
  • 进程-- 将应用程序作为一个或多个无状态进程执行
  • 端口绑定-- 通过端口绑定导出服务
  • 并发性------通过进程模型进行扩展
  • 可处理性------通过快速启动和正常关闭最大程度地提高稳健性
  • 开发/生产一致性------尽可能保持开发、准备和生产环境的相似性
  • 日志------将日志视为事件流
  • 管理流程-- 将管理/管理任务作为一次性流程运行
  • 远程控制
  • 身份验证与授权

1.代码库

云原生应用程序必须始终由在版本控制系统中跟踪的单个代码库组成。代码库是一个源代码存储库或一组共享公共根的存储库,用于生成任意数量的不可变版本。应用程序和代码库之间应该是 1:1 的关系,但代码库和应用程序部署之间应该是一对多的关系。这个单一代码库有助于支持开发团队之间的协作,并有助于实现应用程序的正确版本控制。

此代码库可以是 Git 存储库(包括 GitHub、GitHub Enterprise、GitLab 等)。我们的演示应用程序存储在托管在 GitHub 上的 Git 存储库中。Git 是一种修订控制工具,允许多人同时开发项目并确保所有代码都是安全的。或者,您可以使用其他工具,如 BitBucket、SourceForge、特定于云的源存储库等。

2.API优先

注:这些方法中的某些因素不一定映射到云施加的特定物理要求,而是更多地与构建云原生应用程序时的人们和组织的习惯有关。

在为云开发企业应用程序时,该应用程序通常会成为服务生态系统的参与者。但如果应用程序内的 API 定义不明确,则可能导致该生态系统内的集成失败。此因素正是旨在帮助缓解这种情况。

增加这一因素有助于正式将 API 视为开发过程的一流产物。API 优先方法涉及开发一致且可重复使用的 API,使团队能够根据彼此的公共合同开展工作,而不会干扰内部开发流程。通过利用 API 优先方法并明确规划客户端应用程序和服务将使用的各种 API,每个 API 都可以非常清晰地设计为尽可能有效,并且可以轻松模拟。这可以实现与利益相关者的更好协作,并使开发人员和架构师能够在投入过多资金支持给定 API 之前测试或审查他们的方向/计划。每个 API 的清晰设计流程还可以为每个 API 创建更有效的文档。提供精心设计、全面且易于遵循的文档对于确保开发人员拥有良好的 API 体验至关重要。

API 描述语言有助于建立 API 行为规范。API 描述语言是领域特定语言,适合描述 API。它们是直观的语言,API 开发人员、API 设计人员和 API 架构师可以轻松编写、阅读和理解。与编程语言或 API 实现语言相比,API 描述语言使用更高级别的抽象和声明式范式,这意味着它们可用于帮助表达"是什么"而不是"如何"------它们可以帮助定义可能的响应的数据结构(什么),而不是描述如何计算响应。在 API 的初始设计阶段,这对于模拟 API 和收集利益相关者的反馈特别有用。API 描述语言的示例包括 OpenAPI、Swagger 和 RAML。

为了在应用程序源代码中清晰地定义 API,可以使用 API 规范中的标准模型。API 规范可以广泛地了解 API 的行为方式以及特定 API 与其他 API 的链接方式。它解释了 API 的运行方式以及使用时预期的结果。API 规范的一个例子是 OpenAPI 规范。OpenAPI v3 规范定义了一个与语言无关的标准接口来描述 REST API,从而允许从 API 本身生成文档。MicroProfile 规范以其 OpenAPI 1.0 组件为基础,该组件提供了一组 Java 接口和编程模型,允许 Java 开发人员从其 JAX-RS 应用程序本地生成 OpenAPI v3 文档。

3.依赖

大多数应用程序都需要使用外部依赖项- 例如,在我们的常见的应用程序中,我们依赖某些 Liberty 功能,更具体地说是 servlet-3.1、jsonp-1.0 和 jaxrs-2.0。这些依赖项必须在构建过程中删除,因为无法保证应用程序所依赖的特定依赖项已存在于系统/运行时中。云原生应用程序永远不能依赖于系统范围包的隐式存在。这就是此因素关注的重点 - 鼓励明确声明和隔离应用程序依赖项。这有助于在开发和生产环境之间提供一致性,简化应用程序新开发人员的设置,并支持云平台之间的可移植性。

实现这一因素的第一步是识别、声明和隔离应用程序中的任何外部依赖项。大多数现代编程语言都有用于管理这些依赖项的工具或设施。在 Java 中,两个最流行的依赖项管理工具是 Maven 和 Gradle。这些工具有助于简化依赖项管理固有的复杂性,从而使开发人员能够声明他们的依赖项,然后让工具负责实际确保满足这些依赖项。因此,您不必在微服务中打包第三方库,而是可以在 Mavenpom.xml文件或 Gradlesettings.gradle文件中指定所有依赖项。这使您可以自由升级到较新的版本,并允许将确保依赖项得到满足的责任交给构建工具而不是开发人员。

在我们的常见的应用程序中,我们使用 Maven,但您可以将 Maven 或 Gradle 与 Open Liberty 应用程序一起使用。使用 Maven 时,您必须在构建文件中声明依赖项(pom.xml对于 Maven),并在构建时从 Maven Central Repository 下载所需的软件包以编译代码。打包步骤使用 Maven 或 Gradle Liberty 插件从存储库中拉取 Liberty 运行时并生成包含服务器配置和带有已编译应用程序代码的 WAR 文件的打包 Liberty 服务器。因此,应用程序没有任何它假定已存在于系统中的依赖项。

4.配置

配置是指在部署过程中(例如,开发人员工作站、QA 和生产)可能发生变化的任何值。这可能包括:

  • 有关支持服务(例如 Web 服务和 SMTP 服务器)的 URL 和其他信息
  • 定位和连接数据库所需的信息
  • 第三方服务(如 Amazon AWS)或 Google Maps、Twitter 和 Facebook 等 API 的凭证
  • 通常捆绑在属性文件或配置 XML 或 YML 中的信息

将配置和凭据与应用程序代码分开很重要。凭据是高度敏感的信息,绝不能包含在应用程序代码中,因为这样可能会暴露应用程序的支持服务、内部 URL、应用程序所依赖的资源和服务等。外部化配置也很重要,因为它使我们能够将应用程序部署到多个环境,而不管我们使用哪种云运行时。换句话说,应用程序不应该关心它在什么环境中运行,我们也不应该需要更改应用程序以在其他环境中运行它。将这些配置值存储在环境变量中被视为外部化此配置的最佳实践。这种方法有助于简化应用程序到多个环境的部署,降低泄露凭据和密码的风险,并实现更有效的发布管理。

利用 MicroProfile Config 等工具可以帮助您将配置放置在属性文件中,从而实现配置外部化,这些文件可以轻松更新,而无需重新编译微服务。这可确保我们的配置存储在环境中并在运行时访问,而不是嵌入在代码本身中。它还能够轻松动态更改配置值。在我们的演示应用程序中,我们使用 MicroProfile Config 并将配置存储在属性文件中,例如microprofile-config.properties文件,这些文件在应用程序代码之外外部化。

5.支撑服务

支撑服务是您的应用程序赖以运行的任何服务。一些最常见的支持服务类型包括数据存储、消息传递系统、缓存系统以及许多其他类型的服务,包括执行业务线功能或安全性的服务。

此因素侧重于将这些支持服务视为绑定资源。绑定资源只是将您的应用程序连接到支持服务的一种方式。数据库的资源绑定可能包括用户名、密码和允许您的应用程序使用该资源的 URL。应用程序应声明其对给定支持服务的需求,但允许云环境执行实际的资源绑定。应用程序与其支持服务的绑定应通过外部配置完成。应该可以随意将支持服务附加到应用程序或从应用程序分离,而无需重新部署应用程序。您的应用程序中永远不应该有一行代码将应用程序与特定的支持服务紧密耦合。

将支撑服务作为绑定资源可以使云原生应用程序具有更大的灵活性和弹性,从而实现服务和部署之间的松散耦合(例如,注意到数据库出现故障的管理员可以启动该数据库的新实例,然后更改其应用程序的绑定以指向这个新数据库)。

6.构建、发布和运行

此因素侧重于获得一个没有循环的明确定义的流程,并要求严格区分每个部署阶段。本质上,单个代码库经过构建过程生成编译的工件,然后将其与应用程序外部的配置信息合并以生成不可变的版本,然后将其交付到云环境(开发、QA、生产等)并运行。关键点是每个部署阶段都是独立的并且单独发生。

构建阶段专注于构建应用程序所需的一切;在将构建与配置一起打包以形成发布工件时以及在运行应用程序时,不应有额外的构建步骤。在此阶段,收集在设计阶段声明的依赖项并将其捆绑到构建工件(例如 WAR 或 JAR 文件)中。构建的输出是一个打包的服务器,其中包含运行应用程序所需的所有与环境无关的配置。这对于需要能够部署到多个不同环境的云原生应用程序尤其重要。

发布阶段专注于将构建阶段的输出与配置值(环境和应用特定)结合起来,以生成另一个版本。通过为这些版本添加唯一 ID 标签,可以提高回滚到先前版本的能力(如果有任何异常,则回滚到历史审计)。

运行阶段发生在云提供商上,通常使用容器和进程等工具来启动应用程序。一旦该操作运行,云运行时将负责其维护、运行状况和动态扩展。

7.流程

流程因素强化了应用程序应作为单个无状态进程执行的概念。换句话说,所有长期状态都应在应用程序外部,由支持服务提供。状态不应在应用程序内维护。这是一个有用的因素,因为这意味着如果应用程序的一个实例发生故障,您不会丢失当前状态。它还简化了工作负载平衡,因为您的应用程序与服务的任何特定实例没有亲和力。

REST 是一种广泛采用的传输协议,JAX-RS 可用于实现 RESTful 架构。利用 REST 范式的系统是无状态的。这样,底层基础设施可以销毁或创建新的微服务而不会丢失任何信息。Jakarta RESTful Web Services 和 MicroProfile Rest Client 是用于编写和使用 RESTful 服务的有用 API。我们的演示应用程序不会在微服务本身中存储状态。相反,它利用 REST 协议并利用 Jakarta REST 和 MicroProfile Rest Client 来执行此操作。

8.端口绑定

端口绑定因素表明云原生应用程序应使用端口绑定导出服务。换句话说,用于访问服务的主机和端口应由环境提供,而不是嵌入到应用程序中,这样您就不会依赖于该端点的预先存在或单独配置的服务。您的云提供商应该为您管理端口分配,因为它可能还管理路由、扩展、高可用性和容错,所有这些都需要云提供商管理网络的某些方面,包括将主机名路由到端口和映射。Web 应用程序(尤其是那些已经在企业内部运行的应用程序)通常在某种服务器容器中执行 - Open Liberty、Liberty、WebSphere 等。在非云环境中,Web 应用程序部署到这些容器中,然后容器负责在应用程序启动时为其分配端口。

在云中部署它们时,端口需要能够更改,因此拥有重新绑定端口的方法很重要。MicroProfile Config 可以成为一种有用的工具来帮助实现这一点。您可以在 Kubernetes ConfigMap 中指定新端口,MicroProfile Config 会自动选择该值以向已部署的微服务提供正确的信息。MicroProfile Rest Client 是另一个有用的工具。它有助于创建客户端代码以从一个微服务连接到另一个微服务。我们的演示应用程序同时使用了 MicroProfile Config 和 MicroProfile Rest Client。

Open Liberty Operator 还可以成为另一个有用的工具,通过启用自动服务绑定来促进此因素所鼓励的行为。操作员会自动更新应用程序之间的绑定信息,这意味着它连接应用程序并维护有关特定应用程序是否生成或使用服务的信息。有了这些信息,操作员会自动处理 Kubernetes 级别的详细信息,包括创建和注入 Kubernetes Secrets,以便您的应用程序可以不间断地连接到所需的服务。

9.并发性

并发因素强调,微服务应该能够根据其工作负载弹性地扩大或缩小。以前,当许多应用程序被设计为整体并在本地运行时,这种扩展是通过垂直扩展(即添加 CPU、RAM 和其他资源,无论是虚拟的还是物理的)实现的。但是,现在我们的应用程序更加细粒度并在云中运行,一种更现代的方法,一种适合云支持的弹性可扩展性的方法,是向外扩展或水平扩展。您无需将单个大进程变得更大,而是创建多个进程,并在这些进程之间分配应用程序的负载。

Kubernetes 自动缩放工具可以帮助解决此问题。Horizo​​ntal Pod Autoscaler 根据观察到的 CPU 利用率(或者,在自定义指标支持下,根据其他一些应用程序提供的指标)自动缩放复制控制器、部署、副本集或有状态集中的 Pod 数量。它作为 Kubernetes API 资源和控制器实现。资源决定控制器的行为。控制器定期调整复制控制器或部署中的副本数量,以使观察到的平均 CPU 利用率与用户指定的目标相匹配。

另一个有用的工具是 Open Liberty Operator,尤其是在部署到 OpenShift 时。您可以配置水平自动扩展,以根据资源消耗创建和删除应用程序实例。这种运行应用程序的多个实例并自动扩展它们的能力意味着您的应用程序具有高可用性。

10.可处理性

在云实例上,应用程序的生命周期与支持它的基础设施一样短暂。云原生应用程序的进程必须是一次性的,这意味着它们可以快速启动或停止。如果应用程序无法快速启动和正常关闭,则无法快速扩展、部署、发布或恢复。这在云原生应用程序中尤其重要,因为如果您正在启动应用程序,并且需要几分钟才能进入稳定状态,那么在当今高流量的世界中,这可能意味着在应用程序启动时数百或数千个请求被拒绝。此外,根据部署应用程序的平台,如此缓慢的启动时间可能会触发警报或警告,因为应用程序未通过健康检查。极慢的启动时间甚至可能阻止您的应用程序在云中启动。如果您的应用程序负载不断增加,并且您需要快速启动更多实例来处理该负载,则启动过程中的任何延迟都可能妨碍其处理该负载的能力。如果应用程序不能快速而正常地关闭,这也会妨碍在故障后重新启动它的能力。无法足够快地关闭还会存在无法处理资源的风险,从而可能损坏数据。

看待这个问题的一种方式是牛与宠物模型。我们的应用实例应该更多地被视为牛(即,对它们没有感情依恋,相当容易替换,编号不命名等),而不是宠物(即,有感情依恋,照料它们恢复健康而不是替换它们等)。

Open Liberty 的一大特色是其快速的服务器启动和关闭时间,这有助于支持这一因素的目的。要了解更多信息,请查看我们的Open Liberty 更快的启动时间博客。您还可以将其与 OpenJ9 等快速开源 JVM 结合使用。有关更多信息,请参阅我们的IBM Semeru Runtimes 博客(该博客利用了 OpenJ9)或查看此Eclipse 博客(其中详细介绍了您可以从 OpenJ9 中获得的性能优势)。

我们的常见应用程序利用了 Open Liberty 的快速启动和关闭时间,此外,它在启动时不执行额外的配置步骤,也不要求在关闭时执行任何清理操作以进一步增强这些功能。因此,我们有一个可以快速启动且在出现问题时可以轻松重新启动的应用程序。

此外,实施容错行为可以实现这种一次性行为,有助于正常关闭任何故障或不健康的微服务。MicroProfile 容错可以帮助实现这一点。

11.开发/生产评价

开发/生产同等因素侧重于保持开发、准备和生产环境尽可能相似的重要性。这很重要,因为这样您可以确保在开发和测试中而不是在应用程序投入生产时发现所有潜在的错误/故障。这有助于消除"它在我的笔记本电脑上运行"这种陈词滥调的开发说法。现在许多应用程序都在云中运行,与大型服务生态系统中的许多其他服务交互,因此在开发和测试应用程序时复制此环境非常重要。

像 Docker 这样的工具可以帮助实现这种开发/测试/生产奇偶校验。容器的好处是它为运行代码提供了一个绝对统一的环境。这个环境很容易"丢弃"并重新创建。轻松锁定环境的每个细节是消除开发、测试和生产环境之间差异这一目标的重要因素。容器支持在开发、准备和生产中创建和使用相同的映像。它还有助于确保在每个环境中使用相同的支持服务。利用这种容器化概念,MicroShed Testing 等测试工具使我们能够确保测试环境尽可能接近生产环境。

更进一步说,在许多云平台中,都有机会直接在云环境中开发、测试和运行应用程序 - 例如 Red Hat OpenShift Do (ODO) 工具。查看使用Open Liberty 和 ODO 直接在 OpenShift 中开发云原生 Java 应用程序,了解如何利用 Open Liberty 和 ODO 工具直接在云中进行开发。

在我们的常见的应用程序中,我们利用 Docker 和 Kubernetes 等容器技术来确保我们的环境尽可能相似。

12.日志

日志因素凸显了确保您的应用程序不关心其输出流(即日志)的路由、存储或分析的重要性。在云原生应用程序中,这些日志的聚合、处理和存储是云提供商或与所使用的云平台一起运行的其他工具套件(例如 ELK stack、Splunk、Sumologic 等)的责任。这在云原生应用程序中尤为重要,因为它们具有弹性扩展功能 - 例如,当您的应用程序从 1 个实例动态变为 100 多个实例时,很难知道这些实例在哪里运行并跟踪和组织所有日志。通过简化应用程序在这种日志聚合和分析中的部分,应用程序的代码库可以简化并更多地关注业务逻辑。这个因素有助于提高随时间推移自省行为的灵活性,并能够随时间推移有效地收集和分析实时指标。

为了实现这一因素,日志应被视为事件流:您应该实时流出日志,以便终止实例不会导致日志丢失。所有日志条目都应记录到stdout且stderr仅记录到。

Open Liberty 具有统一的日志记录组件,用于处理由应用程序和运行时写入的消息并提供首次故障数据捕获 (FFDC) 功能。应用程序使用 System.out、 System.err或 java.util.logging.Logger 流写入的日志记录数据将合并到服务器日志中。

13.管理流程

此因素不鼓励将一次性管理任务放入微服务中。常见的示例包括迁移数据库和运行一次性脚本进行清理。相反,这些应该作为一次性过程运行,并且可以作为 Kubernetes 任务运行。这样,您的微服务就可以专注于业务逻辑。它还可以安全地调试和管理生产应用程序,并为云原生应用程序提供更高的弹性。

14.远程控制

远程控制的使用应该是任何云原生应用程序的重要组成部分。以前,在本地构建应用程序使开发人员能够检查应用程序内部、执行调试器并执行数百项其他任务,从而相对轻松地深入了解应用程序及其行为。然而,在云中运行的应用程序没有这种直接访问权限,您的应用程序实例可能会在几乎没有任何警告的情况下移动到世界任何地方。除此之外,您可能只从一个应用程序实例开始,几分钟后,您可能会运行数百个应用程序副本。这些都是非常强大、有用的功能,但它们为实时应用程序监控和遥测提供了一种不熟悉的模式。

远程控制可以包括特定领域的指标(特定组织、部门或团队需要或要求的指标),以及应用程序的健康和系统指标。健康和系统指标包括应用程序启动、关闭、扩展、Web 请求跟踪以及定期健康检查的结果。MicroProfile Health 和 MicroProfile Metrics 是出色的开放云原生 Java 工具,可用于收集这些类型的指标。

15.身份验证与授权

安全性是任何应用程序和云环境的重要组成部分。无论应用程序是面向企业、移动设备还是云,安全性都不应是事后考虑。云原生应用程序可以使用基于角色的访问控制 (RBAC) 保护其端点。这些角色决定调用客户端是否具有足够的权限让应用程序接受请求,并有助于跟踪谁在发出请求以进行审计。

MicroProfile JWT(JSON Web Token)是一种基于令牌的身份验证机制,用于对用户进行身份验证、授权和验证,可以成为实现这一因素的有用开源 Java API。JSON Web Token(JWT)是一种自包含令牌,旨在以 JSON 对象的形式安全地传输信息。此 JSON 对象中的信息经过数字签名,可以被接收者信任和验证。对于微服务,基于令牌的身份验证机制为安全控制和安全令牌提供了一种轻量级的方式,可以在不同的服务之间传播用户身份。JWT 正在成为最常见的令牌格式,因为它遵循定义明确且众所周知的标准。MicroProfile JWT 标准定义了身份验证和授权所需的 JWT 格式。这些标准还将 JWT 声明映射到各种 Jakarta EE 容器 API,并通过 getter 方法提供声明集。

相关推荐
昌sit!7 小时前
K8S node节点没有相应的pod镜像运行故障处理办法
云原生·容器·kubernetes
茶馆大橘10 小时前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel
北漂IT民工_程序员_ZG10 小时前
k8s集群安装(minikube)
云原生·容器·kubernetes
coding侠客10 小时前
揭秘!微服务架构下,Apollo 配置中心凭啥扮演关键角色?
微服务·云原生·架构
IT-民工2111013 小时前
CI/CD 实践总结
运维·ci/cd·自动化
2301_8061313617 小时前
Kubernetes的基本构建块和最小可调度单元pod-0
云原生·容器·kubernetes
licy__21 小时前
Docker 基础命令简介
docker·云原生·eureka
0_1_bits1 天前
【系统设计】高效的分布式系统:使用 Spring Boot 和 Kafka 实现 Saga 模式
spring boot·后端·云原生·架构·kafka·linq
探索云原生1 天前
GPU 环境搭建指南:如何在裸机、Docker、K8s 等环境中使用 GPU
ai·云原生·kubernetes·go·gpu
蚊子不吸吸1 天前
DevOps开发运维简述
linux·运维·ci/cd·oracle·kubernetes·gitlab·devops