Capa: Cloud Application Api

Capa Trip落地

Capa Trip落地.

Capa 落地情况

A、混合云支持

目前支持:

  • Trip私有云
  • AWS公有云

调研开发中:

  • 阿里云

B、应用接入情况

Trip私有云

生产xx+应用,不断接入中

AWS公有云

生产xx+应用,不断接入中

Capa: Mecha SDK of Cloud Application Api

Capa: Mecha SDK of Cloud Application Api.

Capa(Cloud-Application-API):架起混合云应用开发的桥梁

“让代码实现"一次编写,随处运行”。 借助Capa体系,使你的Java应用在改动量较小的情况下,拥有跨云、混合云运行的能力。"

作者简介: KevinTen,携程后端开发工程师,关注Reactive、RPC和云原生领域,对Mecha架构混合云中间件有深度实践经验。 Capa官方GitHub地址:https://github.com/capa-cloud/capa-java

在过去微服务的发展历程中,各大厂商基于SDK模式已经有相对完善的中间件体系。但在业务全球化、混合多云架构场景下,业务应用对基础设施的标准化和解耦、可迁移性以及拥抱开源成为新的诉求。 以当前的业界实践及趋势来看,ServiceMesh 这种 Sidecar 架构与体系是满足上述诉求的最佳实践。

ServiceMesh 在微服务领域已经非常流行,越来越多的公司开始在内部落地,ServiceMesh 带来的业务解耦,平滑升级等优势大大提高了中间件的迭代效率。

不过 ServiceMesh 只解决了服务间通讯的需求,而现实中的分布式应用存在更多的需求。而效仿 ServiceMesh 将应用需要的其他分布式能力外移到各种 Sidecar Runtime,这逐渐演变成了一个趋势。

本文主要对 ServiceMesh 进行回顾总结,并分享业界基于 ServiceMesh 这种 Sidecar 模式解决混合云应用开发的解决方案,最后是关于混合云应用开发模式的探讨。

一、Service Mesh 回顾与总结

A、Service Mesh 的初衷

在微服务架构下,基础架构团队一般会为应用提供一个封装了各种服务治理能力的 SDK,这种做法虽然保障了应用的正常运行,但缺点也非常明显,每次基础架构团队迭代一个新功能都需要业务方参与升级才能使用,尤其是 bugfix 版本,往往需要强推业务方升级,这里面的痛苦程度每一个基础架构团队成员都深有体会。

伴随着升级的困难,随之而来的就是应用使用的 SDK 版本差别非常大,生产环境同时跑着各种版本的 SDK,这种现象又会让新功能的迭代必须考虑各种兼容,就好像带着枷锁前进一般,这样随着不断迭代,会让代码维护非常困难,有些祖传逻辑更是一不小心就会掉坑里。

同时这种“重”SDK 的开发模式,导致异构语言的治理能力非常薄弱,如果想为各种编程语言都提供一个功能完整且能持续迭代的 SDK 其中的成本可想而知。

18 年的时候,Service Mesh 在国内持续火爆,这种架构理念旨在把服务治理能力跟业务解耦,让两者通过进程级别的通信方式进行交互。在这种架构模式下,服务治理能力从应用中剥离,运行在独立的进程中,迭代升级跟业务进程无关,这就可以让各种服务治理能力快速迭代,并且由于升级成本低,因此每个版本都可以全部升级,解决了历史包袱问题,同时 SDK 变“轻”直接降低了异构语言的治理门槛,再也不用为需要给各个语言开发相同服务治理能力的 SDK 头疼了。

B、Service Mesh 落地现状

Istio

参考资料:https://mp.weixin.qq.com/s/xokpWwTItyEW2YN4Iklmww

  • 探索阶段:2017 年 - 2018 年
  • 早期采用者阶段:2019 年 - 2020 年
  • 大规模落地及生态发展阶段:2021 年至今

如果根据 “跨越鸿沟” 理论,服务网格已经跨越了 “鸿沟”,处于 “早期大众” 和 “晚期大众” 阶段之间。根据《Istio 大咖说》 观众中的反馈来看,用户已不再盲从于新技术,开始辩证的考虑 是否真的需要引入服务网格。

Mson

蚂蚁很快意识到了 Service Mesh 的价值,全力投入到这个方向,用 Go 语言开发了 MOSN 这样可以对标 envoy 的优秀数据面,全权负责服务路由,负载均衡,熔断限流等能力的建设,大大加快了公司内部落地 Service Mesh 的进度。

现在 MOSN 在蚂蚁内部已经覆盖了数千个应用、数十万容器,新创建的应用默认接入 MOSN,形成闭环。而且在大家最关心的资源占用、性能损耗方面 MOSN 也交出了一份让人满意的答卷:

  1. RT 小于 0.2ms

  2. CPU 占用增加 0%~2%

  3. 内存消耗增长小于 15M

由于 Service Mesh 降低了异构语言的服务治理门槛,NodeJS、C++等异构技术栈也在持续接入到 MOSN 中。

在看到 RPC 能力 Mesh 化带来的巨大收益之后,蚂蚁内部还把 MQ,Cache,Config 等中间件能力都进行了 Mesh 化改造,下沉到 MOSN,提高了中间件产品整体的迭代效率。

C、新的挑战

Mesh化是云原生落地的关键步骤。

  • 最下方是云,以kubernetes为核心,关于这一点社区基本已经达成共识:kubernetes 就是云原生下的操作系统,屏蔽了不同云的软硬件差异。
  • 在kubernetes之上,是Mesh层。不仅仅有我们熟悉的 ServiceMesh,还有诸如Database Mesh和Message Mesh等类似的其他 Mesh 产品形态,这些Mesh组成了一个标准化的通信层。
  • 运行在各种 Mesh 的应用,不管是微服务形态,还是传统非微服务形态,都可以借助Mesh的帮助实现应用轻量化。非业务逻辑的各种功能被剥离到Mesh中后,应用得以“瘦身减负”。
  • 瘦身之后的应用,其内容主要是业务逻辑实现。这样的工作负载形式,更适合 serverless 的要求,为接下来转型 serverless 做好准备。

X Mesh

参考资料:https://skyao.io/talk/201910-ant-finance-service-mesh-deep-practice/

一个现代分布式应用,往往会同时依赖 RPC、Cache、MQ、Config 等各种分布式能力来完成业务逻辑的处理。

当初看到 RPC 下沉的红利以后,其他各种能力也都快速下沉。

初期,大家都会以自己最熟悉的方式来开发,这就导致没有统一的规划管理,如上图所示,应用依赖了各种基础设施的 SDK,而每种 SDK 又以自己特有的方式跟 MOSN 进行交互,使用的往往都是由原生基础设施提供的私有协议,这直接导致了复杂的中间件能力虽然下沉,但应用本质上还是被绑定到了基础设施。

  1. 中间件抽象不足

比如想把缓存从 Redis 迁移到 Memcache 的话,仍旧需要业务方升级 SDK,这种问题在应用上云的大趋势下表现的更为突出。

  1. 应用跟基础设施强绑定

试想一下,如果一个应用要部署在云上,由于该应用依赖了各种基础设施,势必要先把整个基础设施搬到云上才能让应用顺利部署,这其中的成本可想而知。 因此如何让应用跟基础设施解绑,使其具备可移植能力,能够无感知跨平台部署是我们面临的第一个问题。

  1. 异构语言接入成本高

事实证明 Service Mesh 确实降低了异构语言的接入门槛,但在越来越多的基础能力下沉到 MOSN 以后,我们逐渐意识到为了让应用跟 MOSN 交互,各种 SDK 里都需要对通信协议,序列化协议进行开发,如果再加上需要对各种异构语言都提供相同的功能,那维护难度就会成倍上涨,

Service Mesh 让重 SDK 成为了历史,但对于现在各种编程语言百花齐放、各种应用又强依赖基础设施的场景来说,我们发现现有的 SDK 还不够薄,异构语言接入的门槛还不够低,如何进一步降低异构语言的接入门槛是我们面临的第二个问题。

二、Multi Runtime 理论概述

A、什么是 Runtime?

参考资料:https://skyao.io/talk/202103-dapr-from-servicemesh-to-cloudnative/

20 年初的时候,Bilgin lbryam 发表了一篇名为 Multi-Runtime Microservices Architecture 的文章,里面对微服务架构下一阶段的形态进行了讨论。

如上图所示,作者把分布式服务的需求进行了抽象,总共分为了四大类:

  1. 生命周期(Lifecycle) 主要指应用的编译、打包、部署等事情,在云原生的大趋势下基本被 docker、kubernetes 承包。

  2. 网络(Networking) 可靠的网络是微服务之间进行通信的基本保障,Service Mesh 正是在这方面做了尝试,目前 MOSN、envoy 等流行的数据面的稳定性、实用性都已经得到了充分验证。

  3. 状态(State) 分布式系统需要的服务编排,工作流,分布式单例,调度,幂等性,有状态的错误恢复,缓存等操作都可以统一归为底层的状态管理。

  4. 绑定(Binding) 在分布式系统中,不仅需要跟其他系统通信,还需要集成各种外部系统,因此对于协议转换,多种交互模型、错误恢复流程等功能也都有强依赖。

明确了需求以后,借鉴了 Service Mesh 的思路,作者对分布式服务的架构演进进行了如下总结:

第一阶段就是把各种基础设施能力从应用中剥离解耦,通通变成独立 sidecar 模型伴随着应用一起运行。

第二阶段是把各种 sidecar 提供的能力统一抽象成若干个 Runtime,这样应用从面向基础组件开发就演变成了面向各种分布式能力开发,彻底屏蔽掉了底层实现细节,而且由于是面向能力,除了调用提供各种能力的 API 之外,应用再也不需要依赖各种各样基础设施提供的 SDK 了。

