随着对应用为先的云服务采用愈发广泛,应用与云服务的融合程度也到了前所未有的深度。应用程序和云运行时的边界从虚拟机转移到了容器和函数中。集成边界从仅使用数据库和消息代理访问,转换成应用程序的机械部分在云中的混合运行。在这些因素影响下的架构中,应用程序与“云绑定”,应用逻辑与管理责任转移至云服务中,允许开发者专注于业务逻辑。
本文中将分析,通过使用具备灵活性和可移植性的公开 API 和标准将应用程序与云服务绑定,所带来的软件全栈平价化。
应用程序的内部架构通常归属于单一团队掌控。内部的边界则由所选编程语言、运行时、工具,以及包、模块、接口、类、函数等抽象协助开发者进行控制。领域驱动设计(DDD)协助开发者构建领域模型,用抽象概念封装服务业务逻辑,缓解业务实际与代码之间的鸿沟。
Hexagonal、Onion,以及 Clean 架构可以与 DDD 相互补,以不同边界与外部基础设施依赖划分应用程序代码。虽然这些方式最初都是颇具创新意识且时至今日仍然适用,但这些架构的设计初衷只考虑了包含 JSP、Servlet、部署于共享应用运行时的 EJB 这三层的 Java 应用程序,当年设计的重点还是应用逻辑与 UI、数据库解耦,实现独立测试方面。
图一:内部应用架构
后来,微服务、十二因素应用程序等新挑战、新概念层出不穷,应用程序的设计方式也受此影响。微服务的核心是将应用逻辑切分为归属单一团队的独立可部署单元,十二因素应用程序方法意在构建可在动态云环境中运行、扩展的分布式无状态应用。这些架构所引入的原则和最佳实践改写了我们构建应用程序内部架构并管理的方式。
图二:应用程序架构演变时间线
在应用程序架构演变时间线的后半段,容器的采用和 Kubernetes 的引入成为主流,彻底改变了应用打包、协调的方式。AWS Lambda 引入了高度可扩展的功能即服务(FaaS)的概念,将应用程序细粒度概念再度拔高,将完整的基础设施管理责任卸至云供应商。其他如服务网格、Mecha 架构等技术潮流的出现,网络和分布式开发者基元等非功能性应用栈平价化并剥离至 sidecar 中。受微服务启发,数据网格架构设计意图将应用程序的数据分析架构拆分至更小的独立数据域中,让每个域都有自己的项目和团队。再加上近期的应用为先云服务等等,这一系列技术潮流开始重塑应用程序的外部架构,本文中我将这些统一称为“云绑定应用”。
外部架构是应用与其他团队和组织以专门的内部中间件、存储系统,或云服务等形式拥有的其他应用和基础设施交互的部分。应用与外部系统相连并卸除部分责任的方式构建了外部架构。为充分利用基础设施,应用需要与该基础设施绑定,确立明确分界线以保留其敏捷性。应用的内部架构和实现应独立进行修改,并在不变动内部的情况下与云服务等外界依赖关系互换。
图三:外部引用架构
大体上来说,我们可以将应用程序与其周围环境相绑定的方式分为两类。
计算绑定,包含所有必需的绑定、配置、API,以及程序在 Kubernetes、容器服务,乃至无服务功能(如 AWS Lambda)等计算平台中运行所用的协议。多数情况下,这些绑定对内部架构是透明的,配置更多是为运维团队而非开发所用。容器抽象目前是最广为人知的应用计算绑定“API”。
集成绑定,覆盖范围非常广,从除计算绑定外的其他绑定,到应用的外部依赖关系。云服务同样利用这类绑定与应用交互,常见形式是通过定义完善的 HTTP “API”或专门的消息和存储访问协议,如 AWS S3、阿帕奇卡夫卡、Redis API 等等。集成绑定没有运行时绑定的透明度,开发者也需要实现额外的相关逻辑,如重试、TTL、延时、死信队列(DLQ)等等,并将其与应用的业务逻辑相绑定。
云上运行的应用会通过这些绑定消费其他服务,下面让我们这些绑定背后究竟都是些什么。
从理论上来说,所有应用程序对运维团队而言都是需要在计算平台上操作的黑盒单元。计算绑定是用于管理应用在 Kubernetes、AWS Lambda 及其他服务平台上的生命周期。这类绑定均是规范化,以配置集合加上应用与其所运行的平台间交互的 API 的形式进行定义。多数交互都是对应用透明的,只有少数 API 需要开发者自行实施,如健康端点和指标 API。这是目前 CNCF 对此的定义,也是“云原生”概念所囊括的范围,开发者只需实现云原生应用,就能将其绑定在云计算平台上运行。
图四:应用与平台的计算绑定
为让云平台上的运行更为可靠,应用必须在规范到最佳实践等多个层面与平台绑定。这一过程是通过一系列业界标准规范的实现的,如容器 API 和指标 API、基于普罗米修斯(Prometheus)的健康端点、AWS Lambda 或 AWS ECS 等云供应商规范。此外,还有云原生的最强技术和共享知识,如健康检查、部署策略和安置政策。让我们再看看目前最常见的计算绑定方式。
无论是微服务还是功能,应用总会对 CPU、内存和存储等资源有所需求。根据所用的平台不同,资源的定义也会不同。举例来说,Kubernetes 上 CPU 和内存是通过请求和限制定义的,而 AWS Lambda 则是由用户 指定 运行时需要分配的内存大小和对应 CPU。不同平台对存储的处理方式也是各异,Kubernetes 使用短期存储和卷,而 Lambda 则提供短期资源抓取和基于亚马逊 EFS 挂载的持久存储。
由平台管理的应用程序通常需要对重要的生命周期事件有感知。举例来说,Kubernetes 中的概念(如容器初始化)和钩子(如容器启动后钩子 PostStart 和容器结束前钩子 PreStop)就可以让应用对这些事件做出反应。同理,Lambda 的扩展 API 也能让应用处理初始、调用、关闭阶段。其他处理生命周期事件的方式包括脚本封装或针对特定语言的运行时修改选项(比如 JVM 的关闭钩子)。这些机制为平台与应用程序之间形成协议,使其能够响应并管理自身的生命周期。
健康探针是平台用于监测应用程序健康状况并按需采取对应行动(如重启应用程序)的方式。虽然出于请求短暂的生命周期,Lambda 函数没有健康探针,但容器化应用程序和 Kubernetes、AWS EKS,GCP 云运行等协调器却可以在其定义中涵盖健康探针,让平台上的应用运行更为顺利,出现问题时也能及时采取行动。
在获得所需资源后,计算平台可以开始管理应用程序的生命周期了。若想在不破坏业务逻辑完整性的前提下管理生命周期,平台必须要能意识到扩展的限制所在。部分程序只会是单体程序,比如,平台需要维护事件处理的顺序,且不能将其扩展超过一个实例。其他有状态应用可能受法定人数(Quorum)驱动且需要维持指定数量的最小实例,函数才能正常运行。再有,无状态函数可能会倾向于快速扩展以解决负载中不断增长的额峰值。一旦确定了应用程序的扩展方式,平台便能控制应用程序实例的启动和终止。
计算平台同样提供包括滚动、蓝绿、金丝雀、一次性等多种部署策略,用于控制服务的更新顺序。除了部署顺序外,平台可能还支持用户指定置放策略。比如 Kubernetes 提供标签、污点(Taint)、容忍度、亲和性、反亲和性等选项,而 Lambda 则允许用户在区域置放和边缘置放类型中进行选择。这些平台的偏好选择确保了应用程序的部署,且与预期合规性和性能要求相符。
将低层级网络流量导向服务实例同样是计算平台的责任之一。平台所负责处理的部署顺序、置放,以及自动扩缩容都会影响流量向服务实例的引导。健康检查在流量管理中也发挥着作用,如 GCP 云运行和 Kubernetes 中的就绪检查。通过处理这些任务,计算平台能够协助确保流量是高效且有效地路由到适当的服务实例。
任何用于分布式应用程序的计算平台都必然以日志、指标、跟踪的形式提供深入的应用洞察。如今,在当前领域中也有了约定俗成的标准:日志最好为结构化格式,如 JSON 或其他业界特定标准。计算平台通常会收集日志或提供特殊日志清理和分析服务的访问扩展点。比如 Kubernetes 上的 DaemonSet、Lambda 的监控合作伙伴扩展、Vercel 的边缘函数日志 Drainer。计算平台必须要能支持指标、跟踪数据的收集和分析,才能针对分布式应用程序的性能和行为提供全面的洞察力。业界标准中有许多处理这类数据的格式和工具,如普罗米修斯的指标、OpenTelemetry(OTEL)的跟踪。计算平台或提供内置数据收集和分析工具,或提供扩展点允许专门服务访问这类数据。计算平台应支持任何细粒度(微服务或功能)或位置(边缘与否)的代码,对日志、指标、跟踪数据进行捕捉,并导出至其他优秀的云服务中,如 Honeycomb、DataDog、Grafana 等等。
计算绑定是对编程语言和应用运行时不可知的,主要为运维团队管理运行时的应用,而非为开发人员实施所用。
虽然应用的大小和复杂度随单体应用或函数而不同,但基本都会封装在容器内,具有健康检查端点,实现了生命周期钩子,并有指标暴露。理解计算绑定有助于高效使用任何基于容器的计算平台,无论是企业内部的 Kubernetes 集群,还是 AWS ECS、谷歌云运行、Azure 容器应用等管理型容器服务,基于函数的运行时 AWS Lambda、GCP 函数等等,以及基于边缘运行时的 Vercel 边缘函数、CloudFlare 工作者、Netlify 边缘函数等等。这些开放和事实标准的 API 不仅能协助创建可移植应用程序,还能通过跨云供应商和服务提供方的操作方法和工具,避免被供应商锁定。
另一方面,集成绑定则是供开发人员而非运维团队使用。集成绑定以常见分布式系统的实现区域为中心,如服务调用、事件驱动交互、任务调度,以及有状态工作流协调。协助应用程序通过基于云的中间件形式服务,与专用存储系统及外部系统相连,在本文中我将这些服务统一称作“集成云”。与容器所提供的计算抽象类似,集成云提供了编程语言不可知集成抽象即服务。其基元与用例、应用实现、运行时和计算环境相独立。重试模式、DLQ 模式、Saga 模式、服务发现、断路器模式等等,都可以通过服务形式在集成云中被消费。
图五:应用程序和平台的集成绑定
目前尚不存在将所有主要模式都以独立功能形式暴露的纯集成云。早期的云服务可提供部分集成基元作为卡夫卡、Redis 等存储系统的功能,但却很少能有独立使用或与其他功能相结合的。少有的例外情况是 AWS 事件总线(EventBridge)和 Azure 事件网格等服务,允许与同一厂商的多个云服务同时使用,但不能直接使用其他供应商。这个领域的发展如此快速,虽然还有一些例子或空缺,但我相信在未来,这些空白一定能得到填补。应用程序必须与集成云服务相绑定才能运作,并将部分开发者肩头的责任卸载。以下我将介绍集成云服务的主要类型和绑定方面。
与应用对资源的要求和对计算平台的部署和置放需求同理,应用也会对特定集成绑定有需求。这些绑定可通过声明性配置传入平台,也可在运行时通过代码交互激活。举例而言,应用程序可使用声明式和程序式 订阅 pub/sub 主题。AWS Lambda 函数可以通过配置声明式地订阅数据源,也可以通过客户端库或 SDK,以程序形式向集成平台发送注册或取消注册特定绑定请求。应用程序可订阅 cron 定时任务触发器,激活连接外部系统的连接器,修改配置等等行为,都是在集成云上进行的。
逻辑协调对持久性服务而言不仅是一种极为普遍的需求,还是将其外部化并作为服务消费的主要候选。因此,工作流协调是如今最为知名的集成绑定类型之一。这种服务的常见用途包括:用于服务和业务流程协调的 Saga 模式实现、AWS 编排函数(Step Function)、谷歌有状态函数、Azure 持久函数、谷歌工作流的任务分配等等。在使用这类绑定时,应用程序中的部分编排状态和逻辑被卸载至其他服务中。应用服务内部虽然还有状态和逻辑对状态进行管理,但其他的都放在了外部,比如其他云服务上。这代表了现今应用程序以独立单元的形式设计并操作方式的转变。未来的应用程序可能不仅仅只有数据在外,集成也会被放在外部。随着对集成云采用的不断出现,更多的集成数据和逻辑将会存在于外部。
临时绑定是协调绑定中的一类基于时间的分类,具有单一目标,即根据给定策略在特定时间触发不同服务。类似的例子有:事件总线调度器、谷歌云调度器、Upstash Qstack 服务等等。
这类绑定以事件存储形式卸载请求并解耦应用,但其应用如今也越发地不再局限于存储,而是向提供消息处理模式的方向扩展。除了事件存储,这类绑定也为开发者提供了死信队列、重试、延迟交付等各类基元,还有过滤、聚合、重新排序、基于内容的路由、窃听等等消息处理模式。其示例有:Confluent Cloud kSQL、AWS 事件总线、Decodable 数据管线等等。
这类绑定可协助应用连接至外部系统的同时,也可执行数据规范化、错误处理、协议转换、数据转换。示例有:Knative 源导入器、AWS 事件总线连接器、Confluent 云连接器、Decodable 卡夫卡连接器、AWS Lambda 源和目的地。
健康检查是计算绑定中不可或缺的一环,健康检查失败通常会致使应用重启。集成绑定同样需要健康检查,但集成绑定中健康检查不会影响应用的运行时,只会告知集成云当前应用是否有能力处理与集成驱动的交互。失败的集成健康检查会中止集成绑定的过程,直至应用恢复健康才会恢复绑定。经常会出现计算和集成绑定使用同一应用端点进行检查,比如 Dapr 的应用健康检查可以临时阻止消费者和连接器将数据推入不健康的应用。
许多其他的绑定类型也可被归为集成绑定。比如自省数据传入应用程序,指类似 Kubernetes 的向下 API 或 Lambda 的环境变量等,通过简单机制将自省数据和元数据注入应用;配置和秘密绑定,指不仅在应用启动时将秘密注入,还可在任何配置更新时都推送至应用之中,如 Hashicorp Vault Sidecar 注入器、Dapr 的配置 API、Kubernetes 的服务绑定规范。此外,不太常见的集成绑定模式还有分布式锁,提供对共享资源的互斥访问。
无论是长期运行的微服务还是短期的功能,容器都在逐渐成为应用程序打包和运行使用中最多最广的可移植格式。不过,集成绑定也可以被划分为不同的问题领域:事件驱动交互、有状态协调和状态访问,在底层存储和使用模式方面各有不同。举例来说,阿帕奇卡夫卡是事件日志的 事实标准,AWS S3 API 用于文档访问、Redis 用于键值缓存、PostgreSQL 用于关系型数据访问等等。这些成为标准化的原因在于不断成长的生态系统,其中包含库、工具,以及围绕其所构建的种种服务,保证了相当程度的成熟度、稳定性、未来的向后兼容性。但是,这些 API 本身只局限于存储访问方面,常常需要开发者自行应对应用程序内的分布式系统挑战。随着软件的平价化,集成绑定也逐渐以服务形式可用。越来越多的无服务云服务提供了额外的集成能力,让应用程序可以绑定数据访问之外的东西。
在这种模式下,云绑定应用通常在无服务计算基础设施中运行,和云原生基元一样。它与其他无服务的云服务相绑定,用于服务协调、事件处理或同步互动,如下所示:
图六:云绑定应用程序的生态系统
一项将多数集成绑定和开发者关注的问题统一至开源 API 的项目是 CNCF 的 Dapr。该项目提供了同步的服务调用、有状态的服务协调、异步的事件驱动的交互,以及以 API 为特定技术的连接器。与容器和 Kubernetes 作为计算抽象类似,Dapr 也是外部服务的抽象。此外,Dapr 也提供与底层云服务独立的集成功能,并经常需要在应用层实现,其中就有弹性策略、死信队列、延迟交付、跟踪、细粒度授权等等其他。Dapr 的设计是多边形且可在应用之外运行,在不改变应用内部架构的情况下,让外部依赖关系交换可以更轻松,正如其在六边形架构中描述的一样。虽然 Dapr 主要为开发者实施应用程序而用,不过在引入后 Dapr 也提高了分布式应用程序的可靠性和可观测性,为运维和架构团队提供了整体效益。关于这点更多信息,我将于今年后半年的 QConLondon 大会上讲述“应用为先的云服务如何改变游戏”,欢迎通过线上或线下形式参与。
云绑定的的出现代表了云原生从单纯解决计算问题到管理应用层需求的进步。随着云服务对应用栈的不断扩展,从基础设施向应用为先的转换,让这一趋势也在加速发展。这点从以开发者为中心的有状态协调、事件驱动应用基础设施、同步交互、基于云的开发和部署环境、无服务运行时等爆炸性云服务发展中也能一窥一二。这种向应用为先的云服务正在催生一种新的应用程序架构,其中越来越多的应用逻辑在云服务中执行。这种应用程序于第三方云服务的融合也让开发者们能将更多的责任卸载,但随之而来的还可能有对灵活性和敏捷性的限制,这些都会是不断变化的需求所必备的能力。为保持应用程序内部和外部架构的独立性,应用与云服务应在开发时以整齐的边界进行解耦,并在运行时使用定义明确的开放 API 和格式深度绑定。与容器和 Kubernetes 为计算提供了开放的 API 一样,我们需要为应用集成抽象提供开放的 API。这会使操作实践和工具以及开发模式、能力和实践具有可移植性和可复用性。
原文链接:
What Are Cloud-Bound Applications?(https://www.infoq.com/articles/cloud-bound-applications/)
相关阅读:
微软推出 Azure Developer CLI 公开预览版,帮助开发者加速云应用开发 (https://www.infoq.cn/article/9z8EK3keFuAmJ0NWm9hv )
全新 AWS Auto Scaling – 适用于云应用程序的统一扩展 (https://www.infoq.cn/article/3xRjcVaKD8zP1D43owc5 )
声明:本文为 InfoQ 翻译,未经许可禁止转载。
点击底部阅读原文访问 InfoQ 官网,获取更多精彩内容!
连代码都没写就敢要融资:被ChatGPT带火的向量数据库,带来了一大波造富神话
《2023 大语言模型综合能力测评报告》出炉:以文心一言为代表的国内产品即将冲出重围
文章引用微信公众号"InfoQ",如有侵权,请联系管理员删除!