关于开源:2-万字长文声明式配置技术概述

27次阅读

共计 16670 个字符,预计需要花费 42 分钟才能阅读完成。

零、前言​

文本仅用于廓清申明式配置技术概述,KCL 概念以及外围设计,以及与其余配置语言的比照。

一、申明式配置概述​

1.1 配置的重要性​

  • 软件不是变化无穷的,每天有成千上万的配置更新,并且配置自身也在逐步演进,对规模化效率有较高的诉求

    • 配置更新越来越频繁:配置提供了一种扭转零碎性能的低开销形式,一直倒退的业务需要、基础设施要求和其余因素意味着零碎须要一直变动。
    • 配置规模越来越大:一份配置往往要散发到不同的云站点、不同的租户、不同的环境等。
    • 配置场景宽泛:利用配置、数据库配置、网络配置、监控配置等。
    • 配置格局繁多:JSON, YAML, XML, TOML, 各种配置模版如 Java Velocity, Go Template 等。
  • 配置的稳定性至关重要,零碎宕机或出错的一个最次要起因是有大量工程师进行频繁的实时配置更新,表 1 示出了几个因为配置导致的零碎出错事件。
工夫 事件
2021 年 7 月 中国 Bilibili 公司因为 SLB Lua 配置计算出错陷入死循环导致网站宕机
2021 年 10 月 韩国 KT 公司因为路由配置谬误导致在全国范畴内蒙受重大网络中断

表 1 配置导致的零碎出错事件

1.2 申明式配置分类​

云原生时代带来了如雨后春笋般的技术倒退,呈现了大量面向终态的申明式配置实际,如图 1 所示,申明式配置个别可分为如下几种形式。图 1 申明式配置形式分类

1.2.1 结构化 (Structured) 的 KV​

结构化的 KV 能够满足最小化数据申明需要,比方数字、字符串、列表和字典等数据类型,并且随着云原生技术疾速倒退利用,申明式 API 能够满足 X as Data 倒退的诉求,并且面向机器可读可写,面向人类可读。其优劣如下:

  • 劣势

    • 语法简略,易于编写和浏览
    • 多语言 API 丰盛
    • 有各种 Path 工具不便数据查问,如 XPath, JsonPath 等
  • 痛点

    • 冗余信息多:当配置规模较大时,保护和浏览配置很艰难,因为重要的配置信息被吞没在了大量不相干的反复细节中
    • 功能性有余

      • 束缚校验能力
      • 简单逻辑编写能力
      • 测试、调试能力
      • 不易形象和复用
      • Kustomize 的 Patch 比拟定制,根本是通过固定几种 Patch Merge 策略

结构化 KV 的代表技术有

  • JSON/YAML:十分不便浏览,以及自动化解决,不同的语言均具备丰盛的 API 反对。
  • Kustomize:提供了一种无需 模板 和 DSL 即可自定义 Kubernetes 资源根底配置和差异化配置的解决方案,自身不解决束缚的问题,须要配合大量的额定工具进行束缚查看如 Kube-linter、Checkov 等查看工具,图 2 示出了 Kustomize 的典型工作形式。

图 2 Kustomize 典型工作形式

1.2.3 模版化 (Templated) 的 KV​

模版化 (Templated) 的 KV 赋予动态配置数据动静参数的能力,能够做到一份模版 + 动静参数输入不同的动态配置数据。其优劣如下:

  • 劣势

    • 简略的配置逻辑,循环反对
    • 反对内部动静参数输出模版
  • 痛点

    • 容易落入所有配置参数都是模版参数的陷阱
    • 当配置规模变大时,开发者和工具都难以保护和剖析它们

模版化代表技术有:

  • Helm:Kubernetes 资源的包管理工具,通过配置模版治理 Kubernetes 资源配置。图 3 示出了一个 Helm Jekins Package ConfigMap 配置模版,能够看出这些模版自身都非常短小,能够书写简略的逻辑,适宜 Kubernetes 根底组件固定的一系列资源配置通过包治理 + 额定的配置参数进行装置。相比于单纯的模版化的 KV,Helm 肯定水平上提供了模版存储 / 援用和语义化版本治理的能力相比于 Kustomize 更适宜治理内部 Charts, 然而在多环境、多租户的配置管理上不太善于。

图 3 Helm Jekins Package ConfigMap 配置模版

  • 其余各种配置模版:Java Velocity, Go Template 等文本模板引擎非常适合 HTML 编写模板。然而在配置场景中应用时,存在所有配置字段即模版参数的危险,开发者和工具都难以保护和剖析它们。

1.2.3 代码化 (Programmable) 的 KV​

