Skip to main content

· 阅读需要 1 分钟

简介

Kustomize 提供了一种无需模板和即可自定义 Kubernetes 资源基础配置和差异化配置的解决方案,通过文件级的 YAML 配置方式完成配置合并或覆盖。在 Kustomize 中用户需要更详细地了解将要发生更改的内容和位置,对于复杂递归过深的基础 YAML 可能不太容易通过选择器来匹配 Kustomize 文件。

而在 KCL 中,用户可以直接把对应代码需要修改的配置书写在对应的地方,免去了阅读基础 YAML 的成本,同时能够通过代码的方式复用配置片段,避免 YAML 配置的大量复制粘贴,信息密度更高,也不容易出错。

下面以一个经典的 Kustomize 多环境配置管理例子详细说明 Kustomize 和 KCL 在 Kubernetes 资源配置管理上的区别。

Kustomize

Kustomize 有 base 和 overlay 的概念,bases 和 overlays 一般是一个包含 kustomization.yaml 文件的目录,一个 base 可以被多个 overlay 使用

我们可以执行如下命令行获得一个典型的 Kustomize 工程

  • 创建 base 目录并新建一个 deployment 资源
# Create a directory to hold the base
mkdir base
# Create a base/deployment.yaml
cat <<EOF > base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ldap
labels:
app: ldap
spec:
replicas: 1
selector:
matchLabels:
app: ldap
template:
metadata:
labels:
app: ldap
spec:
containers:
- name: ldap
image: osixia/openldap:1.1.11
args: ["--copy-service"]
volumeMounts:
- name: ldap-data
mountPath: /var/lib/ldap
ports:
- containerPort: 389
name: openldap
volumes:
- name: ldap-data
emptyDir: {}
EOF
# Create a base/kustomization.yaml
cat <<EOF > base/kustomization.yaml
resources:
- deployment.yaml
EOF
  • 创建一个 prod 目录并放置生产环境的配置
# Create a directory to hold the prod overlay
mkdir prod
# Create a prod/deployment.yaml
cat <<EOF > prod/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ldap
spec:
replicas: 6
template:
spec:
volumes:
- name: ldap-data
emptyDir: null
gcePersistentDisk:
readOnly: true
pdName: ldap-persistent-storage
EOF
cat <<EOF > prod/kustomization.yaml
resources:
- ../base
patchesStrategicMerge:
- deployment.yaml
EOF

此时我们可以得到一个基本的 Kustomzie 目录

.
├── base
│ ├── deployment.yaml
│ └── kustomization.yaml
└── prod
├── deployment.yaml
└── kustomization.yaml

其中,base 目录存放的是基本的 deployment 配置,prod 环境存放的是需要覆盖的 deployment 配置,在其中指定了 metadata.name 等字段用于表示对哪个资源进行覆盖

我们可以通过如下命令行显示 prod 环境的真实 deployment 配置

$ kubectl kustomize ./prod
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: ldap
name: ldap
spec:
replicas: 6
selector:
matchLabels:
app: ldap
template:
metadata:
labels:
app: ldap
spec:
containers:
- args:
- --copy-service
image: osixia/openldap:1.1.11
name: ldap
ports:
- containerPort: 389
name: openldap
volumeMounts:
- mountPath: /var/lib/ldap
name: ldap-data
volumes:
- gcePersistentDisk:
pdName: ldap-persistent-storage
readOnly: true
name: ldap-data

也可以通过如下命令行直接将配置下发到集群当中

$ kubectl apply -k ./prod

deployment.apps/ldap created

KCL

我们可以编写如下 KCL 代码并命名为 main.k

apiVersion = "apps/v1"
kind = "Deployment"
metadata = {
name = "ldap"
labels.app = "ldap"
}
spec = {
replicas = 1
# When env is prod, override the `replicas` attribute with `6`
if option("env") == "prod": replicas = 6
# Assign `metadata.labels` to `selector.matchLabels`
selector.matchLabels = metadata.labels
template.metadata.labels = metadata.labels
template.spec.containers = [
{
name = metadata.name
image = "osixia/openldap:1.1.11"
args = ["--copy-service"]
volumeMounts = [{ name = "ldap-data", mountPath = "/var/lib/ldap" }]
ports = [{ containerPort = 80, name = "openldap" }]
}
]
template.spec.volumes = [
{
name = "ldap-data"
emptyDir = {}
# When env is prod
# override the `emptyDir` attribute with `None`
# patch a `gcePersistentDisk` attribute with the value `{readOnly = True, pdName = "ldap-persistent-storage"}`
if option("env") == "prod":
emptyDir = None
gcePersistentDisk = {
readOnly = True
pdName = "ldap-persistent-storage"
}
}
]
}

上述 KCL 代码中我们分别声明了一个 Kubernetes Deployment 资源的 apiVersionkindmetadataspec 等变量,并分别赋值了相应的内容,特别地,我们将 metadata.labels 字段分别重用在 spec.selector.matchLabelsspec.template.metadata.labels 字段。可以看出,相比于 Kustomize 或者 YAML,KCL 定义的数据结构更加紧凑,而且可以通过定义局部变量实现配置重用。

在 KCL 中,我们可以通过条件语句和 option 函数动态地接收外部参数,为不同的环境需要设置不同的配置值生成不同环境的资源。比如对于如上代码,我们编写了一个条件语句并输入一个名为 env 的动态参数,当 envprod 时,我们将 replicas 字段由 1 覆盖为 6,并且对名为 ldap-data 的 volume 配置进行一些调整,如将 emptyDir 字段修改为 None, 增加 gcePersistentDisk 的配置值等。

