点击查看目录
本文译自:https://www.infoq.com/presentations/dapr-cloud-services/,是 Ibryam 今年三月在 QCon London 的分享。
摘要:软件堆栈的商品化是如何改变应用为先的云服务的游戏规则。通过使用开源项目和 API 将应用程序与集成逻辑绑定在一起,可以实现更高级别的抽象。Dapr 是一组作为 API 公开的分布式基元,可以作为 Sidecar 部署。应用程序云服务将计算和集成能力作为 SaaS 提供,开发人员可以将核心应用程序逻辑绑定到云服务上。这种应用程序优先云服务的出现使得应用程序可以更加专业化,并且可以在不同的云提供商之间灵活迁移。
Ibryam: 我叫 Bilgin Ibryam。我在这里要告诉大家我对云服务的演变以及这将如何影响我们正在构建的分布式应用的看法。这将是一个快节奏的演讲,我们没有时间深入研究每个技术和模式,而是快速概述架构的演变,并试图分析这对云原生应用的未来可能意味着什么。我是 Diagrid 的产品经理,我们正在为开发人员构建 API。在此之前,我是红帽公司的顾问、架构师和产品经理,在那里我使用了一些项目,比如集成框架 Apache Camel,并对其进行了贡献并撰写了一本书。同样,我也使用过 Kubernetes 作为开发人员,并撰写了一本关于 Kubernetes 的书,您可以从提供的 URL 中免费获得赞助。我们将看看应用是如何从单体应用变成微服务、函数以及即将到来的其他形式的。同时,我们还将看看基础架构是如何演变为云服务的形式,并且它如何塑造应用架构。
早期云时代
让我们从回顾云之前和云早期时代开始。在我看过的时间线上,这是微服务运动之前的时代。那是云成为主流之前的时代。这主要是指本地和早期云时代,如早期的 EC2 实例。如果我们看一下当时代表性的应用架构,比如构建在 ESB 平台之上的应用,我们会发现开发人员不得不自己实现或从 ESB 中使用所有东西。这包括与应用程序打包、部署在 SOAR 平台上、甚至在 VM 上放置应用程序等相关的内容。新更新的部署和发布流程。处理基于 SOAP 和 Web 服务甚至 RPC 的同步交互的应用程序的配置和扩展方面。所有这些都是在应用程序内部控制的。当时,我们还没有像断路器模式、mTLS 这样的东西。开发人员负责服务发现、重试、超时、身份验证、授权。在异步交互类别下,我包括通过消息传递的任何形式的交互,这涉及到消息转换、从一种协议连接到另一种协议、连接到各种系统、死信队列。最后,在有状态的模式和工作流程下,通常需要持久状态,包括业务工作流编排或更简单的 Saga 模式实现。幂等消费者、共享分布式日志、定时器、Cron 作业,所有这些模式都需要在应用层内部由开发人员实现。在这个时代,所有这些责任都由应用程序处理。
那基础设施呢?作为当时的开发人员,我会将基础设施视为一个薄薄的层次,并且对它的期望并不是很高。基础设施会以虚拟机的形式为开发人员提供计算能力,以关系数据库和消息代理的形式提供网络和存储。即使这些也有时会成为 ESB 平台提供的应用层的一部分。那么界面和这两个层次和团队之间的边界是怎样的呢?它以操作系统抽象的形式存在。运维团队会为您提供特定容量的虚拟机,可能还会安装 Java 或某个应用服务器,然后就完成了。会有一张列有静态 IP 和端口号的清单,用来引用其他服务和数据库。这基本上就是在云计算和早期云计算时代的情况了。那时还没有被广泛接受的可用于不同语言和环境的应用格式和 API。从应用开发人员的角度来看,这是一个智能单体和愚蠢静态基础设施的时代。
内部架构
虽然这是 2010 年之前的最新技术,但随着接下来几年里发生的一些重大软件开发趋势,对应用开发产生了复兴和新的兴趣。这些趋势至今仍然具有影响力。让我们看看它们。软件架构有不同的方面和不同的可视化方式。在流行的方式中,有一种被称为“4+1 架构模型”,它包括逻辑视图、物理视图、过程视图和开发视图。这种技术主要依赖于使用 UML 和图表来可视化这些不同的视图。后来,还出现了另一种流行的软件架构可视化方式,它被称为 C4,由 Simon Brown 创建,它采用更简化的方法,从系统上下文、运行应用程序的容器、构成应用程序的组件、以及类和包的代码级别等层次来看待软件。
对于这次演讲,我想采用更简单的方式,只谈论应用程序架构的两个层次,我将其称为内部架构和外部架构。内部架构是由开发人员完全控制的一切,包括类、函数、包、应用程序内部的不同层次,甚至是外部系统的抽象。我们可以说内部架构就是放入容器镜像中的所有内容,并且从运维和平台的角度来看,它被视为一个黑盒子。外部架构是应用程序与之交互的一切的集合。这是构成整个系统的其他服务、数据库和云服务。作为运维人员,您必须了解这些外部交互,并确保连接可靠、安全、可观察。与 C4 模型相比,内部架构基本上是级别 1 和 2,即系统上下文和容器,而外部架构基本上是级别 3 和 4,即组件和代码。
在放置此免责声明后,让我们来看看一些在单体应用程序中发生的值得记忆的影响以及它们如何改变应用程序的内部架构。第一个是域驱动设计,这个术语是由埃里克·埃文斯在他的书《领域驱动设计》中创造的。域驱动设计是一组原则和模式,帮助开发人员封装复杂的业务逻辑,并弥合业务现实与代码之间的差距。虽然这本书是在微服务出现近十年之前写的,但它为微服务奠定了基础,并成为后来微服务的基石,通过帮助开发人员将单体应用程序拆分为代表不同业务领域的较小的松散耦合模块,这些模块由有界上下文表示。我认为下一个重大转变是六边形架构,由阿利斯泰·科克伯恩提出,旨在避免面向对象软件设计中的结构陷阱。这些陷阱包括层之间的不必要的依赖关系以及用户界面与业务逻辑的相互污染。基本上,这种方法通过解耦组件并提供一种标准化的与外部依赖进行交互的方法,改进了三层应用程序的灵活性和可维护性。还有一些相关的思想,如洋葱架构、干净架构,由阿姨鲍勃提出。这些设计模式基本上强调应用程序层内的关注点分离,并将应用程序代码库组织成具有特定职责的不同层。所有这些架构风格都有助于将应用程序的业务逻辑与基础架构分离,并允许开发人员对基础架构进行更改而不影响业务逻辑,反之亦然。
然后出现了微服务和 12 因素应用程序。基于域驱动设计的思想,如有界上下文、聚合和六边形架构,构建了微服务。微服务基本上允许每个服务独立发布和扩展,以满足不断变化的业务需求。12 因素应用程序方法代表了开发微服务基础应用程序和现代可扩展云应用程序的一组最佳实践。这些想法都建立在前一个想法的基础上,但可能也稍微改变了它。由于这些应用程序开发趋势的结果,在过去的十年中,应用程序的内部架构发生了显着变化。我们之前看到的单体架构变得禁忌,几乎成为反模式,并开始向微服务和函数过渡。
计算优先的云时代
当应用程序开发人员忙于从单体架构过渡到微服务时,让我们看看此时基础架构层发生了什么。由于应用程序内部架构和云迁移的所有变化,我们开始看到为微服务提供独立中间件的出现。无论是用于集成中间件(如 Apache Camel、Spring Integration)还是用于工作流编排的项目(如 Conductor、Cadence、Camunda),这些专门的框架开始提供一些微服务的需求,无论是部署在本地还是在云端,它们仍然留在开发人员的领域内。这种转变代表了将集成责任从单体应用程序中分离出来,但由开发人员管理的独立组件。更有趣的是在计算和低级网络层发生了什么。Docker 于 2013 年宣布发布,这在计算抽象层面解锁了巨大的创新。这基本上为运维团队提供了云和计算,例如 Kubernetes 和 lambda。所有这些都意味着运行时责任开始转移到底层平台,并成为运维团队和云服务的责任。对于开发人员来说,它们不再是关注点。
基于容器的打包意味着可以统一编排使用任何语言编写的应用程序,即执行诸如放置、部署、扩展、配置管理之类的操作,从开发人员的责任转移到运维团队和云服务。它们不再需要开发人员关注。网络也变得更加动态和应用程序化。一些可靠性、服务发现、故障转移、可观察性、路由等职责转移到了平台级别,成为运维团队的责任。我认为这种迅速转变的主要原因之一是我们首次拥有了多语言应用程序特定格式,例如 Docker 和 Kubernetes。这些基本上是在图表上表示的红色框。这些技术弥合了开发人员和运维团队之间的鸿沟,并使用两个团队都使用的共同语言、共同模式、抽象和工具,实现了 DevOps 和 GitOps 等实践。
我想深入探讨一下在这种情况下应用程序与计算之间的合同。无论您是在 Kubernetes 上将微服务作为容器运行,还是在纯容器服务上运行,或者将其作为无服务器函数运行,应用程序与运行时平台之间都有一定的合同。为了与我们将在后面看到的其他类型的平台区分开,并强调这通常是托管服务或 SaaS 提供的,我将其称为计算云。应用程序与计算云之间的合同是通过 API 交互、配置甚至实践(例如滚动部署)形式存在的。所有这些,我们将其称为计算绑定。让我们看看如何将应用程序与计算层绑定,以及这些 API 是什么。
假设我们有一个在容器中有一些应用逻辑的微服务。该应用程序有自己的数据库、内部状态,并且可能与其他系统和服务进行通信。这些是外部依赖项。然后,当我们在计算平台上运行这样的应用程序时,最初这两者之间存在一些合同。通过配置界面,无论是 YAML 文件还是其他格式,我们将某些资源需求传递给计算平台,即 Kubernetes 中的 CPU 和内存请求和限制。在 AWS Lambda 的情况下,这是内存请求。我们将使用这两个运行时平台进行比较。我们使用其他配置策略来定义应用程序应该在哪里运行。对于 Lambda 来说,选择区域或在边缘部署 Lambda 的选项。在 Kubernetes 中,有更丰富的配置选项,例如污点、容忍度、亲和性、反亲和性等。还可以有其他元数据,例如标签、环境变量,我们将其传递给计算平台,以便它知道如何配置我们的应用程序。甚至有一定的合同,如何将此配置从平台传递到应用程序,通常是通过环境变量,但也可以通过特定位置的已挂载文件以特定格式传递。还存在生命周期挂钩。平台现在知道如何启动和停止我们的应用程序,并在启动期间触发某些事件,或在关闭之前或其他重要的生命周期阶段触发事件,应用程序可以与之交互。对于 Kubernetes,有启动后事件和停止前事件。对于 Lambda,也有类似的扩展 API,允许应用程序拦截初始化、调用和关闭阶段。然后,平台还有 API 来检查应用程序的健康状况。API 来检查应用程序是否已启动,以及是否需要平台采取任何纠正措施。对于 Kubernetes 来说,这基本上是平台执行的各种健康探测。对于 Lambda 来说,由于 Lambda 运行时间非常短,基本上健康状况是由响应状态决定的,并且决定平台是否应该重试请求。
然后,每个计算平台还提供了收集日志、指标和跟踪的方式,现在主要基于结构化日志格式、Prometheus 和 OpenTelemetry 基于度量和跟踪。无论您是否意识到这一点,这些都是一些明确和不明确的合同、约定和实践,称为应用程序与计算云之间的计算绑定,无论您使用哪种平台。我将计算云包括在内,还包括了各种服务网格和透明的 mTLS、可靠性和可观察性问题。如果您看看应用程序与平台之间的所有这些 API,我们作为开发人员几乎不需要做什么。也许我们需要实现健康探测 API,确保应用程序被正确地容器化,并且可以启动和关闭。就这些而言,大部分绑定是由运维团队在运行时操作应用程序使用的。所有这些绑定的好处是,如今它们大多受到容器、Kubernetes 和其他开源项目和格式的影响。它们在大多数计算平台、云提供商甚至不同的应用程序架构中都是相当通用的。整体而言,云原生主要关注计算和计算绑定,并且其影响力如此之大。
外部架构
总而言之,我们研究了应用程序的内部架构是如何从单体架构演变为微服务架构的,并且计算中心的应用程序服务诞生,为应用程序和计算平台之间创建了一种绑定,目前主要由运维团队使用。接下来,我们将看到应用程序的外部架构如何发生变化,也转向云端。我们将研究集成绑定,在本次演讲中,这些是应用程序与其他应用程序云服务、存储层之间的交互集合。这些主要是在应用程序的外部架构中使用的绑定。与计算绑定用于运维团队的情况不同,集成绑定是开发人员在实现应用程序时使用的。同样,我们有我们的容器化应用程序,具有一些内部状态。请注意,当我们查看应用程序的外部架构时,还可以存在应用程序正在与其他第三方系统和服务进行交互的情况。此外,如果您熟悉外部数据的概念,可能还有尚未到达应用程序的状态。该状态可以在工作流引擎中、在 DLQ 中或在重试中。它为此服务指定,但尚未被接受。这是图表上的外部状态,与内部状态一样重要,当我们查看端到端请求流时。这些集成绑定可以是与外部系统的连接器形式。它可以是消息传递和事件逻辑,例如消息重试、过滤器、死信队列、消息延迟、基于内容的路由、处理有毒消息等。所有这些都是集成绑定。它们还可以是服务编排和工作流、Web 钩子和触发器,在特定时间触发应用程序执行特定操作。甚至可以是用于单例组件的分布式日志。基本上,我将开发人员在实现分布式应用程序时必须使用的所有分布式系统模式都归类到此类别中。这些基本上是一系列无差别的技术特性,您必须使用它们来实现应用程序的定制业务逻辑。
我发现更有趣的是这些集成能力可以存在的位置。与计算绑定类似,借助容器和 Kubernetes,普遍格式,甚至 lambda,运行时管理和网络责任已从应用程序、ESB 转移到由运维或云提供商管理的计算层。同样,我们可以看到一些集成责任正在从应用程序层移动到其自己的层,作为独立的中间件,甚至移动到无服务器云服务中。对于这些集成能力的部署选项,传统方法是将所有这些集成逻辑放在应用程序层内。例如,可以使用 Apache Camel 和 Spring Integration 等项目。Camel 提供了数十种消息模式、连接器的实现,这些都被很好地封装在一个漂亮的 Java DSL 中。这种方法提供了最大的灵活性,但不适用于所有流行的语言,并且将应用程序与集成逻辑或生命周期紧密耦合。另一种极端是将所有集成需求全部转移到类似 AWS EventBridge 的框架中,并将应用程序与其耦合。这些框架,如 EventBridge,基本上是现代无服务器基于云的 ESB。如果使用它,您基本上将应用程序与该提供商的整个生态系统和工具耦合在一起。
第三种选择是使用事实上的标准、开源项目和 API 来将应用程序与集成逻辑绑定在一起,类似于容器和 Kubernetes 用于计算绑定的方式。这些开放 API 的示例包括 Apache Kafka,其 API 用于流处理。Redis 用于缓存,甚至 AWS S3 用于文件访问,Dapr 用于分布式系统。这些可以部署在本地,因为通常在应用程序本地有一个开源实现,或者它们可以作为云服务使用,并且您甚至可以改变主意来回移动。某些事实上的标准的限制是它们缺乏我在本次演讲中所描述的更高级别抽象。它们主要关注存储访问层。例如,Kafka 用于消息访问,Redis 仅为键值访问,S3 用于云访问。在本次演讲中,我描述的集成绑定不仅仅是存储访问。它们涵盖了高级开发者关注的内容,例如 Dapr 提供的内容。
我将简要介绍一下 Dapr 是什么。Dapr 是由微软创立并于 2021 年捐赠给 CNCF 的。本质上,Dapr 是一组作为 API 公开的分布式基元,以及作为 Sidecar 部署的。在 Dapr 中,这些能力被称为构建块。它们只是多个实现的 API。例如,有一个状态管理构建块,类似于 Redis API,但它可以有不同的状态存储实现。还有一个类似于 Kafka 的发布/订阅 API,但它有多个实现,比如基于 Kafka、Redis、Amazon SQS、GCP Pub/Sub、RabbitMQ 等。不仅如此,例如发布/订阅 API 可能具有一些其他消息系统所没有的高级功能,但 Dapr 实现了它。例如,DLQs 和过滤,延迟消息传递。基本上,Dapr 实现了我之前讨论的大多数配置绑定,有了有状态的编排模式,这是一个名为 Workflows 的新的 Dapr API。有了 Dapr 中的发布/订阅 API,我之前描述的异步交互。有了 Dapr 中的同步交互,这是 Dapr 中的状态调用 API,等等。在架构方面,Dapr 通常作为 Sidecar 部署,但我们的工作是使其作为 SaaS 工具可用。通过定义良好的 HTTP 和 gRPC API 来使用 Dapr API,无需了解这些 API 的后端实现,这些实现可以由云服务提供商、本地部署或用于开发目的的内存实现提供。
如果我要将 Dapr 与 Camel 和 EventBridge 进行比较,那么它们之间有很多区别。在耦合方面,Camel 是一个与云无关,但非常与编程语言相关的框架。而 EventBridge 则仅适用于 AWS。Dapr 可以被多种语言在不同的云提供商上使用。它可以作为 Sidecar 与您的应用程序共同部署,并最终作为 SaaS 工具进行使用。某个东西是否与语言和云特定相关不仅仅是关于应用程序的可移植性。一个与云和语言无关的框架允许模式、工具、实践和知识的可移植性,甚至可以在不同的项目、团队和云中共享,从而成为通用知识和事实上的标准。
应用优先云
最后,让我们看看这些应用程序云服务是什么,以及它们如何影响我们正在构建的应用程序。我们看到计算云接管了来自开发人员到运维的运行时管理和网络的责任,甚至是托管的云服务。有趣的是,新的计算服务都是关于个别应用程序的。这里我列举了一些 AWS 的服务,但其他云提供商也有类似的服务。这个列表还在不断增长,甚至扩展到边缘。网络服务也变得更加注重应用程序。它们能够理解 HTTP、gRPC 甚至应用程序协议,并为您提供应用层的控制。以类似的方式,我看到了集成云的诞生。集成云基本上是一组托管服务,将集成责任从开发人员身上剥离,并将其作为无服务器能力提供。除了纯存储服务,如 Postgres、MySQL、Kafka、Redis、文件存储外,我还看到了用于处理事件的服务,如 AWS EventBridge、Google Eventarc、Azure Event Grid,以及其他各种变体。还有更多用于有状态编排的服务,如 Step Functions、Temporal Cloud。用于发布/订阅的服务,如 Ably,定时任务服务、Webhooks、数据虚拟化服务、GraphQL 服务等等。所有这些都代表了一个集成云。
所有这些服务首先是为开发人员创建的,而不是为运维创建的,并且通常是完全托管和无服务器的。在结果架构中,核心应用程序逻辑可以绑定到一个或多个云服务,通过计算和集成 API 进行。其中一些 API 基于开源项目,而一些则是特定于供应商的。开发人员仍然负责在其应用程序中暴露某些 API,并调用集成云的 API,并将其应用程序业务逻辑与该云连接起来。理想情况下,这应该是遵循六边形架构原则完成的,但从更现代的视角来看,使用开放的 API 和格式,而不是原始的进程内方法调用和接口。在这种架构中,计算和集成能力作为 SaaS 进行消费,委托给值得信赖的第三方公司。运维的角色更多地涉及对这些云服务进行管理、安全配置。运维负责实现区分业务逻辑,并重复使用无差异的集成能力作为服务。
在这里,我们看到更多专业化的应用程序优先云服务,应用程序可以绑定到这些服务。不同的无服务器计算服务、无服务器流量路由服务、事件处理服务、有状态编排服务,理想情况下,一个应用程序不应该绑定到所有这些服务。应用程序应该在同一云区域和云提供商内使用少数服务,并通过开放的 API 进行使用,这样如果需要的话,就可以灵活地迁移到其他地方。在我看来,我们将看到更多的应用程序在云上运行,不仅仅绑定到计算层,也不仅仅绑定到存储层,还绑定到集成层。如果您相信云提供商能够处理数据和计算,为什么不相信它也能处理集成层,只要它具有标准化的边界,实现应用程序和开发人员的可移植性。
主要收获
最后,为什么所有这些都很重要,这次演讲有哪些主要收获。首先,本次演讲的目标之一是概述应用程序和基础设施在过去二十年的演变。也许这是对未来变化方向的一个指示。就收获而言。首先,您应该使用开放的计算绑定封装应用程序的内部架构,无论是微服务、函数、模块化的单体应用程序,使用容器作为基础。如果您理解容器、它们的生命周期事件、资源约束和健康检查,您就可以快速理解许多计算平台并使用它们,从而受益于整个工具和知识生态系统,而无需重新发明轮子。其次,专注于在应用程序中实现差异化的业务功能,并尝试通过 API 重用无差异的重复分布式功能,就像我们今天对计算和存储所做的那样。归根结底,这归结为可移植性。它很少涉及将应用程序从一个云迁移到另一个云。更多的是关于人员和工具的可移植性。它是从一个项目到另一个项目的可移植性,从一个云到另一个云的可移植性,从一个雇主到另一个雇主的可移植性。我们对于计算层具有这种可移植性,我认为我们也需要对集成层具有同样的可移植性。