Configuration as Code (CaC), 应用代码产生配置,就像工程师们只须要写高级 GPL 代码,而不是手工编写容易出错而且难以了解的服务器二进制代码一样。配置变更同代码变更同样庄重地看待,同样能够执行单元测试、集成测试等。代码模块化和重用是保护配置代码比手动编辑 JSON/YAML 等配置文件更容易的一个要害起因。其优劣如下:

  • 劣势

    • 必要的编程能力(变量定义、逻辑判断、循环、断言等)
    • 代码模块化与形象(反对定义数据模版,并用模版失去新的配置数据)
    • 能够形象配置模版 + 并应用配置笼罩
  • 痛点

    • 类型查看有余
    • 运行时谬误
    • 束缚能力有余

代码化 KV 的代表技术有:

  • GCL:一种 Python 实现的申明式配置编程语言,提供了必要的言能力反对模版形象,但编译器自身是 Python 编写,且语言自身是解释执行,对于大的模版实例 (比方 K8s 型) 性能较差。
  • HCL:一种 Go 实现结构化配置语言,原生语法受到 libuclnginx 配置等的启发,用于创立对人类和机器都敌对的结构化配置语言,次要针对 devops 工具、服务器配置及 Terraform 中定义资源配置等。
  • Jsonnet:一种 C++ 实现的数据模板语言,实用于应用程序工具开发人员,能够生成配置数据并且无副作用组织、简化、对立治理宏大的配置。

1.2.4 类型化 (Typed) 的 KV​

类型化的 KV,基于代码化 KV,多了类型检查和束缚的能力,其优劣如下:

  • 劣势

    • 配置合并齐全幂等,人造避免配置抵触
    • 丰盛的配置束缚语法用于编写束缚
    • 将类型和值束缚编写形象为同一种模式,编写简略
    • 配置程序无关
  • 痛点

    • 图合并和幂等合并等概念简单,用户了解老本较高
    • 类型和值混合定义进步形象水平的同时晋升了用户的了解老本,并且所有束缚在运行时进行查看,大规模配置代码下有性能瓶颈
    • 对于想要配置笼罩、批改的多租户、多环境场景难以实现
    • 对于带条件的束缚场景,定义和校验混合定义编写用户界面不敌对

类型化 KV 的代表技术有:

  • CUE:CUE 解决的外围问题是“类型查看”,次要利用于配置束缚校验场景及简略的云原生配置场景

1.2.5 模型化 (Structural) 的 KV​

模型化的 KV 在代码化和类型化 KV 的根底上以高级语言建模能力为外围形容,冀望做到模型的疾速编写与散发,其优劣如下:

  • 劣势

    • 引入可分块、可扩大的 KV 配置块编写形式
    • 类高级编程语言的编写、测试形式
    • 语言内置的强校验、强束缚反对
    • 面向人类可读可写,面向机器局部可读可写
  • 有余

    • 扩大新模型及生态构建须要肯定的研发老本,或者应用工具对社区中已有的 JsonSchema 和 OpenAPI 模型进行模型转换、迁徙和集成。

模型化 KV 的代表技术有:

  • KCL:一种 Rust 实现的申明式配置策略编程语言,把运维类研发对立为一种申明式的代码编写,能够针对差异化利用交付场景形象出用户模型并增加相应的束缚能力,冀望借助可编程 DevOps 理念解决规模化运维场景中的配置策略编写的效率和可扩展性等问题。图 4 示出了一个 KCL 编写利用交付配置代码的典型场景

图 4 应用 KCL 编写利用交付配置代码

1.3 不同申明式配置形式的抉择规范与最佳实际​

  • 配置的规模:对于小规模的配置场景,齐全能够应用 YAML/JSON 等配置,比方利用本身的简略配置,CI/CD 的配置。此外对于小规模配置场景存在的多环境、多租户等需要能够借助 Kustomize 的 overlay 能力实现简略配置的合并笼罩等操作。
  • 模型形象与束缚的必要性:对于较大规模的配置场景特地是对多租户、多环境等有配置模型和运维个性研发和积淀迫切需要的,能够应用代码化、类型化和模型化的 KV 形式。

此外,从不同申明式配置形式的应用场景登程

  • 如果须要编写结构化的动态的 K-V,或应用 Kubernetes 原生的技术工具,倡议抉择 YAML
  • 如果心愿引入编程语言便利性以打消文本(如 YAML、JSON) 模板,有良好的可读性,或者已是 Terraform 的用户,倡议抉择 HCL
  • 如果心愿引入类型性能晋升稳定性,保护可扩大的配置文件,倡议抉择 CUE 之类的数据束缚语言
  • 如果心愿以古代语言形式编写简单类型和建模,保护可扩大的配置文件,原生的纯函数和策略,和生产级的性能和自动化,倡议抉择 KCL