可以使用如下命令查看不同环境配置的 diff

diff \
<(kcl main.k) \
<(kcl main.k -D env=prod) |\
more

输出如下:

8c8
< replicas: 1
---
> replicas: 6
30c30,33
< emptyDir: {}
---
> emptyDir: null
> gcePersistentDisk:
> readOnly: true
> pdName: ldap-persistent-storage

可以看到生产环境的配置和基本配置的 diff 主要在于 replicasemptyDir 等字段,与预期相符

此外,我们可以使用 KCL 命令行工具的 -o 参数将编译产生的 YAML 输出到文件中,并查看文件之间的 diff

# Generate base deployment
kcl main.k -o deployment.yaml
# Generate prod deployment
kcl main.k -o prod-deployment.yaml -D env=prod
# Diff prod deployment and base deployment
diff prod-deployment.yaml deployment.yaml

当然我们也可以将 KCL 工具与 kubectl 等工具结合使用,将生产环境的配置下发到集群当中

$ kcl main.k -D env=prod | kubectl apply -f -

deployment.apps/ldap created

可以从命令行的结果看出与我们使用直接使用 Kustomize 配置和 kubectl apply 的一个 Deployment 体验完全一致,并且无更多的副作用

最后,通过 kubectl 检查部署状态

$ kubectl get deploy

NAME READY UP-TO-DATE AVAILABLE AGE
ldap 0/6 6 0 15s

小结

本期内容大概简单介绍了用 KCL 编写复杂多环境 Kubernetes 配置的快速入门和使用 Kustomize 工具进行 Kubernetes 多环境配置管理的对比,可以看出相比于 Kustomize, KCL 在实现配置复用和覆盖的基础上,通过代码化的方式减少了配置文件的个数和代码行数,提升了信息密度比,并且同 Kustomize 一样是一个纯客户端方案,并不会对集群有额外依赖或造成负担。

· 阅读需要 1 分钟

KCL 是什么?

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

KCL Go SDK 是什么?

kclvm 是一个 KCL 语言的运行时库,它提供了一个与 KCL 编译器交互的编程接口。它是一个客户端库,可用于对 KCL源代码执行各种操作,例如 执行、格式化等。KCL Go SDK是 kclvm 的 Go 语言包装,提供了 Go 语言的 SDK,方便了在云原生环境下 KCL 语言的集成。

目前,KCL Go SDK 客户端构建在 kclvm json2 rpc API 之上,这意味着它使用和其他语言的 kclvm 客户端使用的相同 API 与 KCL 源代码交互,这与其他语言的 KCL SDK 工作方式类似,但提供了更加友好的 Go 语言风格的包装。

新版本 KCL Go SDK 解决了什么问题?

KCL 作为一门配置型语言,和云原生领域有着极其紧密的联系,而另一方面,Go 语言已经成为了云原生领域通用编程语言的事实标准。在这样的背景下,开发 KCL 的 Go SDK 来完成 KCL 编译器与 Go 语言的直接交互就有了必要,这也是KCL Go SDK诞生的原因。

最初版本的 KCL 编译器及运行时使用 python 编写,由于 python 语言本身的性能问题和其动态语言的特性,初版 KCL 语言的运行速度和安全性都有很大提升空间。出于安全与效率问题的考虑,后续版本 KCL 编译器又使用了 rust 语言编写,因此新版本的KCL Go SDK基于 Rust 实现的 KCL 核心进行包装,去除了 python 依赖,简化了安装,优化了使用体验。

新版本KCL Go SDK可以视为一个纯 Go 包使用,无需任何外置依赖,可以通过一键go install即可完成安装使用。

Go 代码如何集成 KCL

以上一节的 hello.k 为例,构建以下的 main.go 代码:

package main

import (
"fmt"

kcl "kcl-lang.io/kcl-go"
)

func main() {
yaml := kcl.MustRun("kubernetes.k", kcl.WithCode(code)).GetRawYamlResult()
fmt.Println(yaml)
}

const code = `
apiVersion = "apps/v1"
kind = "Deployment"
metadata = {
name = "nginx"
labels.app = "nginx"
}
spec = {
replicas = 3
selector.matchLabels = metadata.labels
template.metadata.labels = metadata.labels
template.spec.containers = [
{
name = metadata.name
image = "${metadata.name}:1.14.2"
ports = [{ containerPort = 80 }]
}
]
}
`
  • kcl.MustRun("kubernetes.k", kcl.WithCode(code)).GetRawYamlResult()运行对应的kcl源文件
  • fmt.Println(yaml)打印运行结果

总结

通过这一次的 KCL Go SDK 的版本变更,我们去除了 python 依赖并切换至性能更加优秀的 rust 运行时。文章分别简单展示了如何使用  kcl-go 命令行工具执行 KCL 源代码, 以及如何将 KCL 集成至您的 Go 程序之中。

当然除了简单的编译并运行 KCL 源码之外,KCL Go SDK 还提供了丰富的功能以方便用户更好地在 Go 中集成 KCL , 包括:

  • KCL 静态错误分析(lint与格式化)
  • KCL 依赖分析、
  • Go 结构体和 KCL Schema 互相转换等等

其他资源

感谢所有 KCL 用户和社区小伙伴在此次版本更新过程中提出的宝贵反馈与建议。受限于文章篇幅,后续我们会撰写更多 KCL v0.4.6 新版本功能解读系列文章,敬请期待!

更多其他资源请参考:

欢迎加入我们的社区进行交流 👏👏👏:https://github.com/kcl-lang/community 👏👏👏