Serverless时代的Kubernetes工作负载:架构、平台和趋势

之前看到的一篇关于Serverless以及Kubernetes的文章,很受启发。文中介绍了从单体->SOA->微服务->云原生>serverless的架构进化过程,并阐述了当前serverless的现状以及未来的趋势。篇幅稍长,但包含了大量对技术的思考,尤其是关于各种架构的设计取舍权衡,值得认真阅读。一直没发现中文版,于是自己进行了翻译。

原文:Kubernetes Workloads in the Serverless Era: Architecture, Platforms, and Trends,作者Bilgin Ibryam,Red Hat的首席架构师。

关键要点

  • 微服务架构已演化为云原生架构,而其中许多基础设施功能已由Kubernetes以及服务网格(service mesh)和无服务器(serverless)框架提供。
  • Kubernetes的卓越之处在于其通过Pod抽象对新的工作负载提供了扩展能力,这也支持了新的云原生应用程序模式的出现。
  • Kubernetes不仅支持无状态应用程序,而且还支持有状态工作负载、单例、批处理作业、cron作业,甚至通过CRD和operator提供无服务器和自定义工作负载。
  • 当今的serverless平台仍具有很大的局限性,使其无法被需要强大互操作和可移植能力的企业公司采用。
  • 通过探索标准和开放的打包、运行时和事件格式,serverless生态系统正在不断发展。这些领域的进步正在模糊云原生和serverless工作负载之间的差异,并且已推动serverless产品变成开放、可移植、可互操作的框架。

Kubernetes中的工作负载(workload)是描述pod部署规则的一组对象,通过这些对象,Kubernetes可以定义应用的调度、扩容以及升级等操作,具体包括Deployment、StatefulSet、DeamonSet、Job、CronJob等。

在预测serverless的发展方向和适用范围之前,我们首先分析下如何以及为什么会到当前这样的一个时代。

单体架构(Monolithic Architecture)

我在开始从事大型集成项目时,面向服务的体系结构(SOA)是最受欢迎的体系架构,而企业服务总线(ESB)是其最常见的实现。
SOA建立在良好的原则之上,这些原则大多数如今仍然有效:契约优先开发,松耦合,可组合和无状态的服务,这些服务同时具有自治性和可重用性。
ESB框架提供了一组很好的能力,例如协议转换、技术连接器、路由和编排机制、错误处理和高可用性原语。

分布式架构的演进

从架构和组织的角度来看,SOA和ESB的主要问题是集中化。SOA的重要原则是服务和组件的重用,这导致了分层服务架构的创建,分层架构实现了重用但却导致了紧密的架构服务耦合。在组织上,ESB由一个团队拥有,这使得中间件变成可伸缩性、快速演进(更重要)的技术与组织瓶颈。由少数人定义的复杂行业规范发展是缓慢的。

微服务架构1.0

尽管SOA为解决企业级复杂性提供了良好的基础,但技术发展迅速。开源模式推动了更快的创新,并成为了新的技术发行和标准化机制。

同时,诸如XP、Scrum和看板之类的敏捷开发实践产生了迭代的软件开发,但是这种方法与现有的单体架构相冲突,后者无法应对快速部署的增量设计。因此,实际情况是功能是经过两周的迭代开发的,但每年仅部署一次或两次。

微服务架构的适时出现被看作是有望解决这些挑战的灵丹妙药(panacea )。微服务架构凭借其指导原则,可以应对更快的变化。服务围绕业务领域建模,比可重用的SOA服务更有效地将更改封装在服务内。自主且可独立部署的服务允许每个服务以自己的步调演化和扩展。

尽管这些新原则在快速迭代和实验(experimentation)时代优化了SOA,但它们也通过将每个应用程序转变为分布式系统而带来了挑战。结果微服务还必须是高度可自动化的、高度可观察的、容错的,同时还需拥有分布式系统组件必需的所有其它功能。这个代价并不是每个人最初都意识到并做好准备买单的。

微服务架构作为技术答案诞生于快速迭代的开发和试验时代,但是我们在古老的工具之上构建了第一代微服务,这导致它们很难管理。在ESB时代,业务逻辑泄漏到平台中,导致各种耦合;而在微服务时早期时代,我们观察到的正好相反:太多的基础架构问题泄漏到每个微服务中。早期的微服务有其自身的挑战;他们必须自己进行服务发现、配置管理,网络弹性、日志分发等。一个组织具备创建成十上百个新服务的能力,并不一定意味着已做好准备将这些服务统一管理和部署到生产中。发布、管理以及向运营团队移交的流程和支持工具必须进行改进,所有这些将我们推向了Kubernetes时代。

云原生架构(又名微服务2.0)