作者的思路跟我们希望解决的问题一致,我们决定使用 Runtime 的理念来解决 Service Mesh 发展到现在所遇到的新问题。

B、Service Mesh vs Runtime

为了让大家对 Runtime 有一个更加清晰的认识,上图针对 Service Mesh 跟 Runtime 两种理念的定位、交互方式、通信协议以及能力丰富度进行了总结,可以看到相比 Service Mesh 而言,Runtime 提供了语义明确、能力丰富的 API,可以让应用跟它的交互变得更加简单直接。

三、Multi Runtime 落地实践

A、dapr

dapr 是社区中一款知名的 Runtime 实现产品,活跃度也比较高。

  1. 提供了多种分布式能力,API 定义清晰,基本能满足一般的使用场景。
  2. 针对各种能力都提供了不同的实现组件,基本涵盖了常用的中间件产品,用户可以根据需要自由选择。

控制平面

和Service Mesh共存

参考资料:https://docs.dapr.io/concepts/service-mesh/#using-dapr-with-a-service-mesh

当考虑如何在已使用Service Mesh公司内部落地 dapr 时,提出了两种方案,如下:

  1. 替换:废弃掉现在的Istio,用 dapr 进行替换,这种方案存在两个问题:

    a. dapr 虽然提供了很多分布式能力,但目前并不具备 Service Mesh 包含的丰富的服务治理能力。

    b. Istio 已经大规模落地,并且经过了考验,直接用 dapr 来替换 Istio 稳定性有待验证。

  2. 共存:新增一个 dapr 容器,跟 Istio 以两个 sidecar 的模式进行部署。这种方案同样存在两个问题:

    a. 引入一个新的 sidecar,我们就需要考虑它配套的升级、监控、注入等等事情,运维成本飙升。

    b. 多维护一个容器意味着多了一层挂掉的风险,这会降低现在的系统可用性。

B、Layotto

同样的,如果你目前正在使用 envoy 作为数据面,也会面临上述问题。

因此我们希望把 Runtime 跟 Service Mesh 两者结合起来,通过一个完整的 sidecar 进行部署,在保证稳定性、运维成本不变的前提下,最大程度复用现有的各种 Mesh 能力。此外我们还希望这部分 Runtime 能力除了跟 Istio 结合起来之外,未来也可以跟 envoy 结合起来,解决更多场景中的问题,Layotto 就是在这样的背景下诞生。

如上图所示,Layotto 是构建在 MOSN 之上,在下层对接了各种基础设施,向上层应用提供了统一的,具有各种各样分布式能力的标准 API。对于接入 Layotto 的应用来说,开发者不再需要关心底层各种组件的实现差异,只需要关注应用需要什么样的能力,然后调用对应能力的 API 即可,这样可以彻底跟底层基础设施解绑。

对应用来说,交互分为两块,一个是作为 gRPC Client 调用 Layotto 的标准 API,一个是作为 gRPC Server 来实现 Layotto 的回调,得利于gRPC 优秀的跨语言支持能力,应用不再需要关心通信、序列化等细节问题,进一步降低了异构技术栈的使用门槛。

除了面向应用,Layotto 也向运维平台提供了统一的接口,这些接口可以把应用跟 sidecar 的运行状态反馈给运维平台,方便 SRE 同学及时了解应用的运行状态并针对不同状态做出不同的举措,该功能考虑到跟 k8s 等已有的平台集成,因此我们提供了 HTTP 协议的访问方式。

除了 Layotto 本身设计以外,项目还涉及两块标准化建设,首先想要制定一套语义明确,适用场景广泛的 API 并不是一件容易的事情,为此我们跟阿里、 dapr 社区进行了合作,希望能够推进 Runtime API 标准化的建设,其次对于 dapr 社区已经实现的各种能力的 Components 来说,我们的原则是优先复用、其次开发,尽量不把精力浪费在已有的组件上面,重复造轮子。

最后 Layotto 目前虽然是构建在 MOSN 之上,未来我们希望 Layotto 可以跑在 envoy 上,这样只要应用接入了 Service Mesh,无论数据面使用的是 MOSN 还是 envoy,都可以在上面增加 Runtime能力。

C、Capa

Capa主要为了解决携程混合云部署开发的问题,主要面向Java技术栈。 github: https://github.com/capa-cloud/capa-java

Capa项目基于Mecha架构的设计理念,使用 富SDK模式 提供Multi-Runtime的标准API。

您可以简单的将Capa项目理解为 Dapr / Layotto 等Sidecar模式项目的SDK实现版本。

Sidecar or SDK

基于Mecha架构理念的Multi-Runtime,以Sidecar的方式提供标准API的功能,看起来似乎是最合理的选择。

那为什么不直接使用Dapr/Layotto等项目,而是选择开发 富SDK模式 的Capa项目呢。

概括:以Dapr为代表的Sidecar架构是未来,但现有的很多企业和系统很难一步到位的升级到Sidecar架构,富SDK架构将会长期的存在下去。

  • sidecar的开发和运维成本高。
  • sidecar中实现自定义逻辑的复杂度高。
  • 当前sidecar模式还不足够成熟,无法短时间内完成开发和部署。

引申:面对庞大的Java系统体系,Capa项目将使用富SDK模型支持Java系统向Mecha架构过渡。在Dapr等项目成熟后,也可以无缝衔接到Sidecar架构。

能力水平模型

选用sidecar来进行中间件支持,存在以下几个问题:

  • 在过去我们积累了丰富的Java SDK,全盘舍弃意味着巨大的损失。
  • 社区还比较年轻,短时间内用golang重写所有SDK逻辑不太现实。
  • 社区对公有云功能支持比较好,私有云主要依赖SDK模式。

img.png

SDK模式的缺点

  • 跨语言开发和维护成本高。无法支撑多语言战略,团队没有精力维护除了 Java 以外其他语言的 SDK;
  • SDK升级侵入性强。客户端 SDK 版本升级推进困难,特别是遇到 Bug 的时候,彻底下线一个版本可能会花上几个月的时间,给业务带来了隐患;
  • 依赖冲突
  • ……

Capa SDK设计

参考资料:https://github.com/dapr/dapr/issues/3261

设计思路:标准API + 可拔插可替换的SDK组件 模式

在不同的分布式中间件领域,Capa提供与具体中间件API无关的统一的标准编程API。 所以应用程序在使用Capa编程时不需要依赖任何具体的中间件API,只需要依赖Capa的标准编程API即可。

例如:

Mono<Void> result = client.publishEvent("mytopic", "my message");

在部署到不同的目标环境时,Capa将会装载标准API的不同实现类到应用程序中。当调用统一的编程API时,底层运行时会适配到不同的具体中间件SDK实现。

中间件团队需要针对不同目标环境,开发标准API在目标环境下的实现类即可;而应用代码可以拥有"一次编写,随处运行"的开发体验。

示例:Capa-java + Maven

核心实现思想就是:面向接口编程 + 通过JavaSPI动态加载实现类

capa-sdk

Maven配置伪代码示例:

<!-- 编程时直接引入API层 -->
<dependency>
    <groupId>group.rxcloud</groupId>
    <artifactId>capa-sdk</artifactId>
</dependency>

<profiles>
    <!-- AWS云实现类 -->
    <profile>aws</profile>
    <dependencys>
        <dependency>
            <groupId>group.rxcloud</groupId>
            <artifactId>capa-sdk-aws</artifactId>
        </dependency>  
    </dependencys>

    <!-- Ali云实现类 -->
    <profile>ali</profile>
    <dependencys>
        <dependency>
            <groupId>group.rxcloud</groupId>
            <artifactId>capa-sdk-ali</artifactId>
        </dependency>
    </dependencys>
</profiles>

开发人员编程时,应用程序直接调用API层的接口进行编程。

在打包成镜像的过程中,通过maven profile加载不同云平台的jar实现。

在运行时,SDK通过Java SPI机制,动态加载API层接口的实现类。 该实现类,即为各个云平台上不同的SDK实现。

D、Femas / OpenSergo / Micro / Other / …

参考资料:https://www.yuque.com/docs/share/5221c27a-9d0c-44c7-8ef2-0956d3b09a32?#

E、移植性

如上图所示,一旦完成 Runtime API 的标准化建设,接入 Mecha 的应用天然具备了可移植性,应用不需要任何改造就可以在私有云以及各种公有云上部署,并且由于使用的是标准 API,应用也可以无需任何改造就在 Layotto / dapr / Capa 之间自由切换。

未来迁移

SDK模式的优点在于开发和接入的成本较低,可以快速拥有混合云部署开发的能力。但缺点也是无法忽视的,当随着Mecha Runtime的发展,未来SDK模式很可能将会迁移到更有技术优势的Mecha Runtime Sidecar模式中。

但也要考虑到性能敏感领域,例如分布式缓存领域。相关模块能接受额外 1ms 的响应延迟吗?Sidecar 带来的优势能否弥补这个问题? 所以in-process(SDK)模型在这些领域仍具备不可替代的优势。

推动API的标准化建设:使各大Mecha体系保持API层的统一,保留未来互相迁移的可能。

从未来几年来看:

  • 在较小的私有云,会长期支持SDK模式。
  • 在较大的私有云,框架团队有能力完成大规模sidecar化改造,会从SDK模式迁移到Sidecar模式。
  • 在公有云,目前可以选用SDK模式/Sidecar模式,SDK模式更有利于二次定制;
  • 随着社区在公有云能力的发展,以及云厂商的支持,未来可全部切换到sidecar模式。
  • 对于异构语言架构,可对主要语种采用sdk模式(功能更丰富),小语种采用sidecar模式。

四、API设计原则

就像SQL标准之于关系型数据库。

参考资料:https://www.infoq.cn/article/wjkNGoGaaHyKs7xIyTSB

