应用程序优先的云服务的日益普及导致应用程序与云服务的融合程度比以前更深。应用程序和云之间的运行时边界正在从虚拟机转移到容器和函数。集成边界正在从仅访问数据库和消息代理转向应用程序的机械部分混合并在云中运行的边界。在这个最终架构中,应用程序是“云绑定”的,允许开发人员通过将更多应用程序逻辑和管理职责转移到云服务中来专注于业务逻辑。
本文通过使用开放 API 和保留灵活性和可移植性的标准将应用程序绑定到云服务来研究整个软件堆栈的商品化。
内部架构演变
应用程序的内部架构通常由单个团队拥有和控制。根据选择的语言和运行时,包、模块、接口、类和函数等工具和抽象可帮助开发人员控制内部边界。领域驱动设计(DDD)帮助开发人员创建领域模型,这些模型作为封装复杂业务逻辑的抽象并调解业务现实和代码之间的鸿沟。
Hexagonal、Onion和Clean架构可以补充DDD,并以不同的边界和外部化的基础设施依赖性来安排应用程序代码。虽然这些方法在诞生之初就具有创新性,并且在今天仍然具有相关性,但它们最初是为三层 Java 应用程序开发的,包括部署在共享应用程序运行时中的 JSP、Servlet 和 EJB。当时的主要焦点是将应用程序逻辑与 UI 和数据库解耦并启用隔离测试。
从那时起,新的挑战和概念(例如微服务和十二要素应用程序)出现并影响了我们设计应用程序的方式。微服务的核心是将应用程序逻辑分离为单个团队拥有的独立可部署单元。十二因素应用程序方法旨在创建在动态云环境中运行和扩展的分布式、无状态应用程序。所有这些架构都引入了原则和最佳实践,这些原则和最佳实践决定了我们如何构建应用程序的内部架构以及如何管理它。
后来在应用程序架构演变时间线中,容器的主流采用和 Kubernetes 的引入彻底改变了应用程序的打包和编排方式。AWS Lambda 引入了高度可扩展的函数即服务 (FaaS) 的概念,将应用程序粒度的理念提升到了一个新的水平,并将完整的基础设施管理责任转移给了云提供商。其他技术趋势,例如服务网格和Mecha 架构,也已经出现并将应用程序堆栈的非功能方面商品化,例如网络和分布式开发人员原语,并将它们提取到边车中。受微服务的启发,数据网格架构旨在将应用程序的分析数据架构分解为更小的、独立的数据域,每个数据域都有自己的产品和团队。这些以及最近的趋势(例如应用程序优先的云服务)开始重塑应用程序的外部架构,我在本文中将其统称为“云绑定应用程序”。
外部架构演变
外部架构是应用程序与其他应用程序以及其他团队和组织通常拥有的专门的本地中间件、存储系统或云服务形式的基础设施相交的地方。应用程序连接到外部系统并卸载其部分职责的方式形成了外部架构。为了从基础设施中受益,应用程序需要与该基础设施绑定并强制执行清晰的边界以保持其敏捷性。应用程序的内部架构和实现应该能够在不改变其他架构和实现的情况下进行更改,并且还能够在不改变内部的情况下交换外部依赖项(例如云服务)。
概括地说,我们可以将应用程序与其周围环境的绑定方式分为两类。
计算绑定是用于在计算平台(例如 Kubernetes、容器服务,甚至无服务器函数(例如 AWS Lambda))上运行应用程序所需的所有绑定、配置、API 和约定。大多数情况下,这些绑定对内部架构是透明的,并由运营团队而不是开发人员配置和使用。容器抽象是当今应用程序计算绑定最广泛的“API”。
集成绑定是一个包罗万象的术语,指的是应用程序所依赖的所有其他与外部依赖项的绑定。云服务还使用这些绑定与应用程序进行交互,通常通过明确定义的 HTTP“API”或专门的消息传递和存储访问协议,例如 AWS S3、Apache Kafka、Redis API 等。集成绑定并不像与运行时绑定一样透明。开发人员需要围绕它们实现额外的逻辑,例如重试、TTL、延迟、死信队列(DLQ)等,并将它们绑定到应用程序的业务逻辑。
应用程序在云上运行并通过使用这些绑定来使用其他服务。让我们更详细地了解这些绑定背后到底是什么,什么不是。
计算绑定
对于运营团队来说,理想情况下,每个应用程序都是需要在计算平台上运行的黑匣子单元。计算绑定用于管理 Kubernetes、AWS Lambda 和其他服务等平台上的应用程序的生命周期。这些绑定以配置集合以及应用程序和运行该应用程序的平台之间的 API 交互的形式进行形式化和定义。大多数交互对应用程序来说是透明的,开发人员只需要实现少数 API,例如运行状况端点和指标 API。这就是目前CNCF对“云原生”的定义和范围的延伸,只要开发者实现了云原生应用程序,就可以绑定并运行在云计算平台上。
为了在云平台上可靠地运行,应用程序必须在多个级别上与其绑定,从规范到最佳实践。这是通过一系列行业标准规范(例如基于 Prometheus 的容器 API、指标 API)、运行状况端点或云供应商规范(例如 AWS Lambda 或 AWS ECS 规范)来实现的。此外,通过云原生最佳技术和共享知识,例如运行状况检查、部署策略和放置策略。让我们看看今天使用的常见计算绑定。
资源需求
应用程序(包括微服务和功能)需要资源,例如 CPU、内存和存储。根据所使用的平台,这些资源的定义有所不同。例如,在 Kubernetes 上,CPU 和内存是通过requests 和 limit定义的,而在 AWS Lambda 上,用户指定在运行时分配的内存量,并分配相应的 CPU。这些平台上的存储处理方式也有所不同,Kubernetes 使用临时存储和卷,而 Lambda 提供临时临时资源和基于 Amazon EFS 挂载的持久存储。
生命周期挂钩
由平台管理的应用程序通常需要了解重要的生命周期事件。例如,在 Kubernetes 上,init 容器等概念以及 PostStart 和 PreStop 等挂钩允许应用程序对这些事件做出反应。同样,Lambda 的扩展API 允许应用程序拦截 Init、Invoke 和 Shutdown 阶段。用于处理生命周期事件的其他选项包括包装器脚本或特定于语言的运行时修改选项,例如 JVM 的关闭挂钩。这些机制形成了平台和应用程序之间的契约,使其能够响应和管理自己的生命周期。
健康检查
运行状况探测是平台监控应用程序运行状况并在必要时采取纠正措施(例如重新启动应用程序)的一种方式。虽然由于请求的生命周期较短,Lambda 函数没有运行状况探测,但容器化应用程序和编排器(例如 Kubernetes、AWS EKS 和 GCP Cloud Run)确实在其定义中包含运行状况探测。这使得平台能够确保应用程序顺利运行,并在运行失败时采取行动。
部署和放置策略
了解所需资源后,计算平台就可以开始管理应用程序的生命周期。为了以不损害业务逻辑完整性的方式实现这一点,平台必须了解扩展约束。有些应用程序旨在成为单例。例如,它们需要维护处理事件的顺序,并且不能扩展到超出一个实例。其他有状态应用程序可能是仲裁驱动的,并且需要持续运行特定数量的最小实例才能正常运行。还有一些函数,例如无状态函数,可能有利于快速扩展以解决不断增加的负载峰值。一旦建立了应用程序的扩展指南,平台就会承担对应用程序实例的启动和终止的控制。
计算平台还提供各种部署策略,包括滚动、蓝绿、金丝雀和立即,以控制服务的更新顺序。除了部署顺序之外,这些平台还可以允许用户指定放置首选项。例如,Kubernetes 提供标签、污点和容忍、亲和性和反亲和性等选项,而 Lambda 允许用户在区域和边缘放置类型之间进行选择。这些首选项可确保应用程序的部署并符合合规性和性能要求。
网络流量
将低级网络流量引导至服务实例也是计算平台的责任。这是因为它负责部署排序、放置和自动缩放,这些都会影响流量定向到服务实例的方式。健康检查还可以在流量管理中发挥作用,例如GCP Cloud Run 和 Kubernetes 中的准备情况检查。通过处理这些任务,计算平台有助于确保流量高效且有效地路由到适当的服务实例。
监控和报告
任何分布式应用程序的计算平台都必须以日志、指标和跟踪的形式提供深入的应用程序洞察。如今,该领域有一些被广泛接受的事实上的标准:日志最好采用结构化格式,例如 JSON 或其他行业特定标准。计算平台通常收集日志或为专门的日志排出和分析服务提供扩展点以访问日志。这可以是 Kubernetes 上的 DaemonSet、用于监控的 Lambda 合作伙伴扩展或 Vercel 边缘函数日志 Drainer。计算平台必须支持指标和跟踪数据的收集和分析,以便提供对分布式应用程序的性能和行为的全面洞察。有多种行业标准格式和工具可用于处理这些数据,用于跟踪的OpenTelemetry (OTEL)。计算平台可以提供用于收集和分析这些数据的内置工具,或者为专门服务提供扩展点来访问数据。无论代码的粒度(微服务或功能)或位置(边缘与否),计算平台都应允许捕获日志、指标和跟踪数据并将其导出到其他最佳云服务,例如Honeycomb、DataDog、Grafana等等。
计算结合趋势
计算绑定与语言和应用程序运行时无关,主要由运营团队用于在运行时管理应用程序,而不是由开发人员实现它们。
虽然应用程序的大小和复杂性因整体应用程序和功能而异,但它们通常打包在具有运行状况检查端点、实现的生命周期挂钩和公开的指标的容器中。了解这些计算绑定将帮助您有效地使用任何基于容器的计算平台,无论是本地 Kubernetes 集群、托管容器服务(例如 AWS ECS、Google Cloud Run、Azure Container Apps)还是基于函数的运行时(例如作为 AWS Lambda、GCP 函数或边缘运行时,例如 Vercel边缘函数、CloudFlare工作线程或 Netlify 边缘函数使用开放且事实上的标准 API 不仅可以帮助您创建可移植的应用程序,还可以通过使用可跨云供应商和服务提供商移植的操作实践和工具来限制供应商锁定。
集成绑定
另一方面,集成绑定是供开发人员而不是运营团队使用的。它们以常见的分布式系统的实现领域为中心,例如服务调用、事件驱动的交互、任务调度和有状态工作流编排。它们通过基于云的中间件类服务(我在本文中将其统称为集成云)帮助将应用程序与专用存储系统和外部系统连接起来。就像容器提供计算抽象一样,集成云服务将与语言无关的集成抽象作为服务提供。这些原语独立于用例、应用程序实现、运行时和计算环境。例如,重试模式、DLQ模式、Saga 模式、服务发现和断路器模式都可以作为来自集成云的服务来使用。
如今,所有主要模式都作为独立功能公开的纯粹集成云尚不存在。早期的云服务提供了其中一些集成原语作为存储系统(例如 Kafka、Redis 等)的功能,但这些功能很少单独使用或与其他功能结合使用。这里值得注意的例外是 AWS EventBridge 和 Azure Event Grid 等服务,您可以将它们与来自同一供应商的多个云服务一起使用,但不能直接与其他供应商一起使用。这是一个快速发展的领域,有一些很好的例子,也有一些尚未填补的空白,但我相信它们将来会被填补。要工作,应用程序必须与集成云服务绑定并减轻其中一些开发人员的责任。以下是集成云服务的主要类型和绑定方面。
整合需求
以同样的方式,应用程序可以请求资源并向计算平台表达部署和放置首选项,应用程序也可以请求并激活特定的集成绑定。这些绑定可以通过以声明方式传递到平台的配置来激活,也可以通过编程交互在运行时激活。例如,应用程序可以使用声明式和编程式订阅来订阅发布/订阅主题。AWS Lambda 函数可以通过配置以声明方式订阅事件源,或者通过客户端库或开发工具包以编程方式订阅事件源,方法是请求集成平台注册或取消注册特定绑定。应用程序可以订阅 cron 作业触发器、激活外部系统的连接器、进行配置更改等,所有这些都在集成云上运行。
工作流程编排
持久性服务编排逻辑是一种非常常见的必需品,也是将其作为服务外部化和使用的主要候选者。因此,工作流编排是当今最著名的集成绑定类型之一。该服务的常见用途包括实现服务和业务流程编排的 Saga 模式、使用 AWS Step Functions 进行功能编排、Google Stateful Functions、Azure Durable Functions、使用 Google Workflow 进行任务分配以及许多其他服务。使用此类绑定时,部分应用程序编排状态和逻辑将被卸载到另一个服务中。虽然应用程序服务具有内部状态和管理该状态的逻辑,但其他部分位于外部,可能位于其他一些云服务中。这代表了当今应用程序作为单个独立单元的设计和操作方式的转变。未来的应用不仅有数据在外部,集成也在外部。随着集成云的日益普及,更多的集成数据和逻辑将开始存在于外部。
时间触发因素
时间绑定代表编排绑定的时间限制专业化。它只有一个目标,即根据给定的策略在特定时间触发各种服务。此类别的示例包括 AWS EventBridge Scheduler、Google Cloud Scheduler、Upstash Qstack 服务等。
事件驱动和消息传递服务
这些绑定充当事件存储来卸载请求和解耦应用程序,但它们越来越不限于存储并扩展到提供消息处理模式。它们在事件存储之上提供开发人员原语,例如死信队列、重试、延迟传递和消息处理模式,例如过滤、聚合、重新排序、基于内容的路由、窃听等。这种绑定的示例是例如 Confluence Cloud kSQL、AWS EventBridge、Decodable 数据管道等。
外部连接器
这些绑定有助于连接到外部系统。它们还执行数据规范化、错误处理、协议转换和数据转换。示例包括 Knative 源导入器、AWS EventBridge 连接器、Confluence Cloud连接器、Decodable Kafka 连接器、AWS Lambda 源和目标。
健康检查
运行状况检查在计算绑定中至关重要,运行状况检查失败通常会导致应用程序重新启动。集成绑定也需要运行状况检查,但目的不同:集成运行状况检查不会影响应用程序运行时,但它告诉集成云应用程序是否能够处理集成驱动的交互。失败的集成运行状况检查可能会停止集成绑定,直到应用程序在集成绑定恢复时恢复正常。通常,您可以使用相同的应用程序端点进行计算和集成绑定检查。Dapr 的应用程序运行状况检查就是一个很好的例子,它可以暂时阻止消费者和连接器将数据推送到不健康的应用程序中。
其他绑定
还有更多的绑定即将出现,并且属于集成绑定类别。例如,向应用程序提供内省数据(例如 Kubernetes Downward API 和 Lambda环境变量),为应用程序内省和元数据注入提供了简单的机制。配置和秘密绑定,其中秘密不仅在启动时注入到应用程序中,而且任何配置更新都会通过 sidecar 推送到应用程序,例如 Hashicorp Vault Sidecar Injector或 Dapr 的配置API、 Kubernetes 的服务绑定规范。以及不太常见的模式,例如分布式锁,这也是一种集成绑定,可以提供对共享资源的互斥访问。
整合绑定趋势
容器正在成为最流行和广泛使用的用于打包和运行应用程序的便携式格式,无论它们是长期运行的微服务还是短期功能。另一方面,集成绑定可以分为不同的问题领域,例如事件驱动的交互、状态编排和状态访问,并且在底层存储和使用模式方面有所不同。例如,Apache Kafka 是事实上的标准对于事件日志,AWS S3 API 用于文档访问,Redis 用于键值缓存,PostgreSQL 用于关系数据访问等。使它们成为标准的原因是围绕它们构建的不断增长的库、工具和服务生态系统,从而提供了保证关于相当程度的成熟度、稳定性和未来的向后兼容性。但这些 API 仅限于存储访问方面,并且通常需要开发人员在应用程序代码中解决分布式系统挑战。与堆栈上层的软件商品化方向保持一致,集成绑定正在作为服务提供。越来越多的无服务器云服务提供除了数据访问之外应用程序代码还可以绑定的附加集成功能。
在此模型中,云应用程序通常在无服务器计算基础设施上运行,遵循云原生原语。它与其他无服务器云服务绑定以进行服务编排、事件处理或同步交互,如下所示。
CNCF 的Dapr是一个将大多数集成绑定和开发人员关注点整合到开源 API 中的项目。它提供同步服务调用、有状态服务编排、异步事件驱动交互以及特定于技术的连接器作为 API。与容器和 Kubernetes 作为计算抽象的方式类似,Dapr 充当外部服务的抽象。Dapr 还提供独立于底层云服务的集成功能,并且通常必须在应用程序层中实现,例如弹性策略、死信队列、延迟交付、跟踪、细粒度授权等。Dapr 被设计为多语言并在应用程序外部运行,从而可以轻松交换外部依赖项,而无需更改应用程序的内部架构,如六边形架构中所述。虽然 Dapr 主要由实施应用程序的开发人员使用,但一旦引入,Dapr 就会增强分布式应用程序的可靠性和可见性,为运营和架构师团队提供整体优势。要了解有关此主题的更多信息,请亲自或以虚拟方式参加今年晚些时候的 QConLondon,届时我将谈论 “应用程序优先的云服务如何改变游戏规则”。
后云原生应用
云绑定应用程序代表了从解决仅计算问题到管理应用程序层需求的云原生进程。云服务在应用程序堆栈中从基础设施向应用程序优先服务的扩展加速了这一趋势。我们可以在以开发人员为中心的云服务爆炸式增长中观察到这种转变,这些服务用于有状态编排、事件驱动的应用程序基础设施、同步交互、基于云的开发和测试环境以及无服务器运行时。向应用程序优先的云服务的转变正在催生一种新的应用程序架构,其中越来越多的应用程序逻辑在云服务中运行。应用程序与第三方云服务的这种混合允许开发人员减轻更多责任,但是,它可能会限制不断变化的业务需求所需的灵活性和敏捷性。为了保持应用程序内部和外部架构的独立性,应用程序和云服务需要在开发时以清晰的边界进行解耦,并在运行时使用定义良好的开放 API 和格式将其深度绑定在一起。就像容器和 Kubernetes 为计算提供开放 API 一样,我们也需要开放 API 来进行应用程序集成抽象。这将实现操作实践和工具以及开发模式、功能和实践的可移植性和重用。应用程序和云服务需要在开发时以清晰的边界进行解耦,并在运行时使用定义良好的开放 API 和格式将其深度绑定在一起。就像容器和 Kubernetes 为计算提供开放 API 一样,我们也需要开放 API 来进行应用程序集成抽象。这将实现操作实践和工具以及开发模式、功能和实践的可移植性和重用。应用程序和云服务需要在开发时以清晰的边界进行解耦,并在运行时使用定义良好的开放 API 和格式将其深度绑定在一起。就像容器和 Kubernetes 为计算提供开放 API 一样,我们也需要开放 API 来进行应用程序集成抽象。这将实现操作实践和工具以及开发模式、功能和实践的可移植性和重用。