微服务架构改进了SOA以应对快速变化,但其实是拿代码复杂性交换成了运维复杂性。它面临的挑战导致了容器的流行,容器一夜之间接管了整个行业。容器的出现是为了解决如何统一部署大量微服务并有效地践行DevOps。通过容器,我们可以一种开发和运营团队都理解和使用的格式打包并运行应用程序。很早以前就很清楚,管理数十或数百个容器将需要自动化,而Kubernetes彷佛如来神掌,从天而降,横扫所有竞争对手。Kubernetes解决了技术挑战,DevOps实践解决了微服务的文化方面。

这是新转变的开始,基础架构职责从应用层转移到平台层。云原生平台(最初是很多,但最终主要是Kubernetes)提供的功能包括资源隔离和管理、自动放置(automated placement)、声明式部署,健康检查、自我修复、配置管理、服务发现、自动扩展等。这些功能使应用开发人员可以专注于业务逻辑,并使用开箱即用的平台功能来统一解决基础架构问题。

当时,我将流行的微服务 1.0工具(Spring Cloud和Netflix OSS)与微服务2.0工具(以Kubernetes作为事实上的标准)进行了比较,并收到了读者的不同意见。如今,它已被更广泛理解和接受,这一点转变也被最终证实:Kubernetes作为微服务管理平台的完全统治地位以及对较早一代的许多Netflix OSS库的弃用。

但是,所有这些都是历史,我们来探索Kubernetes的未来。

Kubernetes的光彩(brilliance)

Kubernetes架构有许多迷人的元素:容器提供了通用的打包机制、运行时和资源隔离模型;简单的控制循环(control-loop )机制用于监视组件的实际状态并将其与所需状态保持一致(调谐reconcile)、自定义资源。但是扩展Kubernetes支持各种工作负载的真正推动力是Pod的概念。

一个pod提供两组保证:

  1. 部署保证可确保将pod内的容器始终放置在同一节点上。此行为具有一些有用的属性,例如允许容器通过localhost、进程间通信(IPC)或使用本地文件系统同步或异步通信。
  2. 生命周期保证可确保将pod中的容器实际分为两组进行管理:初始化容器和应用程序容器。初始化容器首先运行;它们一个接一个地运行,并且仅在先前的容器已成功完成时才运行。初始化容器启用顺序的管道行为,并用单个容器执行每个步骤。另一方面,应用容器并行运行,没有任何顺序保证。这组容器使流行的边车(sidercar)模式能够扩展和增强具有正交功能的已有容器的功能。
Pod的部署和生命周期保证

可扩展的控制循环(control-loop)机制与通用的pod特性相结合,使Kubernetes能够处理各种工作负载,包括serverless。让我们看下这些不同的工作负载,看看每种工作负载都适合哪种用例。

云原生工作负载

要证明Kubernetes是一个能够支持各种工作负载和用例的通用平台,需要探索不同的工作负载类型及其需求。

有状态的服务

让我们从最不令人兴奋的工作负载(workload)开始,但它几乎总是存在于企业环境中:有状态服务。通过将状态转移到外部数据存储中,可以将具有业务逻辑的状态服务转换为可伸缩的无状态服务。这样的设计将约束从业务服务移至数据源,这些数据源成为架构中的有状态组件。这些数据存储通常是现成的关系数据库、分布式缓存、键值存储、搜索索引等。在动态的云基础设施上管理分布式有状态组件需要一些保证,例如:

  • 持久化存储--状态通常位于磁盘上,而分布式有状态应用程序需要每个实例专用的持久存储来存储其状态。
  • 稳定的网络ID--与存储要求类似,分布式有状态应用程序需要稳定的网络身份。有状态应用程序除了在存储空间中存放应用特定的数据外,还存放配置详细信息,例如交互主机名和连接详细信息。这意味着每个实例都应该通过一个可预测的、不会动态更改的地址访问,就像在ReplicaSet中的pod IP地址一样。
  • 稳定的身份标识 --从前面的要求中可以看出,有状态应用程序集群在很大程度上依赖于每个实例保持其持久(long-lived)存储和网络标识。这是因为在有状态应用程序中,每个实例都是唯一的,并且知道其自己的身份标识,该标识的主要构成就是持久存储和网络坐标。在此列表中,我们还可以添加实例的身份/名称(某些有状态应用程序也需要唯一的永久名称),在Kubernetes中将其作为Pod名称。
  • 普通性--除了唯一且长期存在的身份标识外,集群化有状态应用程序的每个实例也具有一个固定位置(这个位置是相对于其它重要实例的)。这种排列通常会影响实例按比例缩放的顺序,但它也可以用作一致性哈希算法、数据分发和访问以及集群内行为归置(in-cluster behaviors placement )(例如锁,单例或主服务器)的基础。
Kubernetes上的分布式有状态应用程序

这些正是Kubernetes StatefulSet提供的保证。一个StatefulSet提供了用于管理具有状态特征的Pod的通用原语(primitives)。除了典型的ZooKeeper、Redis和Hazelcast的部署依赖StatefulSet,其他用例还包括消息代理甚至事务管理器。

