共计 10771 个字符,预计需要花费 27 分钟才能阅读完成。
楔子: 以蚂蚁典型的建站场景为例,在接入 Kusion 后,用户侧配置代码缩小到 5.5%,用户面对的 4 个平台通过接入对立代码库而消减,在无其余异样的状况下交付工夫从 2 天下降到 2 小时……
注:本文是柴树杉在 2021 GIAC 大会上分享的内容。相干 PPT 内容请点击下方自行下载
GIAC 大会 PPT 下载:KCL 申明式的云原生配置策略语言
0. 你好 GIAC
大家好,我是来自蚂蚁的同学,很快乐能在 GIAC 的编程语言新范式板块和大家分享《KCL 配置策略语言》。KCL 语言是蚂蚁外部的 Kusion 解决方案中针对云原生基础设施配置代码化自研的 DSL 语言,目前曾经在建站场景等一些场景开始小范畴推广试用。
咱们先看一下简略的 KCL 代码:
schema GIACInvitation[name: str]:
Name: str = name
Topic: str = "分享主题"
Company?: str = None
Type: str = "分享嘉宾"
Address: str = "深圳"
invitation = GIACInvitation("姓名") {
Topic: "KCL 配置策略语言"
Company: "蚂蚁团体"
}
这个例子代码先通过 schema
定义了一个 GIACInvitation
构造体:该构造体有一个 str
类型的 name
参数,同时还有一组标注了类型和默认值的属性。而后通过申明式的语法结构了 GIACInvitation
的实例 invitation
。
这个例子尽管简略,然而蕴含了 KCL 最重要的 schema 语言构造。从例子能够看出 KCL 尝试通过申明式的语法、动态类型查看个性来改良配置代码的编写和保护工作。这也是设计 KCL 语言的初衷,咱们心愿通过编程畛域成熟的技术实践来解决云原生畛域的配置代码化的问题。
1. KCL 语言的诞生背景
在经典的 Linux/UNIX 操作系统中,咱们通过 Shell 和零碎内置的各种工具和内核进行交互,同时通过 Shell 脚本来治理更下层的 App。能够说 Shell 语言极大地简化了内核的编程界面,不仅仅晋升了操作系统易用性也简化了下层 App 的治理和运维,也进步了生产效率。而 Kubernetes 作为容器治理畛域的事实标准,曾经成为云计算时代的 Linux/UNIX。类比 UNIX 零碎,Kubernetes 目前还短少一种合乎其申明式、凋谢、共享设计理念的交互语言及工具。
1.1 为何要设计 KCL 语言?
K8s 曾经成为云计算的操作系统,然而目前尚短少性能齐备的 SHELL 交互界面。目前尽管有很多而且开源计划,然而还没有像 UNIX 的 Shell 那种呈现比拟成熟的计划,特地是尚无奈满足头部互联网企业大规模工程化的要求。云原生技术与企业落地之间存在 Gap 须要填补,这正是云原生工程化要解决的问题,也是设计 KCL 语言的出发点。
1.2 目前是一个好时机
云原生的思路是高度的凋谢化和民主化,后果就是万物可配置,所有配置都是代码。带配置代码背后人人平等,每个用户都能够通过调整配置代码和根底平台设施进行交互。因而对配置的编写和保护正在成为云计算时代软件工程师的必备的技能和需要。基于对云原生配置代码化需要的日益旺盛,硅谷的诸多头部公司曾经对这个方向进行了大规模的实际和验证,这些都给了咱们大量能够参考的教训。
因而蚂蚁的 Kusion 我的项目尝试通过 KCL 配置策略语言正是为了简化云原生技术设施的接入形式设计,其设计指标不仅仅是为了晋升蚂蚁基础设施的凋谢水平及应用效率,同时心愿可能优化共享、协同的开发流程,能够说其定位正是云原生时代的 Shell 语言。尽管目前还处于摸索和实际阶段,咱们通过此文和大家分享下 KCL 语言的设计与实现的一些理念,为云原生的疾速到来奉献一点绵薄之力。
1.3 KCL 诞生历史
KCL 语言从 2019 年开始初期的调研和设计工作。到 2020 年 3 月公布 kcl-0.1,基于 Python 定制语法,采纳 Go 版本的 Grumpy 和 AntLR 等工具开发。2020 年下半年改用 Python 语言并放慢了开发和迭代速度,公布的 kcl-0.2.x 引入了大量语言个性、减少了 Plugin 扩大反对、同时反对 IDEA 插件。2021 年上半年开始对立优化和整合语言个性,公布的 kcl-0.3 优化类型零碎、集成单元测试工具、优化执行性能并提供了 Go 等多语言的 API 反对、同时通过 LSP 为 VSCode 提供反对。2021 年下半年开始在建站等常见落地,同时引入动态类型检查和优化性能,欠缺语言的文档反对。
2. KCL 语言的设计准则
基于蚂蚁践行多年的经典运维中台积淀的教训和对各种问题利弊的思考,Kusion 我的项目对如何充分利用云原生技术带来的红利,打造一个凋谢、通明、申明式、可协同的运维体系进行了摸索和思考,提出并实际了基于基础设施代码化的云原生协同开发的模型。而 KCL 语言正是 Kusion 我的项目为了解决云原生协同开发而设计的申明式的配置编程语言,简略、稳固、高效和工程化是 KCL 语言设计的设计理念。
2.1 简略为王
简略不仅仅能够升高学习和沟通的老本,而且能够缩小代码出问题的危险。不论是 UNIX 奉行的 KISS 准则还是 Go 语言推崇的 Less is more 设计理念,简化易用的界面始终是各种胜利产品谋求的一个指标。同样从简略准则登程,KCL 语言在参考古代编程语言之上只保留了必要的元素,同时通过类型主动推导、引入受限的控制流和 schema 提供了根底灵便的配置定义编写能力,删减语言个性始终是 KCL 语言设计工作的一个重要指标。
2.1.1 申明式语法
申明式编程是和命令式编程并列的一种编程范式,申明式编程只通知你想要的后果,执行引擎负责执行的过程。申明式编程应用更加简略,能够升高命令式拼装造成的复杂性和副作用,放弃配置代码清晰可读,而简单的执行逻辑曾经由 Kubernetes 零碎提供反对。KCL 语言通过简化对 schema 构造体实例化的语法结构对申明式语法提供反对,通过仅提供大量的语句来缩小命令过程式编程带来的复杂性。围绕 schema 和配置相干的语法,KCL 心愿每种配置需要尽可能通过固定的写法实现,使得配置代码尽可能的统一化。
比方作为 KCL 申明式语法的外围构造 schema 能够采纳申明式形式实例化:
schema Name:
firstName: str
lastName: str
schema Person:
name: Name = {
firstName: "John"
lastName: "default"
}
JohnDoe = Person {name.lastName: "Doe"}
首先通过 schema 定义了一个 Name 构造,构造蕴含 2 个字符串类型的必填属性。而后在 Person 中复用 Name 类型申明一个 name 属性,并且给 name 属性设置了默认值以简化用户应用。最终在定义 JohnDoe 配置定义的时候,只需填写 name.lastName 一个属性参数即可,其余局部属性均采纳默认的参数。
对于一些规范的业务利用,通过将可复用的模型封装为 KCL schema,这样能够为前端用户提供最简略的配置界面。比方基于蚂蚁外部 Konfig 大库中 sofa.SofaAppConfiguration 只需增加大量的配置参数就能够定制一个 App
appConfiguration = sofa.SofaAppConfiguration {
resource: resource.Resource {
cpu: "4"
memory: "8Gi"
disk: "50Gi"
}
overQuota: True
}
通过申明式语法形容必要的参数(其余的参数全副采纳默认配置),能够极大简化普通用户的配置代码。
2.1.2 程序无关语法
有别于命令式编程,KCL 推崇的是更适宜于配置定义的申明式语法。以斐波那契数列为例,能够把一组申明式的定义看作一个方程组,方程式的编写程序实质上不影响方程组的求解,而计算属性依赖并“求解”的过程由 KCL 解释器实现,这样能够防止大量命令式拼装过程及程序判断代码。
schema Fib:
n1: int = n - 1
n2: int = n1 - 1
n: int
value: int
if n <= 1:
value = 1
elif n == 2:
value = 1
else:
value = (Fib {n: n1}).value + (Fib {n: n2}).value
fib8 = (Fib {n: 8}).value # 21
代码中 Fib 定义的成员 n、n1 和 n2 有肯定的依赖关系,然而和它们书写的程序并无关系。KCL 语言引擎会依据申明式代码中的依赖关系主动计算出正确的执行程序,同时对相似循环援用等异样状态告警。
2.2.3 同名配置合并
当整个业务和开发保护团队都变得复杂时,配置代码的编写和保护也将变得复杂化:同一份配置参数可能散落在多个团队的多个模块中,同时一个残缺的利用配置则须要合并这些散落在不同中央的雷同和不同配置参数才能够失效,而雷同的配置参数可能因为不同团队的批改而产生抵触。通过人工形式同步这些同名配置和合并不同的配置都是一个极大的挑战。
比方 Konfig 大库中利用配置模型分为 base 和各环境 stack 配置,要求程序运行时依照某一 merge 策略合并为一份利用配置,相当于要求大库前端配置可能主动合并,即可能离开屡次定义并且合并,而后实例化生成相应的惟一前端配置。借助 KCL 语言的能力和 Konfig 的最佳实际,可通过将基线配置和环境配置主动合并简化配置的编写。比方对于规范 SOFA 利用 opsfree,其基线配置和环境配置别离保护,最终交由平台工具进行配置合并和查看。KCL 语言通过自动化合并同名配置实现简化团队协同开发的设计指标。
比方 base 配置收集的通用的配置:
appConfiguration = sofa.SofaAppConfiguration {
mainContainer: container.Main {readinessProbe: probe_tpl.defaultSofaReadinessProbe}
resource: res_tpl.medium
releaseStrategy: "percent"
}
而后再预发环境在 base 配置的根底之上针对某些参数进行微调:
appConfiguration = sofa.SofaAppConfiguration {
resource: resource.Resource {
cpu: "4"
memory: "8Gi"
disk: "50Gi"
}
overQuota: True
}
合并的 pre 配置理论是一份 SofaAppConfiguration 配置(相当于如下等效代码,环境配置的优先级默认高于基线配置)
appConfiguration = sofa.SofaAppConfiguration {
mainContainer: container.Main {readinessProbe: probe_tpl.defaultSofaReadinessProbe}
resource: resource.Resource {
cpu: "4"
memory: "8Gi"
disk: "50Gi"
}
overQuota: True
releaseStrategy: "percent"
}
目前的同名配置尽管只针对利用的主包配置无效,但曾经带来了可察看的收益。
2.2 稳固压倒一切
越是根底的组件对稳定性要求越高,复用次数越多的稳定性带来的收益也更好。因为稳定性是基础设施畛域一个必备的要求,不仅仅要求逻辑正确,而且须要升高谬误呈现的几率。
2.2.1 动态类型和强不可变性
很多配置语言采纳运行时动静查看类型。动静类型最大的毛病只能查看正在被执行属性的类型,这十分不利于开发阶段提前发现类型的谬误。动态类型不仅仅能够提前剖析大部分的类型谬误,还能够升高后端运行时的动静类型查看的性能损耗。
除了动态类型,KCL 还通过 final 关键字禁止某些重要属性被批改。动态类型再联合属性的强不可变性,能够为配置代码提供更强的稳定性保障。比方对于 CafeDeployment 中的 apiVersion 信息是一种常量类型的配置参数,final 为这类配置提供保障:
schema CafeDeployment:
final apiVersion: str = "apps.cafe.cloud.alipay.com/v1alpha1"
final kind: str = 123 # 类型谬误
schema ContainerPort:
containerPort: int = 8080
protocol: "TCP" | "UDP" | "SCTP" = "TCP"
ext? : str = None
代码中 apiVersion 和 kind 属性都被 final 爱护禁止被批改。然而 kind 因为属性类型初始值不同而隐含一个谬误,通过动态类型查看很容易在开发阶段发现错误并改过。
2.2.2 运行时类型和逻辑 check 验证
KCL 的 schema 不仅仅是带类型的构造体,也能够用于在运行时校验存量的无类型的 JSON 和 YAML 数据。此外 schema 的 check 块能够编写语义查看的代码,在运行时实例化 schema 时会主动进行校验。同时,基于 schema 的继承和 mixin 能够产生跟多关联的 check 规定。
比方以下的例子展现 check 的常见用法:
schema sample:
foo: str
bar: int
fooList: [str]
check:
bar > 0 # minimum, also support the exclusive case
bar < 100, "message" # maximum, also support the exclusive case
len(fooList) > 0 # min length, also support exclusive case
len(fooList) < 100 # max length, also support exclusive case
regex.match(foo, "^The.*Foo$") # regex match
isunique(fooList) # unique
bar in [range(100)] # range
bar in [2, 4, 6, 8] # enum
multiplyof(bar, 2) # multipleOf
check 中每个语句都是一个能够产生 bool 后果的表达式和可选的错误信息组成(每个一般的 bool 表达式其实是 assert 语句的简化而来)。通过内置的语法和函数能够实现在运行时对属性值的逻辑验证。
2.2.3 内置测试反对
单元测试是晋升代码品质的无效伎俩。KCL 基于已有的 schema 语法结构,配合一个内置 kcl-test 命令提供灵便的单元测试框架(联合 testing 包可指定面值类型的命令行参数)。
内置测试工具
schema TestPerson:
a = Person{}
assert a.name == 'kcl'
schema TestPerson_age:
a = Person{}
assert a.age == 1
kcl-test 命令不仅仅执行单元测试,还会统计每个测试执行的工夫,而且能够通过正则表达式参数抉择执行指定的测试。此外通过 kcl-test ./...
能够递归执行子目录的单元测试,同时反对集成测试和 Plugin 测试。
2.3 高效是永恒的谋求
KCL 代码不仅仅通过申明式的格调简化编程,同时通过模块反对、mixin 个性、内置的 lint 和 fmt 工具、以及 IDE 插件提供高效的开发体验。
2.3.1 schema 中好用的语法
schema 是 KCL 编写配置程序的外围语法结构,其中简直每个个性均是针对具体的业务场景提效而设计。比方在定义和实例化深层次嵌套的配置参数时,均能够间接指定属性的门路定义和初始化。
schema A:
a: b: c: int
a: b: d: str = 'abc'
A {a.b.c: 5}
同时为了平安,对于每个属性默认都是非空的字段,在实例化时会主动进行查看。
schema 不仅仅是一个独立的带类型注解的配置对象,咱们也能够通过继承的形式来扩大已有的 schema:
schema Person:
firstName: str
lastName: str
# schema Scholar inherits schema Person
schema Scholar(Person):
fullName: str = firstName + '_' + lastName
subject: str
JohnDoe = Scholar {
firstName: "John",
lastName: "Doe",
subject: "CS"
}
代码中 Scholar 从 Person 继承,而后又扩大了一些属性。作为子类的 Scholar 能够间接拜访父类中定义的 firstName 等属性信息。
继承是 OOP 编程中根底的代码复用伎俩,但同时也有多继承导致的菱形继承的技术问题。KCL 语言刻意简化了继承的语法,只保留了单继承的语法。同时 schema 能够通过 mixin 个性混入复用雷同的代码片段,对于不同的能力配套,咱们通过 mixin 机制编写,并通过 mixin 申明的形式“混入”到不同的构造体中。
比方通过在 Person 中混入 FullnameMixin 能够给 schema 减少新的属性或逻辑(包含 check 代码块):
schema FullnameProtocol:
firstName : str = "default"
lastName : str
mixin FullnameMixin for FullnameProtocol:
fullName : str = "${firstName} ${lastName}"
schema relax Person:
mixin [FullnameMixin]
firstName : str = "default"
lastName : str
通过 KCL 的语言能力,平台侧同学能够通过单继承的形式扩大构造体,通过 mixin 机制定义构造体内属性的依赖关系及值内容,通过构造体内程序无关的编写形式实现申明式的构造体定义,此外还反对如逻辑判断、默认值等罕用性能。
2.3.2 doc、fmt、lint 和外围的 LSP 工具
在编程畛域代码尽管是最外围的局部,然而代码对应的文档和配套的工具也是和编程效率高度相干的局部。策略设计哲学并不局限于语言自身,还包含文档、代码格式化工具、代码格调评估工具和 IDE 的反对等。KCL 通过 kcl-doc 反对从配置代码间接提取产生文档,自动化的文档不仅仅缩小了手工保护的老本,也升高的学习和沟通老本。kcl-fmt 则很不便将当前目录下的全副代码(蕴含嵌套的子目录)格式化为惟一的一种格调,而雷同格局的代码同样升高的沟通和代码评审的老本。kcl-lint 工具则是通过将一些内置的危险监测策略对 KCL 代码平行评估,不便用户依据评估后果优化代码的格调。
2.4 工程化的解决方案
任何语言想要在工程中理论利用,不仅仅须要很好的设计,还须要为降级、扩大和集成等惯例的场景提供残缺的解决方案。
2.4.1 多维度接口
KCL 语言设计通过在不同的抽象层次为普通用户(KCL 命令行)、KCL 语言定制者(Go-API、Python-API)、KCL 库扩大者(Plugin)和 IDE 开发者(LSP 服务)均提供了简直等价的性能界面,从而提供了最大的灵便度。
2.4.2 千人千面的配置 DB
KCL 是面向配置的编程语言,而配置的外围是结构化的数据。因而,咱们能够将残缺 KCL 代码看做是一种配置数据库。通过 KCL 的配置参数的查问和更新(override/- O 命令)能够和对应的配置属性门路,能够实现对属性参数的查问、长期批改和存盘批改。
将代码化的配置作为 DB 的惟一源,不仅仅能够集成 DB 畛域成熟的查问和剖析伎俩,而且能够通过配置代码视角调整配置代码的逻辑构造。特地是在自动化运维实际中,通过程序主动生成的配置代码批改的 PullRequest 能够不便引入开发人员进行代码评审,很好地达到人机通过不同界面配合运维。
2.4.3 版本平滑降级
随着业务和代码的演变,相干模块的 API 也会缓缓腐化。KCL 语言设计通过严格的依赖版本治理,而后联合语言内置的语法和查看工具保障 API 平滑的降级和过渡,再配合代码集成测试和评审流程晋升代码平安。KCL 语言通过 @deprecated 个性在代码呈现腐化晚期给出提醒,同时为用户的过渡降级留出肯定的工夫窗口,甚至等到 API 彻底腐烂前通过报错的形式强制要求同步降级相干的代码。
比方在某次降级中,name 属性被 fullName 代替了,则能够通过 @deprecated 个性标记:
schema Person:
@deprecated(version="1.1.0", reason="use fullName instead", strict=True)
name: str
... # Omitted contents
person = Person {
# report an error on configing a deprecated attribute
name: "name"
}
这样在实例化 Person 时,name 属性的初始化语句将会及时收到报错信息。
2.4.4 内置模块、KCL 模块、插件模块
KCL 是面向配置的编程语言,通过内置模块、KCL 模块和插件模块提供工程化的扩大能力。
用户代码中不必导入间接应用 builtin 的函数(比方用 len 计算列表的长度、通过 typeof 获取值的类型等),而对于字符串等根底类型也提供了一些内置办法(比方转化字符串的大小写等办法)。对于绝对简单的通用工作则通过标记库提供,比方通过 import 导入 math 库就能够应用相干的数学函数,能够通过导入 regex 库应用正则表达式库。而针对 KCL 代码也能够组织为模块,比方 Konfig 大库中将基础设施和各种规范的利用形象为模块供下层用户应用。此外还能够通过 Plugin 机制,采纳 Python 为 KCL 开发插件,比方目前有 meta 插件能够通过网络查问核心配置信息,app-context 插件则能够用于获取以后利用的上下文信息从而简化代码的编写。
3. KCL 语言的实现原理
3.1 整体架构
KCL 尽管作为一个专用于云原生配置和策略定义的语言,然而放弃大多数过程式和函数式编程语言的类似实现架构,其外部整体架构组成也是经典的编译器“三段式”架构。上面是 KCL 实现的架构图:
次要有以下几个要害模块:
- 解析器 Parser:解析器剖析 KCL 源代码产生 AST(形象语法树)。
- 编译器 Compiler:对 AST 进行屡次遍历,对 AST 进行语义查看(比方进行类型查看、有效代码查看)并对 AST 进行优化(合并常量表达式等),最终产生虚拟机能够执行的字节码。
- 虚拟机 Virtual Machine (VM):执行 Compiler 产生的字节码,计算产生相应的配置后果,并将配置后果序列化为 YAML/JSON 进行输入。
整体架构分为三段式的益处是能够把针对 KCL 源语言的前端和针对指标机器的后端组合起来,这种创立编译器组合的办法能够大大减少工作量。比方目前的 KCL 字节码定义和后端虚拟机采纳自研实现,KCL 虚拟机次要用于计算产生配置后果并序列化为 YAML/JSON 进行输入。如果遇到在其余非凡应用 KCL 的场景比方在浏览器中执行 KCL,则能够重写一个适配 WASM 的后端,就可轻易将 KCL 移植到浏览器中应用,然而 KCL 自身的语法和语义不须要产生任何变动,编译器前端代码也无需任何改变。
3.2 Go 和 Python 通信原理
为了更好地开释 KCL 配置策略语言的能力以及遍于下层自动化产品集成(比方驰名的编译器后端 LLVM 就因其 API 设计良好,开发人员能够利用其 API 疾速地构建本人的编程语言),KCLVM 目前提供了 Python 和 Go 两种语言的 API,使得用户能够应用相应的 API 疾速地构建语言外围工具,语言自动化查问批改工具等晋升语言的自动化能力,并且进一步能够基于此构建服务化能力,帮忙更多的用户构建本人云原生配置代码化利用或者疾速接入基础设施。
KCLVM 主体采纳 Python 代码实现,而很多的云原生利用以 Go 程序构建,因而为了更好地满足云原生利用用户诉求。KCLVM 首先基于 CGo 和 CPython 构建了 Go 程序和 Python 程序通信媒介,基于此设计了 Python 函数到 Go 函数的 RPC 调用,调用参数以 JSON 模式存储,使得 KCLVM-Python 编译器的能力平滑地适度到 Go 代码中,通过 Go 一行 import 调用即可操作 KCL 代码。
补充: 在服务化实际的过程中,基于 CGO 调用 Python 的计划也遇到了一些问题:首先是 Go+CGO+Python 导致穿插编译艰难,对 ACI 的自动化测试和打包产生了挑战;其次是 CGO 之后的 Python 不反对多语言多线程并发,无奈利用多核的性能;最初即便通过 CGO 将 Python 虚拟机编译到了 Go 程序中,仍然还是须要装置 Python 的规范库和第三方库。
3.3 协同配置原理
当有了一个简略易用并可能保障稳定性的配置语言后,另一个面临的问题是如何应用配置代码化的形式晋升协同能力。基于此,KCL 配置可分为用户侧和平台侧配置两类,最终的配置内容由各自用户侧和平台侧的配置内容独特决定,因而存在两个方面的协同问题:
- 平台侧配置与用户侧配置之间的协同
- 用户侧配置之间的协同
针对上述协同问题,KCL 在技术侧提出了程序无关语法,同名配置合并等形象模型来满足不同的协同配置场景。
以上图为例,首先 KCL 代码在编译过程中造成两张图(用户不同配置间接的援用和从属关系个别模式一张有向无环图),别离对应构造体外部申明代码及构造体应用申明代码。编译过程能够简略分为三步
- 首先定义平台侧的构造体并造成构造体外部申明代码图
- 其次申明并合并不同用户侧配置代码图
- 最初将用户侧配置代码图计算的后果代入平台侧构造体外部申明代码图求解,最终失去残缺配置图定义。
通过这样简略的计算过程,能够在编译时实现大部分代换运算,最终运行时仅进行大量计算即可失去最终的解。同时在编译合并图过程中依然可能执行类型检查和值的查看,区别是类型查看是做泛化、取偏序上确界(查看某个变量的值是否满足既定类型或者既定类型的子类型),值查看是做特化、取偏序下确界(比方将两个字典合并为一个字典)。
4. 对将来的瞻望
KCL 语言目前仍然处于一个高速倒退的阶段,目前曾经有一些利用开始试用。咱们心愿通过 KCL 语言为 Kusion 技术栈提供更强的能力,在运维、可信、云原生架构演进方面起到踊跃的作用。同时对于一些非凡的非标利用提供灵便的扩大和集成计划,比方咱们正在思考如何让后端反对 WebAssembly 平台,从而反对更多的集成计划。
在适合的工夫咱们心愿可能凋谢 KCL 的全副代码,为云原生代码化的疾速落地奉献绵薄之力。
谢谢大家。
- 蚂蚁团体万级规模 K8s 集群 etcd 高可用建设之路
- 咱们做出了一个分布式注册核心
- 开启云原生 MOSN 新篇章 — 交融 Envoy 和 GoLang 生态
- MOSN 子项目 Layotto:开启服务网格 + 利用运行时新篇章
更多文章请扫码关注“金融级分布式架构”公众号