A、功能集选择

组件提供的能力不平齐

解决思路一:Runtime 弥补组件缺失能力(sidecar)

解决思路二:Component 弥补组件缺失能力

Capa示例:Configuration

在私有云的Configuraion-java-sdk(如apollo/qconfig)中,sdk本身支持监听配置变化。

但在AWS AppConfig java sdk中,本身并不支持监听配置变化。

那我们认为对于configuraion中间件领域而言,监听配置变化是level0的功能,是必须具有的功能。

所以需要在component-sdk中对其进行弥补:

  • sdk中通过定时线程+轮询接口的方式,获取最新的配置变化。

以此,使用户接入时,认为具有这项能力。

解决思路三:无法弥补,但可以模糊处理

能力分层模型:A云能够实现,B云通过其他方式也能实现。

解决思路四:无法弥补又不能模糊处理

高危,按条件选用。 明确告知在特定云上才有这项能力。

社区讨论:协商机制

B、分层模型

C、拓展字段

高度定制化。不建议用户直接使用。

若要用,则使用定义好的有限枚举。 SDK实现中对该枚举进行支持。

E、配置原语

参考资料:https://github.com/dapr/dapr/issues/2988

首先是分布式系统中经常使用的配置功能,应用一般使用配置中心来做开关或者动态调整应用的运行状态。Layotto 中配置模块的实现包括两部分,一个是对如何定义配置这种能力的 API 的思考,一个是具体的实现,下面逐个来看。

想要定义一个能满足大部分实际生产诉求的配置 API 并不是一件容易的事,dapr 目前也缺失这个能力,因此我们跟阿里以及 dapr 社区一起合作,为如何定义一版合理的配置 API 进行了激烈讨论。

目前讨论结果还没有最终确定,因此 Layotto 是基于我们提给社区的第一版草案进行实现,下面对我们的草案进行简要说明。

我们先定义了一般配置所需的基本元素:

  1. appId:表示配置属于哪个应用

  2. key:配置的 key

  3. content:配置的值

  4. group:配置所属的分组,如果一个 appId 下面的配置过多,我们可以给这些配置进行分组归类,便于维护。

此外我们追加了两种高级特性,用来适配更加复杂的配置使用场景:

  1. label,用于给配置打标签,比如该配置属于哪个环境,在进行配置查询的时候,我们会使用 label + key 来查询配置。

  2. tags,用户给配置追加的一些附加信息,如描述信息、创建者信息,最后修改时间等等,方便配置的管理,审计等。

对于上述定义的配置 API 的具体实现,目前支持查询、订阅、删除、创建、修改五种操作,其中订阅配置变更后的推送使用的是 gRPC 的 stream 特性,而底层实现这些配置能力的组件,我们选择了国内流行的 apollo,后面也会根据需求增加其他实现。

level1

level2

levelx…

metadata

G、RPC 原语

RPC 的能力大家不会陌生,这可能是微服务架构下最最基础的需求,对于 RPC 接口的定义,我们同样参考了 dapr 社区的定义,发现完全可以满足我们的需求,因此接口定义就直接复用 dapr 的,但目前 dapr 提供的 RPC 实现方案还比较薄弱,而 MOSN 经过多年迭代,能力已经非常成熟完善,因此我们大胆把 Runtime 跟 Service Mesh 两种思路结合在一起,把 MOSN 本身作为我们实现 RPC 能力的一个 Component,这样 Layotto 在收到 RPC 请求以后交给 MOSN 进行实际数据传输,这种方案可以通过 istio 动态改变路由规则,降级限流等等设置,相当于直接复用了 Service Mesh 的各种能力,这也说明 Runtime 不是要推翻 Service Mesh,而是要在此基础上继续向前迈一步。

五、Capa 实践

A、适配迁移之痛

如果要使用原生的Capa API,您的遗留系统需要面对较大的重构工作量。

为了使迁移做到低成本,我们可以复用目前使用到的中间件API。

通过开发一个适配层项目(提供相同的注解/接口调用方式),将原中间件API的实现更改为Capa API。

如此一来,应用程序只需要更改很少的代码(例如更换注解/接口的路径名)即可迁移到Capa架构。

目前在Java语言上,Capa可以支持:

  • 接入(携程)私有云SDK的适配层
  • 接入spring boot体系的适配层

接入适配模式的改动范围

  1. 替换中间件依赖
<dependency>
    <groupId>com.ctrip.framework.apollo</groupId>
    <artifactId>apollo-client</artifactId>
</dependency>

to

<dependency>
    <groupId>group.rxcloud</groupId>
    <artifactId>capa-sdk-configuration</artifactId>
</dependency>
  1. 替换相关代码(import路径+注解名)
import com.ctrip.framework.apollo.Config;

@Config("testjson.json")
private Person person;

to

import group.rxcloud.capa.config.CloudConfig;

@CloudConfig("testjson.json")
private Person person;

Q: 为什么不保持import路径和使用方式完全一致?

A: 当运行在特定云上时,需要加载对应的sdk进来;这时如果有类路径完全一致的类存在,会导致类加载冲突。

B、Capa API设计

Capa(Java SDK)是面向Java应用实现Mecha架构的SDK解决方案,它目前支持以下领域:

  • Service Invocation (RPC服务调用)
  • Configuration Centor (Configuration动态配置)
  • Publish/Subscribe (Pub/Sub发布订阅)
  • State Management (State状态管理)
  • Application Log/Metrics/Traces (Telemetry可观测性)
  • Redis (Redis高度定制化存储) -beta
  • Database (SQL关系型数据库) -alpha
  • Schedule (Schedule定时调度) -alpha

完全复用的标准API

  • Service Invocation (RPC服务调用)
  • Configuration Centor (Configuration动态配置)
  • Publish/Subscribe (Pub/Sub发布订阅)

补充了的标准API

RPC

标准API中只涵盖了作为Client调用其他服务的API。

但不同云上服务注册的框架弈有所不同(例如:dubbo/spring cloud/service mesh)。

在sidecar模式中,sidecar本身可类似service mesh一样承担服务注册的能力,通过callback API调用服务接口,但SDK模式时却不行。

所以添加了作为服务端的API:

    // -- Runtime as Server

    /**
     * Register onInvoke method when runtime as server.
     *
     * @param <T>            The Type of the request type, use byte[] to skip serialization.
     * @param <R>            The Type of the response type, use byte[] to skip serialization.
     * @param methodName     The actual Method to be call in the application.
     * @param httpExtensions Additional fields that are needed if the receiving app is listening on                       HTTP, {@link HttpExtension#NONE} otherwise.
     * @param onInvoke       the on invoke
     * @param metadata       Metadata (in GRPC) or headers (in HTTP) to be received in request.
     * @return A Mono Plan of register result.
     */
    <T, R> Mono<Boolean> registerMethod(String methodName, List<HttpExtension> httpExtensions,
                                        Function<T, R> onInvoke,
                                        Map<String, String> metadata);

    /**
     * Register controller class when runtime as server.
     *
     * @param registerServerRequest the register server request
     * @return A Mono Plan of register result.
     */
    Mono<Boolean> registerServer(RegisterServerRequest registerServerRequest);

PubSub

标准API中定义了Pub发布消息的API,而Sub订阅消息则通过callback进行定义。

sidecar通过callback回调触发Sub订阅,但对于SDK模式而言,并没有额外的进程可触发callback逻辑。

所以添加了消息订阅的API:

    // -- Runtime as Subscriber

    /**
     * Subscribe events.
     *
     * @param pubsubName the pubsub name we will subscribe the event from.
     * @param topicName  the topicName where the event will be subscribed.
     * @param metadata   The metadata for the subscription.
     * @return a Flux stream of subscription events.
     */
    Flux<TopicEventRequest> subscribeEvents(String pubsubName, String topicName, Map<String, String> metadata);

    /**
     * Subscribe events.
     *
     * @param topicSubscription the request for topic subscription.
     * @return a Flux stream of subscription events.
     */
    Flux<TopicEventRequest> subscribeEvents(TopicSubscription topicSubscription);

未使用的标准API

State Management (State状态管理)

目前KV主要使用的Redis。

而标准State API,可表达的语义较弱,无法cover redis场景。

所以暂时未使用。

Distributed Lock API (分布式锁)

规划中。

但对于跨云环境而言,很难做到全局锁,只能做到Region/云级别的锁。

Sequencer API (全局UUID)

目前在java中,直接使用本地进程内算法实现。

File API (文件系统)

规划中。

后续应该会做,因为不同云上的文件系统使用方式不同。

Secret API (密钥存储)

规划中。

后续可能会做,因为不同云上密钥存储使用方式不同。

但目前密钥存储还未暴露给用户使用,而只是中间件在使用,所以中间件中直接引用了对应云的SDK来实现。

自定义的非标准API

目前的 dapr api 并不能在保持可移植性的同时满足现实世界中应用程序的所有需求。

参考:https://github.com/mosn/layotto/issues/530

DB SQL

对于SQL而言:

  • SQL协议已经十分成熟
  • 将SQL抽象为一套API,面临的挑战很多

故Capa目前仅提供了一套待讨论的SQL API: dapr【提案】数据库API设计 #3354

而在落地实践过程中,SQL关系型数据库领域采用的方案是 DAL 数据库连接技术,经二次开发支持混合云SDK模式。

Redis

目前的State API,无法支持复杂的Redis语义表达。 故Capa在Redis领域,基于 Jedis 定义了一套 Redis API

Telemetry (Log/Metric/Trace)

场景:中间件SDK/应用程序 需要记录指标埋点。

之前,我们可以通过 CAT 这类应用监控告警平台进行实现。

Capa复用了 OpenTelemetry 的API,来实现监控告警/指标收集的功能。