例如,Narayana事务管理器用来StatefulSet确保在缩减服务期间使用分布式事务不会丢失任何JTA日志。在缩小集群消息代理时,Apache Artemis消息代理依赖StatefulSet消费完消息。StatefulSet是一个功能强大的通用的抽象,复杂的状态使用的情况下非常有用。

全局单例

来自“ 四人帮 ”(Gang of Four)的单例模式是一个古老且易于理解的概念。在分布式、云原生世界中,对应的是单例组件(整个服务或其中的一部分)的概念,该组件是全局单例(在所有分布式服务中),但仍具有很高的可用性。这种工作负载类型的用例通常源于我们必须与之交互的其它系统的技术约束,例如,一次仅允许单个客户端访问的API、数据源和文件系统。另一个用例是必须保留消息的顺序性,于是将其消费者服务限制为单例。Kubernetes有一些支持这些用例的选项。

最简单的选择是依靠Kubernetes运行服务的单个实例。我们可以通过使用一个ReplicaSetStatefulSet,设置replicas=1轻松实现这一目标。两种选择之间的区别在于,你是需要强一致性的单例(保证至多一个),还是弱一致性单例(保证至少一个)。一个ReplicaSet更看重可用性并优先保持单个实例可用(“至少一个”语义),这有时可能会导致多个实例同时运行,例如当某个节点与集群断开连接,但ReplicaSet不确认此节点Pod已停止时又启动另一个节点上Pod。一个StatefulSet优先考虑一致性而不是可用性,并提供“至多一个”语义。如果某个节点与集群断开连接,它将无法在运行正常的节点上启动Pod。只有在运维人员确认断开连接的节点或pod真正关闭后,才能重新启动Pod。有时这可能会导致服务停机,但绝不会导致同时运行多个实例。

还有一种选择可以从应用程序内部实现自我管理的单例。在前面的方案中,应用程序并不知道会被当作一个单例来管理,而自我管理的单例则确保仅激活了一个组件,而与启动的服务实例(pods)数量无关。这种单例机制依赖特定运行时的实现来获取锁并充当单例,但是它具有一些优点。首先,不存在意外配置错误的危险,并且增加副本数仍然能保证任何给定时间只有一个组件处于活动状态。其次,它允许扩展服务同时能够确保服务service的部分功能(例如端点)的单例行为。当由于特定操作或端点的外部技术限制,微服务的局部而不是整个必须是单例时,这很有用。此用例的示例实现是Apache Camel的Kubernetes连接器的单例功能,该连接器可以使用Kubernetes ConfigMap作为分布式密钥,并且仅激活Kubernetes中部署的多个Camel服务中的单个Camel使用者。

Kubernetes上的单例工作负载

单例是数量较少的另一种工作负载类型,但是它们也很常见。单例和高可用性是两个相互冲突的需求,但是Kubernetes足够灵活,可以在可接受的折衷方案中同时提供这两个。

批处理作业

批处理作业适用于管理那些处理孤立原子工作单元的工作负载。在Kubernetes原语中,它是作为job抽象实现的,它可以在分布式环境上通过可靠地运行临时pod完成。

Kubernetes上的批处理和周期性工作负载

从生命周期的角度来看,批处理工作负载具有与异步serverless工作负载相似的特征,因为它们专注于单个操作,并且寿命很短,持续到任务完成即可。但是,尽管基于job的工作负载本质上是异步的,但它们不会从客户那里直接获取输入信息,也不会直接响应客户请求。它们通常知道从何处检索输入数据以及在何处写入结果。如果作业具有时间维度(即已计划),则将定期由时间事件触发执行。

无状态工作负载(又名12要素应用程序)

无状态工作负载是Kubernetes上使用最广泛的工作负载类型。这是使用ReplicaSet在Kubernetes上管理的典型的12要素应用程序或基于微服务的系统。通常,一个ReplicaSet将管理此类服务的多个实例,并将使用不同的自动缩放策略来水平和垂直地缩放此类工作负载。

Kubernetes上的支持服务发现的无状态工作负载

ReplicaSet管理的服务的常见要求是服务发现和负载平衡。在这里,Kubernetes提供了多种开箱即用的选项。

Kubernetes的服务发现机制

这里的要点是,即使有多种服务发现机制可以动态检测健康和不健康的Pod实例,但不同的服务类型本质上都是相对静态的。 Kubernetes service原语不提供动态流量监控和转移的能力。这是服务网格(service mesh)粉墨登场的原因。

服务网格(service mesh)

构建基于微服务的系统时面临的挑战之一是创建不属于业务逻辑的商品特性,例如弹性通信、跟踪、监视等。此逻辑曾经位于中央ESB层,现在必须重复分散在微服务的智能客户端之间。服务网格技术旨在通过提供额外增强的网络功能来解决此问题,例如:

  • 流量路由—A / B测试,分阶段回滚。
  • 弹性—重试,断路器,连接限制,运行状况检查。
  • 安全性—身份验证、授权、加密(mTLS)。
  • 可观察性—指标,跟踪。
  • 测试—故障注入,流量镜像。
  • 平台独立性—多种语言,允许运行时配置。
    如果我们仔细研究这些功能,会注意到集成框架提供的功能存在大量重叠。