不同于社区中的其余同类型畛域语言,KCL 是一种面向利用研发人员并驳回了古代语言设计和技术的动态强类型编译语言

留神,本文将不会探讨通用语言编写配置的状况,通用语言个别是 Overkill 的,即远远超过了须要解决的问题,通用语言存在各式各样的平安问题,比方能力边界问题 (启动本地线程、拜访 IO, 网络,代码死循环等不安全隐患),比方像音乐畛域就有专门的音符去示意音乐,不便学习与交换,不是个别文字语言能够表述分明的。

此外,通用语言因为自身就款式繁多,存在对立保护、治理和自动化的老本,通用语言个别用来编写客户端运行时,是服务端运行时的一个连续,不适宜编写与运行时无关的配置,最终被编译为二进制从过程启动,稳定性和扩展性不好管制,而配置语言往往编写的是数据,再搭配以简略的逻辑,形容的是冀望的最终后果,而后由编译器或者引擎来生产这个冀望后果。

二、KCL 的外围设计与利用场景​

Kusion 配置语言(KCL)是一个开源的基于束缚的记录及函数语言。KCL 通过成熟的编程语言技术和实际来改良对大量繁冗配置的编写,致力于构建围绕配置的更好的模块化、扩展性和稳定性,更简略的逻辑编写,以及更快的自动化集成和良好的生态延展性。

KCL 的外围个性是其 建模 束缚 能力,KCL 外围性能根本围绕 KCL 这个两个外围个性开展,此外 KCL 遵循以用户为核心的配置理念而设计其外围个性,能够从两个方面了解:

  • 以畛域模型为核心的配置视图:借助 KCL 语言丰盛的个性及 KCL OpenAPI 等工具,能够将社区中宽泛的、设计良好的模型间接集成到 KCL 中(比方 K8s 资源模型),用户也能够依据本人的业务场景设计、实现本人的 KCL 模型 (库),造成一整套畛域模型架构交由其余配置终端用户应用。
  • 以终端用户为核心的配置视图 :借助 KCL 的代码封装、形象和复用能力,能够对模型架构进行进一步形象和简化(比方将 K8s 资源模型形象为以利用为外围的 Server 模型),做到 最小化终端用户配置输出,简化用户的配置界面,不便手动或者应用自动化 API 对其进行批改。

不论是以何为核心的配置视图,对于代码而言(包含配置代码)都存在对配置数据束缚的需要,比方类型束缚、配置字段必选 / 可选束缚、范畴束缚、不可变性束缚等,这也是 KCL 致力于解决的外围问题之一。综上,KCL 是一个开源的基于束缚和申明的函数式语言,KCL 次要蕴含如图 5 所示的外围个性:

图 5 KCL 外围个性

  • 简略易用:源于 Python、Golang 等高级语言,驳回函数式编程语言个性,低副作用
  • 设计良好:独立的 Spec 驱动的语法、语义、运行时和零碎库设计
  • 疾速建模:以 Schema 为核心的配置类型及模块化形象
  • 性能齐备:基于 Config、Schema、Lambda、Rule 的配置及其模型、逻辑和策略编写
  • 牢靠稳固:依赖动态类型零碎、束缚和自定义规定的配置稳定性
  • 强可扩大:通过独立配置块主动合并机制保障配置编写的高可扩展性
  • 易自动化:CRUD APIs,多语言 SDK,语言插件 形成的梯度自动化计划
  • 极致性能:应用 Rust & C,LLVM 实现,反对编译到本地代码和 WASM 的高性能编译时和运行时
  • API 亲和:原生反对 OpenAPI、Kubernetes CRD,Kubernetes YAML 等 API 生态标准
  • 开发敌对:语言工具 (Format,Lint,Test,Vet,Doc 等)、IDE 插件 构建良好的研发体验
  • 平安可控:面向畛域,不原生提供线程、IO 等零碎级性能,低噪音,低平安危险,易保护,易治理
  • 多语言 API:Go, Python 和 REST API 满足不同场景和利用应用需要
  • 生产可用:广泛应用在蚂蚁团体平台工程及自动化的生产环境实际中

图 6 KCL 语言外围设计

更多语言设计和能力详见 KCL 文档,只管 KCL 不是通用语言,但它有相应的利用场景,如图 6 所示,研发者能够通过 KCL 编写 配置 (config) 模型 (schema) 函数 (lambda) 规定(rule),其中 Config 用于定义数据,Schema 用于对数据的模型定义进行形容,Rule 用于对数据进行校验,并且 Schema 和 Rule 还能够组合应用用于残缺形容数据的模型及其束缚,此外还能够应用 KCL 中的 lambda 纯函数进行数据代码组织,将罕用代码封装起来, 在须要应用时能够间接调用。