Schedule

场景:触发job调度任务(规划中)

/**
 * Schedule Bindings Runtimes standard API defined.
 */
public interface ScheduleRuntimes {

    /**
     * Invokes a Schedule Binding operation.
     *
     * @param appId    the app id
     * @param jobName  the job name
     * @param metadata the metadata
     * @return the job flux stream
     */
    Flux<Object> invokeSchedule(String appId, String jobName, Map<String, String> metadata);
}

C、云原生技术栈选型

Capa SDK本身不具有丰富的功能,更像是一个包装适配层,具体功能由下层的具体中间件来实现。

领域云厂商技术选型实践经验
RPCTripTrip SOA
AWSAWS AppMesh重试、熔断、超时等全部从SDK中下沉到Mesh层处理
ConfigurationTripApollo / QConfig
AWSAWS AppConfig功能较为简陋,需要在SDK中进行弥补
MQTripQMQ
AWSAWS MSK Kafka缺少如延时消息等功能,需要借助周边系统进行弥补
RedisTripCRedis
AWSAWS ElasticCache
Log/Metric/TraceTripCAT
AWSAWS CloudWatchOpenTelemetryAPI对接CloudWatch SDK
DB(SQL)TripDAL非Mecha架构
AWSDAL + AWS RDS非Mecha架构
ScheduleTripQSchedule
AWSK8S cronJob

D、个性化的配置和逻辑

  1. 应用程序可以覆写 配置 ,实现自定义
  2. 应用程序可以覆写 插件 ,实现自定义

自定义配置类似layotto:layotto 配置下发通道与配置热加载 #500

特定云的配置

集成在对应云的SDK中。

举个例子:layotto 解耦API跟具体实现 #513

如果用户在调用API时明确指定了,使用redis作为store_name,那么对于没有部署redis的云平台,则无法运行。

所以对于redis这种与基础设施有关的选择,capa并不暴露给用户:

  • store_name,存放在sdk的配置文件中。
  • 用户通过一个接口方法(比如getStoreName()),获取该name,并透传给API调用。

主要也是因为,capa当前都是1:1的情况,目前不存在1个领域有多种实现。

后续如果有这种情况,考虑使用configuration配置功能进行映射:

  1. 定义一个configuration配置,key1=redis, key2=mongo
  2. 用户发起了两次调用,两次调用分别为例如key1=1, key2=2
  3. 则getStoreName(key1)=redis,getStoreName(key1)=mongodb,默认=redis
  4. 用户将此store_name透传给API调用

应用个性化的配置和逻辑

SDK中的一些逻辑是可替换的,实现上通过面向Java接口编程,在SDK中提供了接口的默认实现。

应用程序可以通过覆盖接口实现,从而实现自定义逻辑。

E、跨云交互

一方面在进行混合云改造的过程中,面临过渡期的问题。 当一部分的服务和数据在原云平台上时,新云平台将面临和原云平台进行交互的问题。

另一方面作为混合云架构,若要发挥每个云各自的优势,有可能不同服务会部署在不同的云平台上,这时也会面对跨云交互的问题。

而要实现跨云交互,主要由以下两个思路:

1. 由Mecha Runtime解决跨云交互问题

Mecha Runtime/SDK感知混合云,并实现跨云交互。

但可能实现起来比较复杂,设计到跨云的认证、网络打通等。

2. (Capa)由外部插件/周边系统,完成跨云交互部分的功能

Mecha Runtime/SDK将会比较轻量,不需要感知其他云。 涉及到某个领域的跨云交互问题,交由该领域的 基础设施/周边系统 完成。

Capa相比于layotto等sidecar模型,采用SDK模式更不易引入较复杂逻辑,会导致SDK过于臃肿,且无法多语言复用。 故Capa架构中,所有跨云交互问题,都交由具体领域的周边系统完成,Capa只关注于当前云的配置和使用。

RPC服务调用跨云

跨云服务调用的前提是,网络层面流量已经打通。 例如在AWS上,通过PrivateLink技术进行网络流量打通。

之后在服务调用中,依赖网络基础的流量转发实现:

RPC网络基础设施跨云交互能力
K8SExternalService转发
IstioServiceEntry转发
Dubbo/SpringCloud/Trip SOAMockService转发
AWS AppMeshVirtualNode DNS转发
不支持?搭建Nginx代理实现转发

MQ跨云

使用周边系统:MQ同步工具,进行跨云消息传输。

按需申请,消息格式转换,失败重试。

数据跨云

DB数据使用周边系统:MySQL同步工具 ,进行跨云数据传输。

Redis数据目前不做跨云传输。

Configuration等

使用周边系统:XX同步工具,进行跨云的数据同步。

六、高阶拓展

A、WebAssembly 的探索

WebAssembly,简称 WASM,是一个二进制指令集,最初是跑在浏览器上来解决 JavaScript 的性能问题,但由于它良好的安全性,隔离性以及语言无关性等优秀特性,很快人们便开始让它跑在浏览器之外的地方,随着 WASI 定义的出现,只需要一个 WASM 运行时,就可以让 WASM 文件随处执行。

既然 WebAssembly 可以在浏览器以外的地方运行,那么我们是否能把它用在 Serverless 领域?目前已经有人在这方面做了一些尝试,不过如果这种方案真的想落地的话,首先要考虑的就是如何解决运行中的 WebAssembly 对各种基础设施的依赖问题。

WebAssembly 落地原理

目前 MOSN 通过集成 WASM Runtime 的方式让 WASM 跑在 MOSN 上面,以此来满足对 MOSN 做自定义扩展的需求。同时,Layotto 也是构建在 MOSN 之上,因此我们考虑把二者结合在一起,实现方案如下图所示:

开发者可以使用 Go/C++/Rust 等各种各样自己喜欢的语言来开发应用代码,然后把它们编译成 WASM 文件跑在 MOSN 上面,当 WASM 形态的应用在处理请求的过程中需要依赖各种分布式能力时就可以通过本地函数调用的方式调用 Layotto 提供的标准 API,这样直接解决了 WASM 形态应用的依赖问题。

目前 Layotto 提供了 Go 跟 Rust 版 WASM 的实现,虽然只支持 demo 级功能,但已经足够让我们看到这种方案的潜在价值。

此外,WASM 社区目前还处于初期阶段,有很多地方需要完善,我们也给社区提交了一些 PR共同建设,为 WASM 技术的落地添砖加瓦。

WebAssembly 落地展望

虽然现在 Layotto 中对 WASM 的使用还处于试验阶段,但我们希望它最终可以成为 Serverless 的一种实现形态,如上图所示,应用通过各种编程语言开发,然后统一编译成 WASM 文件,最后跑在 Layotto+MOSN 上面,而对于应用的运维管理统一由 k8s、docker、prometheus 等产品负责。

  1. 用户实现自定义的逻辑

对于中间件领域,可以由社区定义一套执行模板,而用户可以灵活的插入自定义的回调逻辑,更具有动态扩展性。

  1. 隔离中间件运行时

Runtime启动多个相互隔离的 WASM Runtime ,从而隔离每个中间件组件的运行时(CPU/内存/…),避免异常的扩散和传播,互不影响。

Istio Wasm

Istio 的扩展机制使用 Proxy-Wasm 应用二进制接口(ABI)规范,提供了一套代理无关的流媒体 API 和实用功能,可以用任何有合适 SDK 的语言来实现。

扩展 Istio 的功能,满足你的特定需求,需要三个步骤:

  1. 在 Golang 中实现你的插件功能。
  2. 编译、构建,并将 Wasm 模块推送到符合 OCI 标准的 Docker 镜像仓库。
  3. 使用 WasmPlugin 资源配置服务网格工作负载,以便从远程镜像仓库中拉取 Wasm 模块。

参考资料:https://mp.weixin.qq.com/s/VAwTCIP6RhqDNoBbmE8zVA

Wasm 展望

  1. 支持网络接口
  2. 对go-sdk有更好的支持,现在以c/rust为主。

B、eBPF 的探索

参考资料:https://mp.weixin.qq.com/s/W9NySdKnxuQ6S917QQn3PA

应用场景: sidecar加速

socket重定向,绕过TCP/IP协议栈,

七、社区规划

最后来看下社区的做的一些事情。

参考资料:https://zhuanlan.zhihu.com/p/435012312?utm_source=wechat_session&utm_medium=social&utm_oi=618742049890111488&utm_content=group2_article&utm_campaign=shareopn

A、建设更多的API标准

上图列出了 Layotto 跟 dapr 现有的能力对比,在 Layotto 的开发过程中,我们借鉴 dapr 的思路,始终以优先复用、其次开发为原则,旨在达成共建的目标,而对于正在建设或者未来要建设的能力来说,我们计划优先在 Layotto 上落地,然后再提给社区,合并到标准 API,鉴于社区异步协作的机制,沟通成本较高,因此短期内可能 Layotto 的 API 会先于社区,但长期来看一定会统一。

  • 分布式锁API
  • 文件系统API
  • 延迟消息API
  • 密钥管理API

B、API 标准化建设

关于如何定义一套标准的 API 以及如何让 Layotto 可以跑在 envoy 上等等事项,我们已经在各个社区进行了深入讨论,并且以后也还会继续推进。

对于 API 标准化的建设是一件需要长期推动的事情,同时标准化意味着不是满足一两种场景,而是尽可能的适配大多数使用场景。

C、可扩展API架构

参考OS领域当年是怎么定API的,我们可以把Runtime API设计成多层:

  • 社区标准API

  • 企业内部的私有API

  • 基础设施特定API

分别对应OS领域的:

  • POSIX API

  • 各种Unix-like系统自己的System Call

  • 特殊硬件提供的特殊功能

基于这种思想,尝试让Runtime支持API插件,让用户自己实现自己的私有API