服务网格和集成框架职责重叠

将所有这些职责移到服务之外是否是一个好的方法,存在不同的意见。尽管网络职责已从应用层转移到通用的云原生平台中,但并非所有网络职责都移出了应用程序:

  • 服务网格适用于基于连接的流量路由,而服务内部的集成框架则适用于基于内容的路由。
  • 服务网格可以进行协议转换,而集成框架可以进行内容转换。
  • 服务网格可以进行灰度发布,而集成框架可以进行监控(wire-tapping)。
  • 服务网格进行基于连接的加密,而集成框架可以进行内容加密。

某些需求可以在服务中更好地处理,而某些需求可以使用服务网格从外部更好地处理。还有一些必须在两层都进行处理:可以从服务网格层配置连接超时,但是仍然必需在服务内部设置连接超时。对于其它行为,例如通过重试和任何其它错误处理逻辑进行恢复也是如此。服务网格、Kubernetes和其他云服务是当今的工具,但是对应用程序的端到端可靠性和正确性的最终责任在于服务实现及其开发和设计团队。这不会改变。

服务网格技术进一步强调了第一代和第二代微服务之间的主要区别:将某些操作职责转移到平台上。Kubernetes将部署职责转移到平台,而服务网格将网络职责转移到平台。但这还不是最终状态。这些变化只是为serverless世界铺平了道路,在serverless世界中,部署和基于流量的即时可伸缩性是前提。

Serverless(无服务器)概念

一切都与视角有关

为了讨论serverless的特性,我将使用 Cloud Native Computing Foundation(CNCF)的serverless工作组的定义 ,因为它是许多不同软件供应商之间最广泛认可的定义之一:

无服务器计算(serverless computing)是指构建和运行不需要服务器管理的应用程序的概念。它描述了一种更细粒度的部署模型,该模型中应用程序被封装为一个或多个功能,然后上传到平台,按需进行执行、扩容以及计费。

如果从开发人员可从serverless平台获益这个角度理解将这种定义,我们可将serverless总结为这样一种架构:该架构可实现“按需运行更细粒度的功能同时无需管理服务器”。通常我们都是从开发人员的角度考虑serverless,但还有另一个很少讨论的角度。每个无服务器平台都有提供者来管理平台和服务器:他们必须管理粗粒度计算单元,并且无论需求如何,其平台都会产生24x7的成本。这些提供商是AWS Lambda,Azure Functions和Google Cloud Functions背后的团队,或者你公司中管理Apache OpenWhisk,使用Knative的Kubernetes或其他功能的团队。在这些场景下,提供商可以使开发人员可以将计算和存储作为没有任何服务器概念的工作负载类型使用。根据组织和业务因素,提供商可以是同一组织中的另一个团队/部门(想象一个使用AWS Lambda来满足其需求的Amazon团队),也可以是另一个组织(当AWS客户使用Lambda和其他服务时)。无论提供商与消费者之间的业务安排如何,消费者都不会对服务器负责,需要负责的是提供者。

Serverless(无服务器)架构

上面的定义仅指“无服务器计算”。但是,应用程序的架构是由计算和数据组合而成的。无服务器架构的更完整定义是既包含无服务器计算又包含无服务器数据。典型情况是,此类应用程序将集成云托管服务来管理状态和通用服务器端逻辑,例如身份验证、API网关、监视、告警、日志记录等。我们通常将这些托管服务称为“后端即服务” (BaaS)–考虑诸如DynamoDB,SQS,SNS,API网关,CloudWatch等服务。事后看来,用术语“服务化(而非“无服务器”)或许可以更精确地描述所这种架构。但是并不是所有的东西都可以用第三方服务代替;如果是这种情况,并且有一个业务逻辑的服务,那么你就没法开展生意了(也就是有些服务总归是要自己开发的)!因此,无服务器架构通常还具有“功能/函数即服务”(FaaS)组件,这些组件允许执行由事件触发的自定义无状态计算。当下最受欢迎的示例是AWS Lambda。

完整的无服务器架构由BaaS和FaaS组成,从消费者/开发人员的角度来看,没有服务器的概念。没有要管理或配置的服务器还意味着基于消耗的定价(并不是配置的容量),内置的自动扩展(可到一个限制),内置的高可用和容错能力,内置的修补程序和安全强化(带有内置支持策略限制),监视和日志记录(作为额外的付费服务)等。所有这些都由无服务器的开发人员使用,并由无服务器的提供程序提供。

Pure Serverless(纯粹无服务器)