对于应用场景而言,KCL 能够进行结构化 KV 数据验证、简单配置模型定义与形象、强束缚校验防止配置谬误、分块编写及配置合并能力、自动化集成和工程扩大等能力,上面针对这些性能和应用场景进行论述。

2.1 结构化 KV 数据验证​

如图 7 所示,KCL 反对对 JSON/YAML 数据进行格局校验。作为一种配置语言,KCL 在验证方面简直涵盖了 OpenAPI 校验的所有性能。在 KCL 中能够通过一个构造定义来束缚配置数据,同时反对通过 check 块自定义束缚规定,在 schema 中书写校验表达式对 schema 定义的属性进行校验和束缚。通过 check 表达式能够十分清晰简略地校验输出的 JSON/YAML 是否满足相应的 schema 构造定义与 check 束缚。

图 7 KCL 中结构化 KV 校验形式

基于此,KCL 提供了相应的校验工具间接对 JSON/YAML 数据进行校验。此外,通过 KCL schema 的 check 表达式能够十分清晰简略地校验输出的 JSON 是否满足相应的 schema 构造定义与 check 束缚。此外,基于此能力能够构建如图 8 所示的 KV 校验可视化产品。

图 8 基于 KCL 结构化 KV 校验能力构建的可视化产品界面

2.2 简单配置模型定义与形象​

如图 9 所示,借助 KCL 语言丰盛的个性及 KCL OpenAPI 等工具,能够将社区中宽泛的、设计良好的模型间接集成到 KCL 中(比方 K8s 资源模型 CRD),用户也能够依据本人的业务场景设计、实现本人的 KCL 模型 (库),造成一整套畛域模型架构交由其余配置终端用户应用。

图 9 KCL 简单配置建模的个别形式

基于此,能够像图 10 示出的那样用一个大的 Konfig 仓库 治理全副的 KCL 配置代码,将业务配置代码 (利用代码)、根底配置代码 (外围模型 + 底层模型)在一个大库中,不便代码间的版本依赖治理,自动化零碎解决也比较简单,定位惟一代码库的目录及文件即可,代码互通,对立治理,便于查找、批改、保护,能够应用对立的 CI/CD 流程进行配置管理(此外,大库模式也是 Google 等头部互联网公司外部实际的模式)。

图 10 应用 KCL 的语言能力集成畛域模型并形象用户模型并应用

2.3 强束缚校验防止配置谬误​

如图 11 所示,在 KCL 中能够通过丰盛的强束缚校验伎俩防止配置谬误:

图 11 KCL 强束缚校验伎俩

  • KCL 语言的类型零碎被设计为动态的,类型和值定义拆散,反对编译时类型推导和类型查看,动态类型不仅仅能够提前在编译时剖析大部分的类型谬误,还能够升高后端运行时的动静类型查看的性能损耗。此外,KCL Schema 构造的属性强制为非空,能够无效防止配置脱漏。
  • 当须要导出的 KCL 配置被申明之后,它们的类型和值均不能发生变化,这样的动态个性保障了配置不会被随便篡改。
  • KCL 反对通过构造体内置的校验规定进一步保障稳定性。比方对于如图 12 所示的 KCL 代码,,在 App 中定义对 containerPortservicesvolumes 的校验规定,目前校验规定在运行时执行判断,后续 KCL 会尝试通过编译时的动态剖析对规定进行判断从而发现问题。

图 12 带规定束缚的 KCL 代码校验

2.4 分块编写及配置合并​

KCL 提供了配置分块编写及主动合并配置的能力,并且反对幂等合并、补丁合并和惟一配置合并等策略。幂等合并中的多份配置须要满足交换律,并且须要开发人员手动解决根底配置和不同环境配置抵触。补丁合并作为一个笼罩性能,包含笼罩、删除和增加。惟一的配置要求配置块是全局惟一的并且未修改或以任何模式从新定义。KCL 通过多种合并策略简化了用户侧的协同开发,缩小了配置之间的耦合。

如图 13 所示,对于存在基线配置、多环境和多租户的利用配置场景,有一个根本配置 base.k。开发和 SRE 别离保护生产和开发环境的配置 base.k 和 prod.k,他们的配置互不影响,由 KCL 编译器合并成一个 prod 环境的等效配置代码。

图 13 多环境场景配置分块编写实例

2.5 自动化集成​