D、直接支持行业标准协议

  • 直接支持SQL

  • 直接支持Redis

需要进一步讨论。

E、开源协同

各个Mecha项目积极交流,合作共建,互为补充。

八、云厂商的支持

A、阿里云在内部的落地

参考资料:https://blog.dapr.io/posts/2021/03/19/how-alibaba-is-using-dapr/

微服务架构、Faas 与 多语言应用

微服务架构分层逐渐形成,后端 BaaS 化,客户端轻量化,业务侧 Serverless 化,让业务更加聚焦业务开发,进一步提升研发效率。

FaaS 对多语言支持的需求强烈,并不局限于 Java。但实际上,由于成本和时间限制,用所有语言重新开发客户是不切实际的。

借助 Dapr,阿里巴巴解决了 FaaS 的多语言问题,帮助客户通过 FaaS 提高开发效率。

Are you using Dapr? #3169

适配基于Java的遗留系统

我们旨在消除业务开发团队进行代码更改以减少迁移期间对业务应用程序的影响的需要。在将这些遗留系统迁移到 Dapr 时,我们设计了一个 Java 适配层,它将原始 Java 调用适配到 Dapr 客户端 API。

混合云:云到云迁移

Dapr 的可移植性将钉钉文档的上层应用与消息系统等底层基础设施解耦。从而,钉钉文档实现了不同云平台之间的平滑迁移。

B、蚂蚁集团Mesh化进展

参考文章:https://mp.weixin.qq.com/s/eh0Jz7YeF27bXqdBMd4MPw

1、SOA 时代

中间件的客户端,均直接集成在业务进程内:

2、Mesh 化阶段

中间件能力下沉,应用和基础设施实现部分解耦:

3.应用运行时阶段

将应用和具体基础设施的类型解耦,仅依赖标准 API 编程:

C、微软Azure的托管支持

微软在 Ignite 2021 大会上发布了预览版的Azure Container Apps,这是一个完全托管的无服务器容器运行时,用于大规模构建和运行现代应用程序。

  • 利用分布式应用运行时 Dapr 轻松构建和连接微服务。 Dapr允许你构建松耦合的微服务应用程序,而不是构建一个分布式单体应用,在Azure Container Apps里我,利用 Dapr 及其组件构建具有弹性、可扩展性和松耦合的应用程序

参考资料:https://www.cnblogs.com/shanyou/p/15509042.html

D、Dapr/Layotto 加入 CNCF 孵化器

CNCF 托管了许多与 Dapr 紧密结合的项目。例如,Dapr 使用 gRPC 作为它的主要 RPC 堆栈。OpenTelemetry 用作 Dapr 的遥测协议,CloudEvents 用于标准化 Dapr 的 Pub/Sub API 中的消息格式,SPIFFE 用作 Dapr 的访问列表特性的身份格式,Prometheus 用作 Dapr 端点的指标格式,Dapr 支持 NATS 的 Pub/Sub 实现以及 Open Policy Agent。此外,Dapr 使用 Operator 在 Kubernetes 上原生运行,并使用 Helm 作为主要部署机制。

参考资料:https://mp.weixin.qq.com/s/8qnmL12JrESwJg3RsSelJQ


主要参考文献

References and Thinking

Sidecar设计模式 - 参考Istio Sidecar

Sidecar设计模式: 参考Istio Sidecar.

参考文档:https://www.servicemesher.com/istio-handbook/concepts/sidecar-injection.html

一、Sidecar模式介绍

A、Sidecar模式是什么

Sidecar模式是一种单节点、多容器的应用设计形式。Sidecar主张以额外的容器来扩展或增强主容器,而这个额外的容器被称为Sidecar容器

sidecar 模式也符合当前微服务的以下特点:

  • 隔离(separation of concerns):让每个容器环境不需要相互依赖而独立运行,也就意味着sidecar程序可以和任何语言的应用服务一起运行。
  • 单一责任原则(single responsibility principle),各个容器负责自己的处理逻辑,各司其职
  • 内聚性/可重用性(Cohesiveness/Reusability)

在软件架构中, Sidecar 连接到父应用并且为其添加扩展或者增强功能。Sidecar 应用与主应用程序松散耦合。它可以屏蔽不同编程语言的差异,统一实现微服务的可观察性、监控、日志记录、配置、断路器等功能。

使用 Sidecar 模式的优势

使用 sidecar 模式部署服务网格时,无需在节点上运行代理,但是集群中将运行多个相同的 sidecar 副本。在 sidecar 部署方式中,每个应用的容器旁都会部署一个伴生容器(如 Envoy 或 MOSN),这个容器称之为 sidecar 容器。Sidecar 接管进出应用容器的所有流量。在 Kubernetes 的 Pod 中,在原有的应用容器旁边注入一个 Sidecar 容器,两个容器共享存储、网络等资源,可以广义的将这个包含了 sidecar 容器的 Pod 理解为一台主机,两个容器共享主机资源。

因其独特的部署结构,使得 sidecar 模式具有以下优势:

  • 将与应用业务逻辑无关的功能抽象到共同基础设施,降低了微服务代码的复杂度。
  • 因为不再需要编写相同的第三方组件配置文件和代码,所以能够降低微服务架构中的代码重复度。 Sidecar 可独立升级,降低应用程序代码和底层平台的耦合度。

借助于K8S良好的可拓展性,使用sidecar模式可以享受到分布式系统中的规模化效率红利。相比于这种效率提升,我们可以容许性能上的开销。

要不要使用sidecar模式

  • 在设计sidecar服务时,请慎重决定进程间通信机制。除非达不到性能要求,否则请尽量使用不区分语言或框架的技术。
    • tcp
    • http
    • grpc
  • 在将功能放入sidecar之前,请考虑该功能是作为独立的服务还是更传统的守护程序运行更有利。
    • 进程内 or 跨进程
  • 此外,请考虑是否能够以库的形式或使用传统扩展机制实现功能.特定于语言的库可能提供更深度的集成和更少的网络开销。
    • 跨语言
    • 升级频率
    • 性能敏感度

B、Sidecar模式的模型

Pod容器级

Pod容器+Node节点级

相关文档:https://www.infoq.cn/news/58j970hvYSx2QSEvCESh

Node节点级

DaemonSet


二、Sidecar集成

怎么实现一个sidecar

sidecar的代码很简单,与写一个webserver类似。

但与 vm/k8s 的自动化集成比较复杂。

func main() {
	if err := rootCmd.Execute(); err != nil {
		log.Error(err)
		os.Exit(-1)
	}
}

1. Sidecar自动化集成

A、Sidecar VM

手动/脚本 操作:

  1. 创建虚拟机
  2. 设置环境变量
  3. 安装依赖包
  4. 上传脚本到虚拟机
  5. 命令行启动多个可执行文件

B、Sidecar docker

借助 docker-compose.yml 文件,开发人员可定义一组相关服务,通过部署命令将其部署为组合应用程序。 它还配置其依赖项关系和运行时配置。多个容器使用同一个network。

  1. dockerfile -> image
  2. images -> docker-compose
  3. docker-compose -> new image

istio dockerfile:

# BASE_DISTRIBUTION is used to switch between the old base distribution and distroless base images
ARG BASE_DISTRIBUTION=debug

# Version is the base image version from the TLD Makefile
ARG BASE_VERSION=latest

# The following section is used as base image if BASE_DISTRIBUTION=debug
FROM gcr.io/istio-release/base:${BASE_VERSION} as debug

# The following section is used as base image if BASE_DISTRIBUTION=distroless
# This image is a custom built debian11 distroless image with multiarchitecture support.
# It is built on the base distroless image, with iptables binary and libraries added
# The source can be found at https://github.com/istio/distroless/tree/iptables
# This version is from commit 105e1319a176a5156205b9e351b4e2016363f00d.
FROM gcr.io/istio-release/iptables@sha256:bae9287d64be13179b7bc794ec3db26bd5c5fe3fb591c484992366314c9a7d3d as distroless

# This will build the final image based on either debug or distroless from above
# hadolint ignore=DL3006
FROM ${BASE_DISTRIBUTION:-debug}

WORKDIR /

ARG proxy_version
ARG istio_version
ARG SIDECAR=envoy

# Copy Envoy bootstrap templates used by pilot-agent
COPY envoy_bootstrap.json /var/lib/istio/envoy/envoy_bootstrap_tmpl.json
COPY gcp_envoy_bootstrap.json /var/lib/istio/envoy/gcp_envoy_bootstrap_tmpl.json

# Install Envoy.
ARG TARGETARCH
COPY ${TARGETARCH:-amd64}/${SIDECAR} /usr/local/bin/${SIDECAR}

# Environment variable indicating the exact proxy sha - for debugging or version-specific configs 
ENV ISTIO_META_ISTIO_PROXY_SHA $proxy_version
# Environment variable indicating the exact build, for debugging
ENV ISTIO_META_ISTIO_VERSION $istio_version

ARG TARGETARCH
COPY ${TARGETARCH:-amd64}/pilot-agent /usr/local/bin/pilot-agent

COPY stats-filter.wasm /etc/istio/extensions/stats-filter.wasm
COPY stats-filter.compiled.wasm /etc/istio/extensions/stats-filter.compiled.wasm
COPY metadata-exchange-filter.wasm /etc/istio/extensions/metadata-exchange-filter.wasm
COPY metadata-exchange-filter.compiled.wasm /etc/istio/extensions/metadata-exchange-filter.compiled.wasm

# The pilot-agent will bootstrap Envoy.
ENTRYPOINT ["/usr/local/bin/pilot-agent"]

docker-compose:

version: '3.4'