如果无服务器架构听起来不错,为什么不拥有一个纯粹的无服务器架构---其中所有组件都100%无服务器并且没有服务器概念?这个问题可以引用艾伦·厄尔曼(Ellen Ullman)的话来解释其原因:“我们以建立城市的方式来构建计算机系统:随着时间推移,没有计划,建立废墟之上。” 企业系统就像一座古老的城市,通常它已经存在了十多年,这就是它的价值和重要性所在,这就是使其成为“企业”的原因。想象一下伦敦,这座已经存在了2000多年的城市,拥有百年历史的地铁系统,狭窄的街道、宫殿、维多利亚时代的社区和供水系统;如此复杂的系统永远无法被新系统完全取代,它总是会进行恢复和更新(重构,升级,迁移,并在IT方面重新平台化)。在这样的系统中,变化的状态是常态,新旧混合存在的状态是常态。这些系统应该是这样存在的。

Serverless1.0

容器技术已经以多种形式存在了很多年,但是Docker对其进行了普及,而Kubernetes使它成为部署的规范。同样,无服务器技术已经存在了很多年,但是AWS Lambda使其变得流行,我们还没有看到谁将其带入一个新的高度。

Serverless 1.0基本上是由AWS定义的,并由FaaS组件的AWS Lambda代表,以及BaaS组件的其他AWS服务(例如API Gateway,SQS,SNS等)代表。基于AWS(定义趋势)以及Google和Azure(正努力追赶),这里列举了当前这代无服务器的一些特征,这些特征不理想并且是待改进的。

非确定性执行模型具有:

  • 不可预测的容器生命周期以及对冷启动有影响的重用语义;
  • 对编程模型的约束,这会影响代码初始化逻辑,导致回调泄漏,产生附加的递归调用成本等;
  • 不可预测的资源生命周期,例如/ tmp文件存储;
  • 内存、超时、有效负载、程序包、临时文件系统和环境变量的强行限制;
  • 整个行业中的总体的、非标准化执行模型,其非标准化约束会影响特定无服务器供应商的编程模型。

受限的运行时支持意味着:

  • 无服务器软件栈包含操作系统、语言运行时和库版本,这些都局限于OS,JDK和应用程序库(例如AWS开发工具包)的一个特定版本。
  • 平台支持策略通常规定在什么条件下可以弃用和更新任何无服务器堆栈组件 ,从而迫使所有serverless用户按照严格的时间步调升级。
  • 编程API可能会导致问题。尽管我使用AWS SDK开发服务方面有一个好的体验,但我不喜欢所有功能都必须与com.amazonaws.services.lambda.runtime包及其编程模型紧密结合。
  • 我们使用非标准包装。将.zip,uber-JAR或lib目录与自定义AWS特定的分层和依赖关系模型一起使用,并不能使我确信该打包程序可以用于将来,以及在其它无服务器平台上运行。
    自定义环境变量(以AWS_开头)在其他无服务器平台上不起作用。

专有数据格式

专有数据格式是一个障碍。事件是serverless体系结构中函数(function)的主要连接机制。它们将每个function与其他function和BaaS连接起来。它们实际上是function的API和数据格式。在所有函数中使用com.amazonaws.services.lambda.runtime.events包中定义的事件可根本无法保证互操作性。

缺少Java支持

尽管有适用于AWS Lambda的Java运行时,但Java与Lambda之间存在如此不匹配的地方,甚至AWS都不推荐这样做。在Tim Bray的“ Inside AWS:现代应用程序的技术选择”演讲中,他建议改为使用Go和Python。我希望没有服务器的提供商能够做得更好,并改善其运行时,而不是试图对数百万的Java开发人员进行重新教育并改变数以百万计的Java库的生态系统。Java已经和Go一样轻巧和快速(甚至更好),因此使用这种语言构建Serverless是难以避免的。

可能的影响

综上所述,不确定性和非标准化执行模型,专有运行时,专有数据格式,专有API以及缺少Java支持的结合意味着所有这些问题都会渗透到应用程序代码中,并影响我们实现业务逻辑的方式。这是从一个组织到另一个组织的最终控制权的下放。由于当今serverless提供了构建时的构件,并提供了运行时环境(以SDK、打包格式,事件格式和软件堆栈的形式),因此使用者会遵循支持策略和提供商所施加的专有限制。使用者承诺坚持并遵守提供者的语言运行时,SDK,升级和弃用。他们通过编写在无服务器平台之间具有零互操作性的函数来遵守这些条款。如果我们使用BaaS进行所有操作,并且我们刚刚将以function编写的组织业务逻辑与专有执行模型、运行时、API和数据格式相结合,那么我们将无处可去。尽管我们可能不想走到其他任何地方,但对于某些人来说,拥有选择权很重要。