在 KCL 中提供了很多自动化相干的能力,次要包含工具和多语言 API。通过 package_identifier : key_identifier的模式反对对任意配置键值的索引,从而实现对任意键值的增删改查。比方图 14 所示批改某个利用配置的镜像内容,能够间接执行如下指令批改镜像,批改前后的 diff 如下图所示。

图 14 应用 KCL CLI/API 主动批改利用配置镜像

此外,能够基于 KCL 的自动化能力实现如图 15 所示的一镜交付及自动化运维能力并集成到 CI/CD 当中。

图 15 典型 KCL 自动化集成链路

三、KCL 与其余申明式配置的比照​

3.1 vs. JSON/YAML​

YAML/JSON 配置等适宜小规模的配置场景,对于大规模且须要频繁批改的云原生配置场景,比拟适宜 KCL 比拟适宜,其中波及到次要差别是配置数据抽象与开展的差别:

  • 对于 JSON/YAML 等动态配置数据开展的益处是:简略、易读、易于解决,然而随着动态配置规模的减少,当配置规模较大时,JSON/YAML 等文件维护和浏览配置很艰难,因为重要的配置信息被 吞没在了大量不相干的反复细节 中。
  • 对于应用 KCL 语言进行配置形象的益处是:对于静态数据,形象一层的益处这意味着整体零碎具备 部署的灵活性,不同的配置环境、配置租户、运行时可能会对静态数据具备不同的要求,甚至不同的组织可能有不同的标准和产品要求,能够应用 KCL 将最须要、最常批改的配置裸露给用户,对差异化的配置进行形象,形象的益处是能够反对不同的配置需要。并且借助 KCL 语言级别的自动化集成能力,还能够很好地反对不同的语言,不同的配置 UI 等。

3.2 vs. Kustomize​

Kustomize 的外围能力是其 Overlay 能力,并 Kustomize 反对文件级的笼罩,然而存在会存在多个笼罩链条的问题,因为找到具体字段值的申明并不能保障这是最终值,因为其余中央呈现的另一个具体值能够笼罩它,对于简单的场景,Kustomize 文件的继承链检索往往不如 KCL 代码继承链检索不便,须要认真思考指定的配置文件笼罩程序。此外,Kustomize 不能解决 YAML 配置编写、配置束缚校验和模型形象与开发等问题,较为实用于简略的配置场景,当配置组件增多时,对于配置的批改依然会陷入大量反复不相干的配置细节中,并且在 IDE 中不能很好地显示配置之间的依赖和笼罩关系状况,只能通过搜寻 / 替换等批量批改配置。

在 KCL 中,配置合并的操作能够细粒度到代码中每一个配置字段,并且能够灵便的设置合并策略,并不局限于资源整体,并且通过 KCL 的 import 能够动态剖析出配置之间的依赖关系。

3.3 vs. HCL​

3.3.1 性能比照​

HCL KCL
建模能力 通过 Terraform Go Provider Schema 定义,在用户界面不间接感知,此外编写简单的 object 和必选 / 可选字段定义时用户界面较为繁琐 通过 KCL Schema 进行建模,通过语言级别的工程和局部面向对象个性,能够实现较高的模型形象
束缚能力 通过 Variable 的 condition 字段对动静参数进行束缚,Resource 自身的束缚须要通过 Go Provider Schema 定义或者联合 Sentinel/Rego 等策略语言实现,语言自身的残缺能力不能自闭环,且实现形式不对立 以 Schema 为外围,在进行建模的同时定义其束缚,在 KCL 外部自闭环并一对立形式实现,反对多种束缚函数编写,反对可选 / 必选字段定义
扩展性 Terraform HCL 通过分文件进行 Override, 模式比拟固定,能力受限。 KCL 能够自定义配置分块编写形式和多种合并策略,能够满足简单的多租户、多环境配置场景需要
语言化编写能力 编写简单的对象定义和必选 / 可选字段定义时用户界面较为繁琐 简单的构造定义、束缚场景编写简略,不借助其余外围 GPL 或工具,语言编写自闭环

3.3.2 举例​

Terraform HCL Variable 束缚校验编写 vs. KCL Schema 申明式束缚校验编写

  • HCL
variable "subnet_delegations" {
  type = list(object({
    name               = string
    service_delegation = object({
      name    = string
      actions = list(string)
    })
  }))
  default     = null
  validation {condition = var.subnet_delegations == null ? true : alltrue([for d in var.subnet_delegations : (d != null)])
  }
  validation {condition = var.subnet_delegations == null ? true : alltrue([for n in var.subnet_delegations.*.name : (n != null)])
  }
  validation {condition = var.subnet_delegations == null ? true : alltrue([for d in var.subnet_delegations.*.service_delegation : (d != null)])
  }
  validation {condition = var.subnet_delegations == null ? true : alltrue([for n in var.subnet_delegations.*.service_delegation.name : (n != null)])
  }
}
  • KCL
