文字僅用於澄清宣告式設定技術概述,KCL 概念以及核心設計,以及與其他設定語言的對比。
軟體不是一成不變的,每天有成千上萬的設定更新,並且設定本身也在逐漸演進,對規模化效率有較高的訴求
設定更新越來越頻繁:設定提供了一種改變系統功能的低開銷方式,不斷髮展的業務需求、基礎設施要求和其他因素意味著系統需要不斷變化。
設定規模越來越大:一份設定往往要分發到不同的雲站點、不同的租戶、不同的環境等。
設定場景廣泛:應用設定、資料庫設定、網路設定、監控設定等。
設定格式繁多:JSON, YAML, XML, TOML, 各種設定模版如 Java Velocity, Go Template 等。
設定的穩定性至關重要,系統宕機或出錯的一個最主要原因是有大量工程師進行頻繁的實時設定更新,表 1 示出了幾個由於設定導致的系統出錯事件。
時間 | 事件 |
---|---|
2021 年 7 月 | 中國 Bilibili 公司由於 SLB Lua 設定計算出錯陷入死迴圈導致網站宕機 |
2021 年 10 月 | 韓國 KT 公司由於路由設定錯誤導致在全國範圍內遭受重大網路中斷 |
表 1 設定導致的系統出錯事件
雲原生時代帶來了如雨後春筍般的技術發展,出現了大量面向終態的宣告式設定實踐,如圖 1 所示,宣告式設定一般可分為如下幾種方式。 圖 1 宣告式設定方式分類
結構化的 KV 可以滿足最小化資料宣告需求,比如數位、字串、列表和字典等資料型別,並且隨著雲原生技術快速發展應用,宣告式 API 可以滿足 X as Data 發展的訴求,並且面向機器可讀可寫,面向人類可讀。其優劣如下:
優勢
語法簡單,易於編寫和閱讀
多語言 API 豐富
有各種 Path 工具方便資料查詢,如 XPath, JsonPath 等
痛點
冗餘資訊多:當設定規模較大時,維護和閱讀設定很困難,因為重要的設定資訊被淹沒在了大量不相關的重複細節中
功能性不足
約束校驗能力
複雜邏輯編寫能力
測試、偵錯能力
不易抽象和複用
Kustomize 的 Patch 比較客製化,基本是通過固定幾種 Patch Merge 策略
結構化 KV 的代表技術有
圖 2 Kustomize 典型工作方式
模版化 (Templated) 的 KV 賦予靜態設定資料動態引數的能力,可以做到一份模版+動態引數輸出不同的靜態設定資料。其優劣如下:
優勢
簡單的設定邏輯,迴圈支援
支援外部動態引數輸入模版
痛點
容易落入所有設定引數都是模版引數的陷阱
當設定規模變大時,開發者和工具都難以維護和分析它們
模版化代表技術有:
圖 3 Helm Jekins Package ConfigMap 設定模版
Configuration as Code (CaC), 使用程式碼產生設定,就像工程師們只需要寫高階 GPL 程式碼,而不是手工編寫容易出錯而且難以理解的伺服器二進位制程式碼一樣。設定變更同程式碼變更同樣嚴肅地對待,同樣可以執行單元測試、整合測試等。程式碼模組化和重用是維護設定程式碼比手動編輯 JSON/YAML 等組態檔更容易的一個關鍵原因。其優劣如下:
優勢
必要的程式設計能力(變數定義、邏輯判斷、迴圈、斷言等)
程式碼模組化與抽象(支援定義資料模版,並用模版得到新的設定資料)
可以抽象設定模版+並使用設定覆蓋
痛點
型別檢查不足
執行時錯誤
約束能力不足
程式碼化 KV 的代表技術有:
型別化的 KV,基於程式碼化 KV,多了型別檢查和約束的能力,其優劣如下:
優勢
設定合併完全冪等,天然防止設定衝突
豐富的設定約束語法用於編寫約束
將型別和值約束編寫抽象為同一種形式,編寫簡單
設定順序無關
痛點
圖合併和冪等合併等概念複雜,使用者理解成本較高
型別和值混合定義提高抽象程度的同時提升了使用者的理解成本,並且所有約束在執行時進行檢查,大規模設定程式碼下有效能瓶頸
對於想要設定覆蓋、修改的多租戶、多環境場景難以實現
對於帶條件的約束場景,定義和校驗混合定義編寫使用者介面不友好
型別化 KV 的代表技術有:
模型化的 KV 在程式碼化和型別化 KV 的基礎上以高階語言建模能力為核心描述,期望做到模型的快速編寫與分發,其優劣如下:
優勢
引入可分塊、可延伸的 KV 設定塊編寫方式
類高階程式語言的編寫、測試方式
語言內建的強校驗、強約束支援
面向人類可讀可寫,面向機器部分可讀可寫
不足
擴充套件新模型及生態構建需要一定的研發成本,或者使用工具對社群中已有的 JsonSchema 和 OpenAPI 模型進行模型轉換、遷移和整合。
模型化 KV 的代表技術有:
圖 4 使用 KCL 編寫應用交付設定程式碼
此外,從不同宣告式設定方式的使用場景出發
不同於社群中的其他同型別領域語言,KCL 是一種面向應用研發人員並採納了現代語言設計和技術的靜態強型別編譯語言
注意,本文將不會討論通用語言編寫設定的情況,通用語言一般是 Overkill 的,即遠遠超過了需要解決的問題,通用語言存在各式各樣的安全問題,比如能力邊界問題 (啟動本地執行緒、存取 IO, 網路,程式碼死迴圈等不安全隱患),比如像音樂領域就有專門的音符去表示音樂,方便學習與交流,不是一般文字語言可以表述清楚的。
此外,通用語言因為本身就樣式繁多,存在統一維護、管理和自動化的成本,通用語言一般用來編寫使用者端執行時,是伺服器端執行時的一個延續,不適合編寫與執行時無關的設定,最終被編譯為二進位制從程序啟動,穩定性和擴充套件性不好控制,而設定語言往往編寫的是資料,再搭配以簡單的邏輯,描述的是期望的最終結果,然後由編譯器或者引擎來消費這個期望結果。
Kusion 設定語言(KCL)是一個開源的基於約束的記錄及函數語言。KCL 通過成熟的程式語言技術和實踐來改進對大量繁雜設定的編寫,致力於構建圍繞設定的更好的模組化、擴充套件性和穩定性,更簡單的邏輯編寫,以及更快的自動化整合和良好的生態延展性。
KCL 的核心特性是其建模和約束能力,KCL 核心功能基本圍繞 KCL 這個兩個核心特性展開,此外 KCL 遵循以使用者為中心的設定理念而設計其核心特性,可以從兩個方面理解:
不論是以何為中心的設定檢視,對於程式碼而言(包括設定程式碼)都存在對設定資料約束的需求,比如型別約束、設定欄位必選/可選約束、範圍約束、不可變性約束等,這也是 KCL 致力於解決的核心問題之一。綜上,KCL 是一個開源的基於約束和宣告的函數式語言,KCL 主要包含如圖 5 所示的核心特性:
圖 5 KCL 核心特性
圖 6 KCL 語言核心設計
更多語言設計和能力詳見 KCL 檔案,儘管 KCL 不是通用語言,但它有相應的應用場景,如圖 6 所示,研發者可以通過 KCL 編寫設定(config) 、模型(schema) 、函數(lambda) 及規則(rule) ,其中 Config 用於定義資料,Schema 用於對資料的模型定義進行描述,Rule 用於對資料進行校驗,並且 Schema 和 Rule 還可以組合使用用於完整描述資料的模型及其約束,此外還可以使用 KCL 中的 lambda 純函數進行資料程式碼組織,將常用程式碼封裝起來,在需要使用時可以直接呼叫。
對於使用場景而言,KCL 可以進行結構化 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 校驗能力構建的視覺化產品介面
如圖 9 所示,藉助 KCL 語言豐富的特性及 KCL OpenAPI 等工具,可以將社群中廣泛的、設計良好的模型直接整合到 KCL 中(比如 K8s 資源模型 CRD),使用者也可以根據自己的業務場景設計、實現自己的 KCL 模型 (庫) ,形成一整套領域模型架構交由其他設定終端使用者使用。
圖 9 KCL 複雜設定建模的一般方式
基於此,可以像圖 10 示出的那樣用一個大的 Konfig 倉庫 管理全部的 KCL 設定程式碼,將業務設定程式碼 (應用程式碼)、基礎設定程式碼 (核心模型+底層模型)在一個大庫中,方便程式碼間的版本依賴管理,自動化系統處理也比較簡單,定位唯一程式碼庫的目錄及檔案即可,程式碼互通,統一管理,便於查詢、修改、維護,可以使用統一的 CI/CD 流程進行設定管理(此外,大庫模式也是 Google 等頭部網際網路公司內部實踐的模式)。
圖 10 使用 KCL 的語言能力整合領域模型並抽象使用者模型並使用
如圖 11 所示,在 KCL 中可以通過豐富的強約束校驗手段避免設定錯誤:
圖 11 KCL 強約束校驗手段
App
中定義對 containerPort
、services
、volumes
的校驗規則,目前校驗規則在執行時執行判斷,後續 KCL 會嘗試通過編譯時的靜態分析對規則進行判斷從而發現問題。
圖 12 帶規則約束的 KCL 程式碼校驗
KCL 提供了設定分塊編寫及自動合併設定的能力,並且支援冪等合併、修補程式合併和唯一設定合併等策略。冪等合併中的多份設定需要滿足交換律,並且需要開發人員手動處理基礎設定和不同環境設定衝突。 修補程式合併作為一個覆蓋功能,包括覆蓋、刪除和新增。唯一的設定要求設定塊是全域性唯一的並且未修改或以任何形式重新定義。 KCL 通過多種合併策略簡化了使用者側的協同開發,減少了設定之間的耦合。
如圖 13 所示,對於存在基線設定、多環境和多租戶的應用設定場景,有一個基本設定 base.k。 開發和 SRE 分別維護生產和開發環境的設定 base.k 和 prod.k,他們的設定互不影響,由 KCL 編譯器合併成一個 prod 環境的等效設定程式碼。
圖 13 多環境場景設定分塊編寫範例
在 KCL 中提供了很多自動化相關的能力,主要包括工具和多語言 API。 通過 package_identifier : key_identifier
的模式支援對任意設定鍵值的索引,從而完成對任意鍵值的增刪改查。比如圖 14 所示修改某個應用設定的映象內容,可以直接執行如下指令修改映象,修改前後的 diff 如下圖所示。
圖 14 使用 KCL CLI/API 自動修改應用設定映象
此外,可以基於 KCL 的自動化能力實現如圖 15 所示的一鏡交付及自動化運維能力並整合到 CI/CD 當中。
圖 15 典型 KCL 自動化整合鏈路
YAML/JSON 設定等適合小規模的設定場景,對於大規模且需要頻繁修改的雲原生設定場景,比較適合 KCL 比較適合,其中涉及到主要差異是設定資料抽象與展開的差異:
Kustomize 的核心能力是其 Overlay 能力,並 Kustomize 支援檔案級的覆蓋,但是存在會存在多個覆蓋鏈條的問題,因為找到具體欄位值的宣告並不能保證這是最終值,因為其他地方出現的另一個具體值可以覆蓋它,對於複雜的場景,Kustomize 檔案的繼承鏈檢索往往不如 KCL 程式碼繼承鏈檢索方便,需要仔細考慮指定的組態檔覆蓋順序。此外,Kustomize 不能解決 YAML 設定編寫、設定約束校驗和模型抽象與開發等問題,較為適用於簡單的設定場景,當設定元件增多時,對於設定的修改仍然會陷入大量重複不相關的設定細節中,並且在 IDE 中不能很好地顯示設定之間的依賴和覆蓋關係情況,只能通過搜尋/替換等批次修改設定。
在 KCL 中,設定合併的操作可以細粒度到程式碼中每一個設定欄位,並且可以靈活的設定合併策略,並不侷限於資源整體,並且通過 KCL 的 import 可以靜態分析出設定之間的依賴關係。
HCL | KCL | |
---|---|---|
建模能力 | 通過 Terraform Go Provider Schema 定義,在使用者介面不直接感知,此外編寫複雜的 object 和必選/可選欄位定義時使用者介面較為繁瑣 | 通過 KCL Schema 進行建模,通過語言級別的工程和部分物件導向特性,可以實現較高的模型抽象 |
約束能力 | 通過 Variable 的 condition 欄位對動態引數進行約束,Resource 本身的約束需要通過 Go Provider Schema 定義或者結合 Sentinel/Rego 等策略語言完成,語言本身的完整能力不能自閉環,且實現方式不統一 | 以 Schema 為核心,在進行建模的同時定義其約束,在 KCL 內部自閉環並一統一方式實現,支援多種約束函數編寫,支援可選/必選欄位定義 |
擴充套件性 | Terraform HCL 通過分檔案進行 Override, 模式比較固定,能力受限。 | KCL 可以自定義設定分塊編寫方式和多種合併策略,可以滿足複雜的多租戶、多環境設定場景需求 |
語言化編寫能力 | 編寫複雜的物件定義和必選/可選欄位定義時使用者介面較為繁瑣 | 複雜的結構定義、約束場景編寫簡單,不借助其他外圍 GPL 或工具,語言編寫自閉環 |
Terraform HCL Variable 約束校驗編寫 vs. KCL Schema 宣告式約束校驗編寫
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)])
}
}
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 函數編寫
add_func = lambda x: int, y: int -> int {
x + y
}
two = add_func(1, 1) # 2
HCL 刪除 null 值與 KCL 使用 -n 編譯引數刪除 null 值
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 },
)] },
)
}
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 欄位預設必選),結構更加分明,並且可以在程式碼層面直接獲得型別檢查和約束校驗的能力。
CUE | KCL | |
---|---|---|
建模能力 | 通過 Struct 進行建模,無繼承等特性,當模型定義之間無衝突時可以實現較高的抽象。由於 CUE 在執行時進行所有的約束檢查,在大規模建模場景可能存在效能瓶頸 | 通過 KCL Schema 進行建模,通過語言級別的工程和部分物件導向特性(如單繼承),可以實現較高的模型抽象。 KCL 是靜態編譯型語言,對於大規模建模場景開銷較小 |
約束能力 | CUE 將型別和值合併到一個概念中,通過各種語法簡化了約束的編寫,比如不需要泛型和列舉,求和型別和空值合併都是一回事 | KCL 提供了跟更豐富的 check 宣告式約束語法,編寫起來更加容易,對於一些設定欄位組合約束編寫更加簡單(能力上比 CUE 多了 if guard 組合約束,all/any/map/filter 等集合約束編寫方式,編寫更加容易) |
分塊編寫能力 | 支援語言內部設定合併,CUE 的設定合併是完全冪等的,對於滿足複雜的多租戶、多環境設定場景的覆蓋需求可能無法滿足 | KCL 可以自定義設定分塊編寫方式和多種合併策略,KCL 同時支援冪等和非冪等的合併策略,可以滿足複雜的多租戶、多環境設定場景需求 |
語言化編寫能力 | 對於複雜的迴圈、條件約束場景編寫複雜,對於需要進行設定精確修改的編寫場景較為繁瑣 | 複雜的結構定義、迴圈、條件約束場景編寫簡單 |
CUE 約束校驗編寫 vs. KCL Schema 宣告式約束校驗編寫及設定分塊編寫能力
CUE (執行命令 cue export base.cue prod.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
app: #App & {
containerPort: 8080, // error: app.containerPort: conflicting values 8080 and 80:
}
KCL (執行命令 kcl base.k prod.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
app: App {
# 可以使用 = 屬性運運算元對 base app 的 containerPort 進行修改
containerPort = 8080
# 可以使用 += 屬性運運算元對 base app 的 volumes 進行新增
# 此處表示在 prod 環境增加一個 volume, 一共兩個 volume
volumes += [
{
mountPath = "/tmp2"
}
]
}
此外由於 CUE 的冪等合併特性,在場景上並無法使用類似 kustomize 的 overlay 設定覆蓋和 patch 等能力,比如上述的 base.cue 和 prod.cue 一起編譯會報錯。
在程式碼規模較大或者計算量較高的場景情況下 KCL 比 CUE/Jsonnet/HCL 等語言效能更好 (CUE 等語言受限於執行時約束檢查開銷,而 KCL 是一個靜態編譯型語言)
import "list"
temp: {
for i, _ in list.Range(0, 10000, 1) {
"a(i)": list.Max([1, 2])
}
}
a = lambda x: int, y: int -> int {
max([x, y])
}
temp = {"a${i}": a(1, 2) for i in range(10000)}
local a(x, y) = std.max(x, y);
{
temp: {["a%d" % i]: a(1, 2) for i in std.range(0, 10000)},
}
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 設定
package templates
import (
apps "k8s.io/api/apps/v1"
)
deployment: apps.#Deployment
deployment: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: {
name: "me"
labels: me: "me"
}
}
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 網站