耦合和锁定本身并不坏,但是迁移成本很高。使用AWS AMI,AWS RDS,流行的开源项目作为托管服务,甚至使用SQS都是例子,他们不介意被锁定,因为迁移到替代服务或提供商是可行的选择。这与将业务逻辑与不成熟的serverless技术和serverless提供商特征相结合是不同的。在这里,迁移工作需要对业务逻辑和粘合代码的完整重写和测试,考虑到无服务器体系结构的高度分布式特性,这特别昂贵。

微服务是将用代码复杂性与操作复杂性进行了交换。serverless是用控制权换来了速度。选择现代化架构,但请阅读小字体附加条款(原文:read the small print,意思是指要注意这些架构潜在的一些附加特性)。每个架构选择都是一个权衡。

Serverless1.5

AWS将serverless技术带到当前现状已做得非常出色,但是如果serverless技术的当前状态是其最高程度,那将是令人遗憾的。如果只有AWS能在serverless领域内进行创新并定义其未来,那也将是可悲的。考虑到AWS在开源生态系统中的资历相对有限,期望AWS标准化serverless模式将是一个难题,进而在整个基础层面上影响整个行业。AWS业务模型和市场位置非常适合识别市场趋势和最初的封闭式创新,而开源模式则更适合于泛化、标准化和被行业范围内的自愿接受。我希望下一代serveless将使用开源模型创建,并在整个行业中进行更广泛的协作,这将有助于其被采用和提高互操作能力。这个过程已经开始,并且业界正在缓慢地探索当今专有serverless产品的可互操作和可移植的替代方案。

让我们讨论一些行业趋势(不分先后顺序),我相信这些趋势将驱动并影响未来的serverless技术。

统一的打包和执行模型

容器被成为应用程序打包和运行时的行业标准。如前所述,容器化应用程序与功能强大的编排引擎相结合可支持丰富的工作负载。serverless工作负载没有理由是例外,因为这会使我们回到打包格式和执行模型的混合状态。Knative是一个开放的、多家供应商的共同努力的一个成果,它通过向Kubernetes上基于容器的工作负载提供无服务器特性(缩放至零,基于HTTP请求自动缩放、订阅、交付、绑定和事件管理)来挑战现状。基于容器的打包和基于Kubernetes的执行将允许一个开放的执行模型,该模型可以在多个无服务器提供商之间进行标准化。它通过对Java、自定义软件堆栈,限制和自定义可能性更好支持来实现一组更丰富的运行时。

有人可能会争辩说,按照serverless的原始定义,在函数包中包括语言运行时和事件处理程序不是FaaS而是实现细节,而这是未来serverless一个急需的选择。

行业公认的事件格式

Serverless架构的定义就是事件驱动的,事件扮演着中心角色。在Serverless环境中,事件类型越多,开发人员的经验越丰富,可用现成服务替换的逻辑就越多。但是,随着业务逻辑与事件格式和结构的结合,将需付出一定的代价。尽管您可以阅读将核心业务逻辑和事件处理逻辑分离为单独方法的AWS最佳实践,但这远没有解耦。业务逻辑与serverless平台的数据格式的这种耦合阻止了互操作性。 CloudEvents致力于创建可在所有无服务器平台上运行的标准化事件格式。这是一个很棒的想法,业内很多企业包括 AWS 都对其产生了浓厚的兴趣 ,这可能是对其重要性和采用潜力的最终验证。

可移植性和互操作性

一旦有了标准的打包格式和标准事件,下一个程度的自由就是能在公共或私有云、本地或边缘上的serverless提供商上运行serverless工作负载,并按需将所有内容搭配并匹配到一个混合环境中。function应可在多云,混合云,任何云,非云或混合云上运行,并且只需要少量配置和映射。就像我们以前编写Java应用程序以实现抽象接口并将其部署到不同的Web容器一样,我希望能够为非专有API,事件和编程模型编写函数,并将其部署到任何无服务器的环境中平台,并使其以可预测和确定性的方式运行。

除了可移植性之外,我还希望看到互操作性,该函数能够从任何平台消费事件,而无论该函数在何处运行。诸如KEDA之类的 项目使我们可以运行自定义函数(如Azure Functions),以响应AWS,Azure和其他事件触发器。TriggerMesh等项目 使我们能够在Kubernetes 和OpenShift之上部署与AWS Lambda兼容的function 。这些迹象表明,未来的function将在多个级别上具有可移植性和互操作性:打包、执行环境、事件格式、事件源、工具等。

将Java视为一等公民

尽管serverless工作负载适用于许多用例,但阻止使用Java(企业最流行的编程语言)是一个主要限制。得益于Substrate VM 和Quarkus等框架 ,Java已经轻巧,快速,云原生且serverless友好。而且有迹象表明 ,这种Java运行时很快也将可用于serverless,包括AWS Lambda,希望是这样。

容器化工作负载具有serverless特性、function可移植性、具有标准化事件的互操作性、为云原生和serverless环境创建的超轻、快速Java运行时,这些都是serverless即将改变的信号。我还不想将这些特征叫做“第二代无服务器”,但它不是第1代无服务器,感觉1.5会更合适。