schema SubnetDelegation:
    name: str
    service_delegation: ServiceDelegation

schema ServiceDelegation:
    name: str
    actions?: [str]  # 应用 ? 标记可选属性

subnet_delegations: [SubnetDelegation] = option("subnet_delegations")

此外,KCL 还能够像高级语言一样写类型,写继承,写内置的束缚,这些性能是 HCL 所不具备的

Terraform HCL 函数 vs. KCL Lambda 函数编写

  • 正如 https://www.terraform.io/language/functions 文档和 https://gi… 中展现的那样,Terraform HCL 提供了丰盛的内置函数用于提供,然而并不反对用户在 Terraform 中应用 HCL 自定义函数 (或者须要编写简单的 Go Provider 来模仿一个用户的本地自定义函数);而 KCL 不仅反对用户应用 lambda 关键字间接在 KCL 代码中自定义函数,还反对应用 Python, Go 等语言为 KCL 编写插件函数
  • KCL 自定义定义函数并调用
add_func = lambda x: int, y: int -> int {x + y}
two = add_func(1, 1)  # 2

HCL 删除 null 值与 KCL 应用 -n 编译参数删除 null 值

  • HCL
variable "conf" {
  type = object({
    description = string
    name        = string
    namespace   = string
    params = list(object({default     = optional(string)
      description = string
      name        = string
      type        = string
    }))
    resources = optional(object({
      inputs = optional(list(object({
        name = string
        type = string
      })))
      outputs = optional(list(object({
        name = string
        type = string
      })))
    }))
    results = optional(list(object({
      name        = string
      description = string
    })))
    steps = list(object({args    = optional(list(string))
      command = optional(list(string))
      env = optional(list(object({
        name  = string
        value = string
      })))
      image = string
      name  = string
      resources = optional(object({
        limits = optional(object({
          cpu    = string
          memory = string
        }))
        requests = optional(object({
          cpu    = string
          memory = string
        }))
      }))
      script     = optional(string)
      workingDir = string
    }))
  })
}

locals {
  conf = merge(defaults(var.conf, {}),
    {for k, v in var.conf : k => v if v != null},
    {resources = { for k, v in var.conf.resources : k => v if v != null} },
    { steps = [for step in var.conf.steps : merge({ resources = {} },
      {for k, v in step : k => v if v != null},
    )] },
  )
}
  • KCL (编译参数增加 -n 疏忽 null 值)
schema Param:
    default?: str
    name: str

schema Resource:
    cpu: str
    memory: str

schema Step:
    args?: [str]
    command?: [str]
    env?: {str:str}
    image: str
    name: str
    resources?: {"limits" | "requests": Resource}
    script?: str
    workingDir: str

schema K8sManifest:
    name: str
    namespace: str
    params: [Param]
    results?: [str]
    steps: [Step]

conf: K8sManifest = option("conf")

综上能够看出,在 KCL 中,通过 Schema 来申明形式定义其类型和束缚,能够看出相比于 Terraform HCL, 在实现雷同性能的状况下,KCL 的束缚能够编写的更加简略 (不须要像 Terraform 那样反复地书写 validation 和 condition 字段),并且额定提供了字段设置为可选的能力 (?运算符,不像 Terraform 配置字段默认可空,KCL Schema 字段默认必选),构造更加明显,并且能够在代码层面间接取得类型检查和束缚校验的能力。

3.4 vs. CUE​

3.4.1 性能比照​

CUE KCL
建模能力 通过 Struct 进行建模,无继承等个性,当模型定义之间无抵触时能够实现较高的形象。因为 CUE 在运行时进行所有的束缚查看,在大规模建模场景可能存在性能瓶颈 通过 KCL Schema 进行建模,通过语言级别的工程和局部面向对象个性(如单继承),能够实现较高的模型形象。KCL 是动态编译型语言,对于大规模建模场景开销较小
束缚能力 CUE 将类型和值合并到一个概念中,通过各种语法简化了束缚的编写,比方不须要泛型和枚举,求和类型和空值合并都是一回事 KCL 提供了跟更丰盛的 check 申明式束缚语法,编写起来更加容易,对于一些配置字段组合束缚编写更加简略(能力上比 CUE 多了 if guard 组合束缚,all/any/map/filter 等汇合束缚编写形式,编写更加容易)
分块编写能力 反对语言外部配置合并,CUE 的配置合并是齐全幂等的,对于满足简单的多租户、多环境配置场景的笼罩需要可能无奈满足 KCL 能够自定义配置分块编写形式和多种合并策略,KCL 同时反对幂等和非幂等的合并策略, 能够满足简单的多租户、多环境配置场景需要
语言化编写能力 对于简单的循环、条件束缚场景编写简单,对于须要进行配置准确批改的编写场景较为繁琐 简单的构造定义、循环、条件束缚场景编写简略