services:

  webmvc:
    image: eshop/web
    environment:
      - CatalogUrl=http://catalog-api
      - OrderingUrl=http://ordering-api
    ports:
      - "80:80"
    depends_on:
      - catalog-api
      - ordering-api

  catalog-api:
    image: eshop/catalog-api
    environment:
      - ConnectionString=Server=sqldata;Port=1433;Database=CatalogDB;…
    ports:
      - "81:80"
    depends_on:
      - sqldata

  ordering-api:
    image: eshop/ordering-api
    environment:
      - ConnectionString=Server=sqldata;Database=OrderingDb;…
    ports:
      - "82:80"
    extra_hosts:
      - "CESARDLBOOKVHD:10.0.75.1"
    depends_on:
      - sqldata

  sqldata:
    image: mcr.microsoft.com/mssql/server:latest
    environment:
      - SA_PASSWORD=Pass@word
      - ACCEPT_EULA=Y
    ports:
      - "5433:1433"

C、基于 docker 进行 k8s 注入

Kompose是个转换工具,可将 compose(即 Docker Compose)所组装的所有内容 转换成容器编排器(Kubernetes 或 OpenShift)可识别的形式。

要将 docker-compose.yml 转换为 kubectl 可用的文件,请运行 kompose convert 命令进行转换,然后运行 kubectl create -f 进行创建。

D、k8s 控制面进行注入

k8s 控制面拓展机制

参考文章:https://blog.hdls.me/15564491070483.html

K8S作为云原生操作系统的定位,其设计理念是"微内核"架构。

可拓展机制:

  • 单体进程,往往采用Filter机制。实现进程内可拓展。
  • 分布式系统,通过webhook机制将自定义插件注入到分布式集群中。
  • in-proxy模式,通过沙箱+远程脚本,实现非侵入性的单体进程内filter机制。

k8s webhook

Kubernetes 的 apiserver 一开始就有 AdmissionController 的设计,这个设计和各类 Web 框架中的 Filter 很像,就是一个插件化的责任链,责任链中的每个插件针对 apiserver 收到的请求做一些操作或校验。分类

  • MutatingWebhookConfiguration,操作 api 对象的, 会对request的resource,进行转换,比如填充默认的request/limit(有副作用)
  • ValidatingWebhookConfiguration,校验 api 对象的, 比如校验Pod副本数必须大于2。(无副作用)

Kubernetes 中的许多高级功能需要启用准入控制器才能正确支持该功能。

对于在数据持久化之前,拦截到 Kubernetes API server 的请求

K8S准入控制器

通过创建webhook资源,利用k8s的webhook能力实现pod的自动注入。