我记得当许多人以为Cloud Foundry赢得了PaaS战争时,却出现了Kubernetes。现在,许多人声称AWS Lambda赢得了FaaS战争。我希望Kubernetes(或更好的东西)证明他们是错误的。

Serverless工作负载

我们看到了微服务架构如何通过围绕业务域对服务进行建模,并通过将变更封装在服务中来改善单片应用程序的部署周期。serverless的一个简单的描述是:更小的微服务,每个操作都是一个function。尽管从技术上讲这是可能的,但这在两种架构中都是最糟糕的,导致大量function以同步方式相互调用,不能从最终架构中获得任何好处。

微服务的价值来自以下事实:它可以使用基于Web的API(通常为REST风格)将复杂的业务域逻辑和持久性逻辑封装在一系列“请求/响应”方式的操作之后。另一方面,serverless和function专注于事件和触发器。虽然可以将function放在API网关后面并以“请求/响应”方式运行,但该API并不是主要接口:事件和触发器才是。当应用程序是异步的(单向即发即弃(fire-and-forget))方式,而不是“请求/响应”方式)并且通过队列或其他数据和事件源进行连接时,serverless应用程序往往会运行得更好。结果,每个函数仅打算执行一个操作,并且应避免直接直接调用其他function,然后将结果写入事件存储。这种执行模型下,function的生命周期很短,应该与其他非面向连接的无服务器数据源一起使用(RDBMS是典型面向连接得),部署规模较小,并且启动迅速。以下所有用例使serverless更加适合,因为它充当连接各种事件驱动系统的粘合代码:

  • 按需function,例如批处理,流处理和提取转换加载(ETL);
  • 短期执行的可分割工作的任务调度,例如批处理工作;
  • 事件驱动架构,可响应数据源更改执行逻辑;
  • 处理非均匀流量,例如不经常发生的不一致流量或负载不可预测的负载;
  • 操作中的通用“胶水”代码;
  • 用于构建作业,具有按需资源的持续集成流水线;
  • 自动化操作任务,例如触发操作或在事件发生时通知值班人员。

我讨论了serverless世界中正在发生的一些重大创新,但没有描述它们运行在Kubernetes上是什么样。许多努力试图将serverless带入Kubernetes,但是拥有最广泛的行业支持和成功机会的项目是Knative。Knative的主要目标是为常见的serverless用例提供具有更高层次抽象的集中API。它仍然是一个年轻的项目(撰写本文时为0.5版),并且变化很快。让我们探讨一下Knative当前支持的serverless工作负载。

Knative 包括3个关键组件:1. build(构建)你的应用程序;2. 为其提供流量 serving(服务);3. 确保应用程序能够轻松地生产和消费 event(事件),原文中只提到了后两种。

Request Serving

从微服务到无服务器的低摩擦(low-friction,指的是改造困难更小、更容易)过渡方法是使用单操作function来处理HTTP请求。我们希望serverless平台能够在几秒钟内拉起(stand up)一个无状态、可扩展的function--这就是 Knative Serving旨在通过为serverless工作负载提供通用工具包和API框架来实现的。在这种情况下,serverless工作负载是一个单一容器的无状态Pod,主要由应用层(L7)请求流量驱动。

Knative Serving项目提供了原语,可实现以下功能:

  • 通过提供更高层次的、有思想的原语,快速部署无服务器容器;
  • 激活,根据请求将其放大和缩小到零;
  • 自动路由和配置低级原语;
  • 版本的不变快照(已部署的代码和配置)。

以上所有内容均可在某些限制内实现,例如每个pod一个容器、单个端口,无持久化以及其他一些限制。

Eventing

Knative Eventing项目提供了创建可靠、可扩展、异步事件驱动型应用程序的构建块(building blocks)。它旨在使用CloudEvents标准围绕事件的消耗和创建创建标准的体验。Knative Eventing的高级功能包括:

  • 可扩展和可插拔的体系结构,允许不同的导入程序(例如GitHub,Kafka,SQS,Apache Camel等)和渠道(例如Kafka,Google Pub / Sub,NATS,内存中等);
  • 事件注册表,用于维护事件类型的目录;
  • 通过绑定事件源,触发器和服务来进行事件编排的声明性API;
  • 触发器功能,允许在将事件路由到下游Knative服务之前从特定broker订阅事件并进行可选的过滤。

这些功能很有趣,但是它们如何帮助云原生开发人员在Kubernetes上提高生产力?