3.4.2 举例​

CUE 束缚校验编写 vs. KCL Schema 申明式束缚校验编写及配置分块编写能力

CUE (执行命令 cue export base.cue prod.cue)

  • base.cue
// base.cue
import "list"

#App: {
    domainType: "Standard" | "Customized" | "Global",
    containerPort: >=1 & <=65535,
    volumes: [...#Volume],
    services: [...#Service],
}

#Service: {
    clusterIP: string,
    type: string,

    if type == "ClusterIP" {clusterIP: "None"}
}

#Volume: {
    container: string | *"*"  // The default value of `container` is "*"
    mountPath: string,
    _check: false & list.Contains(["/", "/boot", "/home", "dev", "/etc", "/root"], mountPath),
}

app: #App & {
    domainType: "Standard",
    containerPort: 80,
    volumes: [
        {mountPath: "/tmp"}
    ],
    services: [
        {
            clusterIP: "None",
            type: "ClusterIP"
        }
    ]
}
  • prod.cue
// prod.cue
app: #App & {containerPort: 8080,  // error: app.containerPort: conflicting values 8080 and 80:}

KCL (执行命令 kcl base.k prod.k)

  • base.k
# base.k
schema App:
    domainType: "Standard" | "Customized" | "Global"
    containerPort: int
    volumes: [Volume]
    services: [Service]

    check:
        1 <= containerPort <= 65535

schema Service:
    clusterIP: str
    $type: str

    check:
        clusterIP == "None" if $type == "ClusterIP"

schema Volume:
    container: str = "*"  # The default value of `container` is "*"
    mountPath: str

    check:
        mountPath not in ["/", "/boot", "/home", "dev", "/etc", "/root"]

app: App {
    domainType = "Standard"
    containerPort = 80
    volumes = [
        {mountPath = "/tmp"}
    ]
    services = [
        {
            clusterIP = "None"
            $type = "ClusterIP"
        }
    ]
}
  • prod.k
# prod.k
app: App {
    # 能够应用 = 属性运算符对 base app 的 containerPort 进行批改
    containerPort = 8080
    # 能够应用 += 属性运算符对 base app 的 volumes 进行增加
    # 此处示意在 prod 环境减少一个 volume, 一共两个 volume
    volumes += [
        {mountPath = "/tmp2"}
    ]
}

此外因为 CUE 的幂等合并个性,在场景上并无奈应用相似 kustomize 的 overlay 配置笼罩和 patch 等能力,比方上述的 base.cue 和 prod.cue 一起编译会报错。

3.5 Performance​

在代码规模较大或者计算量较高的场景状况下 KCL 比 CUE/Jsonnet/HCL 等语言性能更好 (CUE 等语言受限于运行时束缚查看开销,而 KCL 是一个动态编译型语言)

  • CUE (test.cue)
import "list"

temp: {for i, _ in list.Range(0, 10000, 1) {"a(i)": list.Max([1, 2])
        }
}
  • KCL (test.k)
a = lambda x: int, y: int -> int {max([x, y])
}
temp = {"a${i}": a(1, 2) for i in range(10000)}
  • Jsonnet (test.jsonnet)
local a(x, y) = std.max(x, y);
{temp: {["a%d" % i]: a(1, 2) for i in std.range(0, 10000)},
}
  • Terraform HCL (test.tf, 因为 terraform range 函数只反对最多 1024 个迭代器,将 range(10000) 拆分为 10 个子 range)
output "r1" {value = {for s in range(0, 1000) : format("a%d", s) => max(1, 2)}
}
output "r2" {value = {for s in range(1000, 2000) : format("a%d", s) => max(1, 2)}
}
output "r3" {value = {for s in range(1000, 2000) : format("a%d", s) => max(1, 2)}
}
output "r4" {value = {for s in range(2000, 3000) : format("a%d", s) => max(1, 2)}
}
output "r5" {value = {for s in range(3000, 4000) : format("a%d", s) => max(1, 2)}
}
output "r6" {value = {for s in range(5000, 6000) : format("a%d", s) => max(1, 2)}
}
output "r7" {value = {for s in range(6000, 7000) : format("a%d", s) => max(1, 2)}
}
output "r8" {value = {for s in range(7000, 8000) : format("a%d", s) => max(1, 2)}
}
output "r9" {value = {for s in range(8000, 9000) : format("a%d", s) => max(1, 2)}
}
output "r10" {value = {for s in range(9000, 10000) : format("a%d", s) => max(1, 2)}
}
  • 运行工夫(思考到生产环境的理论资源开销,本次测试以单核为准)