基于 Kubernetes 的 突变 webhook 入驻控制器(mutating webhook addmission controller 的自动 sidecar 注入方式。

实现流程大致如下:

  1. 定义webhook监听pod(node)
  2. 注册到webhook(master)

实现一个K8S Sidecar注入控制器

1. 编写 Webhook server

sidecarConfig, err := loadConfig(parameters.sidecarCfgFile)
pair, err := tls.LoadX509KeyPair(parameters.certFile, parameters.keyFile)

whsvr := &WebhookServer {
    sidecarConfig:    sidecarConfig,
    server:           &http.Server {
        Addr:        fmt.Sprintf(":%v", 443),
        TLSConfig:   &tls.Config{Certificates: []tls.Certificate{pair}},
    },
}
	
// define http server and server handler
mux := http.NewServeMux()
mux.HandleFunc("/mutate", whsvr.serve)
whsvr.server.Handler = mux

// start webhook server in new rountine
go func() {
    if err := whsvr.server.ListenAndServeTLS("", ""); err != nil {
        glog.Errorf("Filed to listen and serve webhook server: %v", err)
    }
}()
  1. https server: 443
  2. tls: certificate
    1. k8s configmap
  3. api: ‘/mutate’
    1. patch

capa-injector: https://github.com/capa-cloud/capa-injector

2. 编写 Dockerfile 并构建

  1. 创建dockerfile镜像文件
  2. 创建docker账号
  3. 上传镜像到dockerhub

3. 编写 Sidecar 注入配置

  1. 创建 configmap
apiVersion: v1
kind: ConfigMap
metadata:
   name: sidecar-injector-webhook-configmap
data:
   sidecarconfig.yaml: |
      containers:
        - name: sidecar-nginx
          image: nginx:1.12.2
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
          volumeMounts:
            - name: nginx-conf
              mountPath: /etc/nginx
      volumes:
        - name: nginx-conf
          configMap:
            name: nginx-configmap

4. 创建包含秘钥对的 Secret

由于准入控制是一个高安全性操作,所以对外在的 webhook server 提供 TLS 是必须的。作为流程的一部分,我们需要创建由 Kubernetes CA 签名的 TLS 证书,以确保 webhook server 和 apiserver 之间通信的安全性。

  1. 创建CA证书
  2. k8s签发证书
  3. 创建secret
apiVersion: v1
kind: Secret
metadata:
  name: sidecar-injector
  namespace: sidecar-injector
data:
  tls.crt: |
    LS0tLS1CRUdJTi...
  tls.key: |
    LS0tLS1CRUdJTiBSU0...

5. 创建 Sidecar 注入器的 Deployment 和 Service

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sidecar-injector-webhook-deployment
  labels:
    app: sidecar-injector
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sidecar-injector
    spec:
      containers:
        - name: sidecar-injector
          image: morvencao/sidecar-injector:v1
          imagePullPolicy: IfNotPresent
          args:
            - -sidecarCfgFile=/etc/webhook/config/sidecarconfig.yaml
            - -tlsCertFile=/etc/webhook/certs/cert.pem
            - -tlsKeyFile=/etc/webhook/certs/key.pem
            - -alsologtostderr
            - -v=4
            - 2>&1
          volumeMounts:
            - name: webhook-certs
              mountPath: /etc/webhook/certs
              readOnly: true
            - name: webhook-config
              mountPath: /etc/webhook/config
      volumes:
        - name: webhook-certs
          secret:
            secretName: sidecar-injector-webhook-certs
        - name: webhook-config
          configMap:
            name: sidecar-injector-webhook-configmap
  • sidecarCfgFile 指的是 sidecar 注入器的配置文件,挂载自上面创建的 ConfigMap sidecar-injector-webhook-configmap。
  • tlsCertFile 和 tlsKeyFile 是秘钥对,挂载自 Secret injector-webhook-certs。
apiVersion: v1
kind: Service
metadata:
   name: sidecar-injector-webhook-svc
   labels:
      app: sidecar-injector
spec:
   ports:
      - port: 443
        targetPort: 443
   selector:
      app: sidecar-injector

这个 Service 会被 MutatingWebhookConfiguration 中定义的 clientConfig 部分访问,默认的端口 spec.ports.port 需要设置为 443。

6. 动态配置 webhook 准入控制器

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
   name: sidecar-injector-webhook-cfg
   labels:
      app: sidecar-injector
webhooks:
   - name: sidecar-injector.morven.me
     clientConfig:
        service:
           name: sidecar-injector-webhook-svc
           namespace: default
           path: "/mutate"
        caBundle: ${CA_BUNDLE}
     rules:
        - operations: [ "CREATE" ]
          apiGroups: [""]
          apiVersions: ["v1"]
          resources: ["pods"]
     namespaceSelector:
        matchLabels:
           sidecar-injector: enabled
  • ${CA_BUNDLE} - 从k8s apiserver中获取:
    • kubectl config view –raw –minify –flatten -o jsonpath=’{.clusters[].cluster.certificate-authority-data}

  • rules - 描述了 webhook server 处理的资源和操作。在我们的例子中,只拦截创建 pods 的请求;
  • namespaceSelector - namespaceSelector 根据资源对象是否匹配 selector 决定了是否针对该资源向 webhook server 发送准入请求。

E、云原生自动化和规模化

Kubernetes虽然提供了多种容器编排对象,例如Deployment、StatefulSet、DeamonSet、Job等,还有多种基础资源封装例如ConfigMap、Secret、Serivce等,但是一个应用往往有多个服务,有的可能还要依赖持久化存储,当这些服务之间直接互相依赖,需要有一定的组合的情况下,使用YAML文件的方式配置应用往往十分繁琐还容易出错,这时候就需要服务编排工具。

  1. 编写k8s资源文件集合
  2. 通过打包格式进行管理
  3. 上传到镜像仓库
  4. 通过k8s包管理工具helm进行安装

$ helm install istio-base istio/base -n istio-system

F、Istio 注入

sidecar injector 准入控制器

Istio 使用 ValidatingAdmissionWebhooks 验证 Istio 配置,使用 MutatingAdmissionWebhooks 自动将 Sidecar 代理注入至用户 Pod。

它使用 MutatingWebhook 机制在 pod 创建的时候将 sidecar 的容器和卷添加到每个 pod 的模版里。

      containers:
      - image: docker.io/istio/examples-bookinfo-productpage-v1:1.15.0 # 应用镜像
        name: productpage
        ports:
        - containerPort: 9080
      - args:
        - proxy
        - sidecar
        - --domain
        - $(POD_NAMESPACE).svc.cluster.local
        - --configPath
        - /etc/istio/proxy
        - --binaryPath
        - /usr/local/bin/envoy
        - --serviceCluster
        - productpage.$(POD_NAMESPACE)
        - --drainDuration
        - 45s
        - --parentShutdownDuration
        - 1m0s
        - --discoveryAddress
        - istiod.istio-system.svc:15012
        - --zipkinAddress
        - zipkin.istio-system:9411
        - --proxyLogLevel=warning
        - --proxyComponentLogLevel=misc:error
        - --connectTimeout
        - 10s
        - --proxyAdminPort
        - "15000"
        - --concurrency
        - "2"
        - --controlPlaneAuthPolicy
        - NONE
        - --dnsRefreshRate
        - 300s
        - --statusPort
        - "15020"
        - --trust-domain=cluster.local
        - --controlPlaneBootstrap=false
        image: docker.io/istio/proxyv2:1.5.1 # sidecar proxy
        name: istio-proxy
        ports:
        - containerPort: 15090
          name: http-envoy-prom
          protocol: TCP
      initContainers:
      - command:
        - istio-iptables
        - -p
        - "15001"
        - -z
        - "15006"
        - -u
        - "1337"
        - -m
        - REDIRECT
        - -i
        - '*'
        - -x
        - ""
        - -b
        - '*'
        - -d
        - 15090,15020
        image: docker.io/istio/proxyv2:1.5.1 # init 容器
        name: istio-init

配置管理设计

1. 启动静态配置

启动的最小配置集,可以和镜像集成在一起。

  • cmd params
  • env variables
2. 自定义配置

可以使用K8S的配置进行管理:

  • k8s configmap
    • 热更新
  • k8s crd
3. 灵活的动态配置

控制面下发,全量/增量推送:

  • xDS
  • Configuration(Nacos/…)

三、Sidecar架构

参考文档:https://www.infoq.cn/article/jTJGTtu2AgX74GkGif8Y

  • Init 容器 istio-init:用于 pod 中设置 iptables 端口转发
  • Sidecar 容器 istio-proxy:运行 sidecar 代理,如 Envoy 或 MOSN。

init

Init 容器是一种专用容器,它在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。

一个 Pod 中可以指定多个 Init 容器,如果指定了多个,那么 Init 容器将会按顺序依次运行。只有当前面的 Init 容器必须运行成功后,才可以运行下一个 Init 容器。当所有的 Init 容器运行完成后,Kubernetes 才初始化 Pod 和运行应用容器。

Init 容器使用 Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问 Secret 的权限,而应用程序容器则不能。

在 Pod 启动过程中,Init 容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。如果由于运行时或失败退出,将导致容器启动失败,它会根据 Pod 的 restartPolicy 指定的策略进行重试。然而,如果 Pod 的 restartPolicy 设置为 Always,Init 容器失败时会使用 RestartPolicy 策略。

在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready 状态。Init 容器的端口将不会在 Service中进行聚集。 正在初始化中的 Pod 处于 Pending 状态,但应该会将 Initializing 状态设置为 true。Init 容器运行完成以后就会自动终止。

A、istio-init容器

该容器存在的意义就是让 sidecar 代理可以拦截所有的进出 pod 的流量,15090 端口(Mixer 使用)和 15092 端口(Ingress Gateway)除外的所有入站(inbound)流量重定向到 15006 端口(sidecar),再拦截应用容器的出站(outbound)流量经过 sidecar 处理(通过 15001 端口监听)后再出站。

istio-iptables [flags]
  -p: 指定重定向所有 TCP 流量的 sidecar 端口(默认为 $ENVOY_PORT = 15001)
  -m: 指定入站连接重定向到 sidecar 的模式,“REDIRECT” 或 “TPROXY”(默认为 $ISTIO_INBOUND_INTERCEPTION_MODE)
  -b: 逗号分隔的入站端口列表,其流量将重定向到 Envoy(可选)。使用通配符 “*” 表示重定向所有端口。为空时表示禁用所有入站重定向(默认为 $ISTIO_INBOUND_PORTS  -d: 指定要从重定向到 sidecar 中排除的入站端口列表(可选),以逗号格式分隔。使用通配符“*” 表示重定向所有入站流量(默认为 $ISTIO_LOCAL_EXCLUDE_PORTS  -o:逗号分隔的出站端口列表,不包括重定向到 Envoy 的端口。
  -i: 指定重定向到 sidecar 的 IP 地址范围(可选),以逗号分隔的 CIDR 格式列表。使用通配符 “*” 表示重定向所有出站流量。空列表将禁用所有出站重定向(默认为 $ISTIO_SERVICE_CIDR  -x: 指定将从重定向中排除的 IP 地址范围,以逗号分隔的 CIDR 格式列表。使用通配符 “*” 表示重定向所有出站流量(默认为 $ISTIO_SERVICE_EXCLUDE_CIDR)。
  -k:逗号分隔的虚拟接口列表,其入站流量(来自虚拟机的)将被视为出站流量。
  -g:指定不应用重定向的用户的 GID。(默认值与 -u param 相同)
  -u:指定不应用重定向的用户的 UID。通常情况下,这是代理容器的 UID(默认值是 1337,即 istio-proxy 的 UID)。
  -z: 所有进入 pod/VM 的 TCP 流量应被重定向到的端口(默认 $INBOUND_CAPTURE_PORT = 15006)。

自定义init容器

#!/bin/bash

# Forward TCP traffic on port 80 to port 8000 on the eth0 interface.
iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 80 -j REDIRECT --to-port 8000

# List all iptables rules.
iptables -t nat --list
# Use the latest Ubuntu image for the base.
FROM ubuntu:latest

# Install the iptables command.
RUN apt-get update && \
    apt-get install -y iptables

# Copy the initialization script into the container.
COPY init.sh /usr/local/bin/

# Mark the initialization script as executable.
RUN chmod +x /usr/local/bin/init.sh

# Start the initialization script on container startup.
ENTRYPOINT ["init.sh"]

自定义流量拦截sidecar

capa

B、istio-proxy容器

使用单容器多进程模型。

1. pilot agent进程

在proxy镜像中,pilot-agent 负责的工作包括:

  1. 生成envoy的配置:
    1. 与控制面板通讯,获取xDS配置
    2. 生成 Envoy 的Bootstrap启动配置
  2. 启动envoy
  3. 监控并管理envoy的运行状况
    1. envoy健康检查
    2. envoy出错时pilot-agent负责重启envoy
    3. envoy配置变更后reload envoy
    4. envoy优雅退出

status server

[/pilot-agent/main/initStatusServer]

envoy检查

对于 ready 检查,调用的路径为/healthz/ready, 并配合设置的端口 applicationPorts 通过 Envoy 的 admin 端口进行对应的端口进行检查,用于决定 Envoy 是否已经 ready 接受相对应的流量。

检查原理是通过本地管理端口,如 http://127.0.0.1:15000/listeners 获取 Envoy 当前监听的全部端口,然后将配置的端口 applicationPorts 在监听的端口中进行查找,来决定 Envoy 是否 ready。

应用端口检查

检查的路径为 /url 路径,在 header 中设置 istio-app-probe-port 端口,使用 访问路径中的 url 来进行检查,最终调用的是 http://127.0.0.1:istio-app-probe-port/url,头部设置的全部参数也都会传递到别检测的服务端口上;

xds proxy

[/pilot-agent/main/istio_agent.NewAgent/initXdsProxy]

2. proxy进程(第三方代理进程)

启动Envoy

[/pilot-agent/main/istio_agent.NewAgent]

C、sidecar多进程设计模式

1. 开源集成

代理属于第三方提供,istio是对其进行了管理和拓展。

如果代理本身属于istio,是可以实现单进程模型。

2. agent设计思想

由agent负责配置监听和下发。

解耦配置管理和运行时,同时可以对proxy进程进行热重启。

D、流量拦截

iptables

ip netns exec cni-bf783dac-fe05-cb35-4d5a-848449119b19 iptables -L -t nat

-A PREROUTING -p tcp -j ISTIO_INBOUND                          # PREROUTING全部转发到INBOUND,PREROUTING发生在流入的数据包进入路由表之前
-A OUTPUT -p tcp -j ISTIO_OUTPUT                               # 由本机产生的数据向外转发的
-A ISTIO_INBOUND -p tcp -m tcp --dport 22 -j RETURN            # 22 15090  15021 15020的不转发到ISTIO_REDIRECT 
-A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN         
-A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT                   # 剩余的流量都转发到ISTIO_REDIRECT
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006       # 转发到15006
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN                # 127.0.0.6是InboundPassthroughBindIpv4,代表原地址是passthrough的流量都直接跳过,不劫持
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT  #lo网卡出流量,目标地址不是localhost的,且为同用户的流量进入ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN    # lo网卡出流量 非同用户的不劫持
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN            # 剩下的同用户的都跳过
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT  # lo网卡出流量,目标地址非本地,同用户组的流量进入ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN    # lo网卡出流量非同组的不劫持
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN            # 剩余的同用户的不劫持
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN                      # 剩余的目标地址为127的不劫持
-A ISTIO_OUTPUT -j ISTIO_REDIRECT                              # 剩下的都进入 ISTIO_REDIRECT
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001          # 转达到15001 outbond
COMMIT

https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing/


四、Sidecar 发展

A、sidecar 流量交互

要实现 应用容器(进程) 和 Sidecar容器(进程) 之间的交互。需要完成流量交互的功能。

流量交互模式

对于跨容器(进程)的流量交互,主要有以下两种交互模式:

proxyless sidecar 和 Servicemesh 在方式上的差异:暴露 API 还是代理通讯协议。

1. 流量劫持 servicemesh

在 Servicemesh 中,“零侵入”是一个非常强调的特性,为此不惜引入 iptables 等流量劫持方案。“零侵入”在某些特殊场景下会发挥巨大的优势,如旧有应用不做改造的前提下接入 servicemesh。好处自然不言而喻,但零侵入也有自身的限制:客户端必须能发出符合服务器端要求的网络通讯请求,这个过程外部无法插手。

代理模式强调的是 原协议转发,应用进程无感。往往使用操作系统提供的流量劫持功能。

适合于统一的网络协议栈(HTTP),仅实现原协议层面的控制(路由、重试等)。

应用场景:envoy流量代理

发展方向:内核高性能(eBPF)

eBPF

几乎没有开销是来自代理本身的逻辑。开销是通过注入代理,将网络流量重定向到它,终止连接和启动新的连接而增加的。

2. 流量明确指向 proxyless sidecar

应用进程明确转发,协议自由切换,更丰富的应用层语义。往往使用grpc建立跨进程的链接。

适合异构协议栈,支持更丰富的功能,支持协议之上应用层语义的控制。

应用场景:multi runtime

发展方向:grpc proxyless sidecar

proxyless sidecar

gRPC 项目对 xDS API 有很好的支持,也就是说你可以管理 gRPC 工作负载,而不需要同时部署 Envoy sidecar。

B、multi sidecar

ServiceMesh 在微服务领域已经非常流行,越来越多的公司开始在内部落地,ServiceMesh 带来的业务解耦,平滑升级等优势大大提高了中间件的迭代效率。

不过 ServiceMesh 只解决了服务间通讯的需求,而现实中的分布式应用存在更多的需求。而效仿 ServiceMesh 将应用需要的其他分布式能力外移到各种 Sidecar Runtime,这逐渐演变成了一个趋势。

与其依靠多个代理来实现不同的目的(例如网络代理,缓存代理,绑定代理),不如使用一个 Mecha 提供所有这些能力。

Mecha 强调是“提供能力”,而不是通讯代理。

Mecha 和 Micrologic 之间的交互是开放而有 API 标准的,Mecha 和 Micrologic 之间的“协议”体现在 API 上,而不是 TCP 通讯协议。这提供了一个契机:一个统一 Micrologic 和 Mecha 之间通讯方式的契机。

C、sidecar 拓展性

面对千变万化的需求和复杂的应用环境,期望 Sidecar 本身的控制面和数据面来覆盖所有的场景显然是不现实的。强大、全面往往是因为易扩展

1. 控制面拓展

使用类似K8S webhook的机制,将自定义插件注入到控制面中,作为单独的服务执行。

此类扩展可以完全无侵入的实现数据平面的增强。而且 API 的抽象屏蔽了数据平面的实现细节,扩展会具有更好的可移植性;独立进程执行和部署,具备更强的伸缩性。但是 webhook 模 也引入了大量额外的外部调用和数据交互,带来了巨大的性能开销。

2. in-proxy拓展(WASM)

WebAssembly,简称 WASM,是一个二进制指令集,最初是跑在浏览器上来解决 JavaScript 的性能问题,但由于它良好的安全性,隔离性以及语言无关性等优秀特性,很快人们便开始让它跑在浏览器之外的地方,随着 WASI 定义的出现,只需要一个 WASM 运行时,就可以让 WASM 文件随处执行。

WASM和Java字节码非常相似

WASM 字节码不能直接在任何 CPU 架构上执行,但由于它与机器码非常相近,因此能够以非常快的速度被 WASM 引擎(或者也可以称之为 WASM 虚拟机)翻译为对应架构的机器码,获得和机器码相近的性能。

WASM 本身是为 Web 而设计,因此天然具有跨平台支持;同时,通过 WASM 虚拟机的沙箱隔离,也使得执行 WASM 字节码相比于直接执行机器码有更高的安全性。

ABI

它是扩展提供的网络代理设计的,目标是支持通过 wasm envoy 的功能。

API 是这样一个函数,如果想要接收一个 http 请求,它需要实现开发三个接口:

  1. OnHttpRequestHeaders
  2. OnHttpRequestBody
  3. OnHttpRequestTrailers

Envoy Wasm

Envoy 在可拓展性方面做了两方面的工作:

第一,提供了名为 lua 的特殊扩展,允许控制面通过 xDS 协议动态下发 Lua 脚本并由 Envoy 解释执行。

第二,也是本节的主题,Envoy 引入了 WASM 技术用于开发 Envoy 扩展。

Istio Wasm

Istio 的扩展机制使用 Proxy-Wasm 应用二进制接口(ABI)规范,提供了一套代理无关的流媒体 API 和实用功能,可以用任何有合适 SDK 的语言来实现。

扩展 Istio 的功能,满足你的特定需求,需要三个步骤:

  1. 在 Golang 中实现你的插件功能。
  2. 编译、构建,并将 Wasm 模块推送到符合 OCI 标准的 Docker 镜像仓库。
  3. 使用 WasmPlugin 资源配置服务网格工作负载,以便从远程镜像仓库中拉取 Wasm 模块。

E、其他

1. 服务发现代理

Mesh: dns拦截(udp)


五、Capa Sidecar

A、Feature

7层HTTP流量拦截

基于Iptables,拦截http流量。

多个iptables怎么注入?顺序?

实验性质功能,会有影响主链路的风险。

Actor API

参考 dapr 的 actor 设计。

actor 并不适合集成到SDK中运行,故通过runtime提供actor api。

Binding API

参考 dapr 的 binding 设计。

binding 作为拓展性质的同外部系统的交互方式,可基于runtime提供弱依赖的交互。

SaaS API

SaaS api 可以作为实验性质,在runtime中进行提供

B、控制面设计

尽可能少的引入依赖项,Capa作为基础能力的聚合层,不再引入额外的控制面。

使用 configuration 组件作为控制面配置下发方式。

C、交互形态

  • iptables流量拦截
  • grpc交互

Multi Cloud 多云混合云架构

Multi Cloud 多云混合云架构 - 业界参考资料与思考.

混合云架构

A、混合云的多活架构指南

1. 企业选择混合云的技术诉求

稳定性

随着云厂商的技术建设,稳定性越来越高,且同 region 的多可用区方案进一步降低了企业集中式故障的概率。但云厂商仍存在中心式服务,如 region 的网络汇聚、统一的结算系统等。

region级别的可用性,还是比较依赖云厂商的可用性,尤其是一些中心式服务,可能可用性不能达到要求。

对于用户使用时间窗口特别集中的业务,对稳定性的要求更高。比如在线素养课就是在有限时间内老师和学生完成知识传授,如果在这段时间内云服务出现故障,学生的学业受到影响,学生和老师需要重新匹配时间才能完成学习计划。健康码、打车软件这些使用集中在早晚高峰的场景也是同理。对于 SLA 要求更高的业务,选择多云多活是必然的趋势。

实际应用场景,尤其是跟人的现实活动挂钩的,对可用性要求比线上服务要更高。

成本 &服务

但随着互联网红利不断消退,公司希望实现成本效益最大化,引入更多的供应商也是必选的手段之一。新的供应商,可以用来做数据灾备,或者用于峰值时的弹性计算,也或者按照不同业务进行切分。

不希望鸡蛋放到一个篮子里,厂商锁定可能在议价时处于劣势。而如果可以灵活选择供应商,就可以选择折扣比较多的云服务。

2. 多云SLA的挑战

稳定性

多活架构是用来解决稳定性问题的,但若不能做到多云各自完整的闭环,彼此之间还有千丝万缕的调用依赖,故障率反而会增加。

如果每个云都是单独的服务,并且存在互相调用问题。那么故障的几率是乘积的关系,其实是比单云可用性要更低的。

所以每个服务,都部署到多云上,将会极大地提高SLA。但在这种架构的过渡时期,则必须面对以上问题。

除了部署异构导致的故障率加剧外,脑裂加剧也是一大隐患。多家云厂商中数据存储一般采用主从的方式进行同步,master 所在的主云故障后,需要将另一边云的数据存储提主。但是主云并不是完整挂掉,运维人员无法从控制台登陆,无法将 master 降为 slave。但用户南北流量或者定时任务还在持续请求,主云仍有可能还在处理写入流量。这时候从云的切主,就会导致不可避免的脑裂。脑裂无法简单的进行修复,需要业务研发 case by case 的进行修复,代价巨大。

“脑裂"类似的场景很多,比如数据同步,以及在A云故障时,如何将数据放回B云,是值得思考的问题。

效率

如果做不到多云的对等部署,不得不通过持续演练的方式来应对墒增,保证多云架构的有效性。一旦松懈了,很有可能导致花大精力做的架构升级,但在真正的单云故障面前不起任何作用。另外,在两次演练之间的窗口内,架构能否很好的应对单云故障也是存疑的。

虽然多云号称可以解决单云故障问题,但系统是熵增的,必须通过不断的演练来确保这一点。不然可能真的故障的时候,手忙脚乱,导致各种业务问题。

B、如何正确选择多云架构?

1. 多云架构优势

多云架构有如下优势:
* 灾难恢复,当一家云供应商出现故障时,数据存储可以从另一家云供应商进行恢复。虽然云厂商有多地域,单地域也有多可用区,但还是存在中心化的依赖。这种依赖的故障就会导致整个云的故障。后面提到的故障主要指这种类型的故障。
* 故障转移,当一家云供应商出现故障时,使用另一家云供应商承接服务,实现服务平稳不中断。
* 成本优化,任意的采购只要有了两家及以上供应商,采购方就有了充分选择和议价的能力。
* 避免供应商锁定,单一供应商,除了没有议价能力外,各种依赖也会使得变更极为困难。
* 数据主权,企业提供服务,但产生的数据产权也是归属于服务对象。服务对象既有普通的用户(行权由国家主体代为进行),也有机构。他们对产权的需求,连带的导致企业的云基础设施选择也受其限制。
* 特定服务访问,不同云有自己优势的服务,一般出现在 PaaS 层,如各式各样的数据库、大数据实时方案等。使用多云可以集各家之长。
  • 便于全球化业务,基于当地优势云,快速部署一套当地的服务集群。
  • 业务本地化,边缘节点更接近用户,体验更良好(游戏业务居多?)

2. 5种多云架构模式

Good!回看文章。


多活架构

A、阿里的单元化架构

Multi Mesh 各领域Mesh架构

Multi Mesh 各领域Mesh架构 - 业界参考资料与思考.

DB Mesh架构

A、Database Mesh 2.0 如何在云原生场景下提高数据库治理性能?

1. 企业选择混合云的技术诉求