假设我们已经实现了一个function,将其构建为容器,并对其进行了全面测试。它基本上是一个只包含单个操作的服务,该服务通过HTTP接受CloudEvents。使用Knative ,我们可以将容器作为具有serverless特征的工作负载部署到Kubernetes。例如,使用Knative Serving原语,容器只能在接受HTTP请求时激活,并在必要时迅速扩展。此外,还可以将同一pod进行配置,通过订阅渠道(channel)来接受来自代理(broker)的CloudEvent。该pod还可以充当通过Knative Sequence定义的更复杂的事件编排流程中的步骤。仅使用声明性Knative配置,而无需修改已构建的容器,所有这些都是可能的。Knative将确保serverless基础架构的路由、激活、可伸缩性、可靠性、订阅、重新交付和代理弹性。这些还没有全部实现,但是正在进行中。

自定义工作负载

这还不是全部。如果您的应用程序具有非常特殊的需求,而标准工作负载原语都无法满足您的需求,那么Kubernetes可以提供更多选项。在这种情况下,自定义控制器(Custom Controller)可以通过主动监视并将Kubernetes资源集保持到所需状态,来向集群的行为添加定制功能。

从更高层次看,控制器(controller )是一个执行“观察->分析->操作”步骤的主动调谐(reconciliation )过程。它监视所需对象的所需状态,并将其与现实中实际状态进行比较。然后,该过程发送指令尝试将现实中当前状态修改的更接近所需状态。

处理自定义工作负载的一种更高级的方法是利用另一个优秀的Kubernetes扩展机制:CustomResourceDefinitions。将Kubernetes Operator与CustomResourceDefinitions组合在一起可以将“专有算法”形式的特定应用程序的操作知识封装起来。一个Operator就是一个了解Kubernetes和应用领域的Kubernetes控制器--通过结合这两个方面的知识,它可以自动执行通常需要人工操作的任务。

Controller和Operator正在转变为用于扩展平台、在Kubernetes上实现复杂的应用生命周期的标准机制。结果,形成了一个用于管理更复杂的云原生工作负载的整个生命周期的控制器生态系统。

Operator模式允许我们扩展Controller模式,以获得更大的灵活性和更高的表达能力。我最近与人合著的Kubernetes模式一书涵盖了本文讨论的所有工作负载类型以及其它相关模式。请查看有关这些主题的更多详细信息。

云原生趋势

在Kubernetes生态系统中,将越来越多的不属于业务逻辑的商品功能迁移到平台层的趋势仍然在延续:

  • 部署、放置(placement)、健康检查、恢复、扩展,服务发现和配置管理已全部移至Kubernetes层。
  • 服务网格通过将与网络相关的职责(例如弹性通信,跟踪,监视,传输级安全性和流量管理)转移到平台来延续这种趋势。
  • Knative添加了专用的serverless原语,并将快速扩容、缩容、路由、事件基础设施抽象、事件发布、订阅机制以及工作流等责任也移到了平台上。

剩下的主要是业务逻辑问题由应用程序开发人员实施。平台负责其余的工作。

职责转移到平台上,更多抽象支持多种工作负载

通过向Kubernetes添加更高级别的抽象,我们将越来越多的商品职责从应用程序层转移到平台上。例如,Istio在较低级Kubernetes原语的基础上提供了更高级网络抽象。Knative添加了更高级别的serverless抽象,这些抽象依赖于Kubernetes和Istio的更低层抽象(请注意这将改变--Knative将不再依赖Istio,尽管它需要实现类似的功能)。

这些额外的抽象使Kuberntetes能够以相同的、开放的打包和运行时格式统一支持各种工作负载,包括serverless。

运行时和应用程序设计

随着从单体架构到微服务和serverless的过渡,运行时也在不断发展。如此之多,以至于已有20年历史的Java运行时也逐渐摆脱了“一次编写,到处运行”的口号,从而成为原生、轻量、快速和serverless的先行者。

Java运行时和应用程序设计趋势

多年来,摩尔定律和不断增强的计算能力引导Java使用最先进的垃圾收集器,JIT编译器和许多其他工具,成为最先进的运行时之一。摩尔定律的终结导致Java引入了受益于多处理器和多核系统的非阻塞原语和库。本着同样的精神,随着诸如云原生和serverless之类的平台趋势,分布式系统组件变得轻巧、快速且针对单个任务进行了优化。

Serverless

Serverless范式被认为是下一个基础架构的进化。但是,当前的serverless时代产生在早期采用者和颠覆者中,他们的优先级是速度。但这不是具有复杂业务和技术约束的企业公司的优先考虑事项。在这些组织中,行业标准是容器编排。Kubernetes正在通过Knative计划尝试将云原生和serverless链接起来。其他项目,例如CloudEvents和Substrate VM,也正在影响serverless生态系统,并将其推向一个开放、具备可移植性、互操作性、混合云和全球采用的时代。

对于一些大众所知的技术专业词汇,考虑到翻译成中文反而觉得陌生引起歧义,因此进行保留。为避免一些英文词汇的直译丢失部分原文含义,在括号里加上了英文原词帮助更准确理解。

你可能感兴趣的:(Serverless时代的Kubernetes工作负载:架构、平台和趋势)