环境 KCL v0.4.3 运行工夫 (蕴含编译 + 运行工夫) CUE v0.4.3 运行工夫 (蕴含编译 + 运行工夫) Jsonnet v0.18.0 运行工夫 (蕴含编译 + 运行工夫) HCL in Terraform v1.3.0 运行工夫 (蕴含编译 + 运行工夫)
OS: macOS 10.15.7; CPU: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz; Memory: 32 GB 2400 MHz DDR4; 不开启 NUMA 440 ms (kclvm_cli run test.k) 6290 ms (cue export test.cue) 3340 ms (jsonnet test.jsonnet) 1774 ms (terraform plan -parallelism=1)

综上能够看出:CUE 和 KCL 均能够笼罩到绝大多数配置校验场景,并且均反对属性类型定义、配置默认值、束缚校验等编写,然而 CUE 对于不同的约束条件场景无对立的写法,且不能很好地透出校验谬误,KCL 应用 check 关键字作对立解决,反对用户自定义谬误输入。

另一个简单的例子​

应用 KCL 和 CUE 编写 Kubernetes 配置

  • CUE (test.cue)
package templates

import (apps "k8s.io/api/apps/v1")

deployment: apps.#Deployment

deployment: {
 apiVersion: "apps/v1"
 kind:       "Deployment"
 metadata: {
  name:   "me"
  labels: me: "me"
 }
}
  • KCL (test.k)
import kubernetes.api.apps.v1

deployment = v1.Deployment {
    metadata.name = "me"
    metadata.labels.name = "me"
}
环境 KCL v0.4.3 运行工夫 (蕴含编译 + 运行工夫) CUE v0.4.3 运行工夫 (蕴含编译 + 运行工夫)
OS: macOS 10.15.7; CPU: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz; Memory: 32 GB 2400 MHz DDR4; no NUMA 140 ms (kclvm_cli run test.k) 350 ms (cue export test.cue)

四、小结​

文本对申明式配置技术做了整体概述,其中重点论述了 KCL 概念、外围设计、应用场景以及与其余配置语言的比照,冀望帮忙大家更好的了解申明式配置技术及 KCL 语言。更多 KCL 的概念、背景、设计与用户案例等相干内容,欢送拜访 KCL 网站

五、参考​

  • KusionStack Cloud Native Configuration Practice Blog: https://kusionstack.io/blog/2021-kusion-intro
  • Terraform Language: https://www.terraform.io/language
  • Terraform Provider Kubernetes: https://github.com/hashicorp/terraform-provider-kubernetes
  • Terraform Provider AWS: https://github.com/hashicorp/terraform-provider-aws
  • Pulumi: https://www.pulumi.com/docs/
  • Pulumi vs. Terraform: https://www.pulumi.com/docs/intro/vs/terraform/
  • Google SRE Work Book Configuration Design: https://sre.google/workbook/configuration-design/
  • Google Borg Paper: https://storage.googleapis.com/pub-tools-public-publication-d…
  • Holistic Configuration Management at Facebook: https://sigops.org/s/conferences/sosp/2015/current/2015-Monte…
  • JSON Spec: https://www.json.org/json-en.html
  • YAML Spec: https://yaml.org/spec/
  • GCL: https://github.com/rix0rrr/gcl
  • HCL: https://github.com/hashicorp/hcl
  • CUE: https://github.com/cue-lang/cue
  • Jsonnet: https://github.com/google/jsonnet
  • Dhall: https://github.com/dhall-lang/dhall-lang
  • Thrift: https://github.com/Thriftpy/thriftpy2
  • Kustomize: https://kustomize.io/
  • Kube-linter: https://github.com/stackrox/kube-linter
  • Checkov: https://github.com/bridgecrewio/checkov
  • KCL Documents: https://kcl-lang.io/docs/reference/lang/tour
  • How Terraform Works: A Visual Intro: https://betterprogramming.pub/how-terraform-works-a-visual-in…
  • How Terraform Works: Modules Illustrated: https://awstip.com/terraform-modules-illustrate-26cbc48be83a
  • Helm: https://helm.sh/
  • Helm vs. Kustomize: https://harness.io/blog/helm-vs-kustomize
  • KubeVela: https://kubevela.io/docs/

正文完
 0