简介

在上一篇文章中,咱们介绍了怎么运用 KCL 编写并办理 Kubernetes 装备并将装备下发到集群,这一节咱们经过与其他 Kubernetes 装备办理东西的对比方 Helm 介绍 KCL 在 Kubernetes 装备办理场景更丰富的内容。

Helm 是一个为 Kubernetes 对象生成可部署清单的东西,它承担了以两种不同形式生成终究清单的任务。Helm 是一个办理 Kubernetes 包(称为 charts)的必备模板东西。图表是 YAML 清单的模板化版别,其间混合了 Go template 的子集,它也是 Kubernetes 的包办理器,能够打包、装备和部署/应用 Helm 图表到 Kubernetes 集群。

在 KCL 中,用户能够运用更多的东西和 IDE 插件支撑直接编写装备代码文件,而不是模板文件,这些东西和插件支撑需求在相应位置的代码中进行修正,从而消除了读取 YAML 的成本。一起,用户能够经过代码重用装备片段,避免了YAML 装备的很多复制和粘贴。信息密度更高,更不容易犯错。

下面以一个经典的 Helm Chart 装备办理的比方具体说明 Kustomize 和 KCL 在 Kubernetes 资源装备办理上的差异。

Helm

Helm 具备 values.yamltemplate 的概念, 通常一个 Helm Chart 由一个包含 Chart.yaml 的路径组成。咱们能够履行如下指令取得一个典型的 Helm Chart 工程

  • 创立 workload-helm 目录来保存 chart 工程
# Create a directory to hold the chart project
mkdir workload-helm
# Create a workload-helm/Chart.yaml
cat <<EOF > workload-helm/Chart.yaml
apiVersion: v2
appVersion: 0.3.0
description: A helm chart to provision standard workloads.
name: workload
type: application
version: 0.3.0
EOF
# Create a workload-helm/values.yaml
cat <<EOF > workload-helm/values.yaml
service:
  type: ClusterIP
  ports:
    - name: www
      protocol: TCP
      port: 80
      targetPort: 80
containers:
  my-container:
    image:
      name: busybox:latest
    command: ["/bin/echo"]
    args: 
      - "-c"
      - "Hello World!"
    resources:
      limits:
        cpu: 100m
        memory: 128Mi
      requests:
        cpu: 100m
        memory: 128Mi
EOF
  • 创立模版文件夹
# Create a directory to hold templates
mkdir workload-helm/templates
# Create a workload-helm/templates/helpers.tpl
cat <<EOF > workload-helm/templates/helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "workload.name" -}}
{{- default .Release.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "workload.fullname" -}}
{{- \$name := default .Chart.Name .Values.nameOverride }}
{{- if contains \$name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name \$name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "workload.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "workload.labels" -}}
helm.sh/chart: {{ include "workload.chart" . }}
{{ include "workload.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "workload.selectorLabels" -}}
app.kubernetes.io/name: {{ include "workload.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
EOF
cat <<EOF > workload-helm/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "workload.name" . }}
  labels:
    {{- include "workload.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      {{- include "workload.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "workload.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        {{- range \$name, \$container := .Values.containers }}
        - name: {{ \$name }}
          image: "{{ $container.image.name }}"
          {{- with \$container.command }}
          command:
            {{- toYaml \$container.command | nindent 12 }}
          {{- end }}
          {{- with \$container.args }}
          args:
            {{- toYaml \$container.args | nindent 12 }}
          {{- end }}
          {{- with \$container.env }}
          env:
            {{- toYaml \$container.env | nindent 12 }}
          {{- end }}
          {{- with \$container.volumeMounts }}
          volumeMounts:
            {{- toYaml \$container.volumeMounts | nindent 12 }}
          {{- end }}
          {{- with \$container.livenessProbe }}
          livenessProbe:
            {{- toYaml \$container.livenessProbe | nindent 12 }}
          {{- end }}
          {{- with \$container.readinessProbe }}
          readinessProbe:
            {{- toYaml \$container.readinessProbe | nindent 12 }}
          {{- end }}
          {{- with \$container.resources }}
          resources:
            {{- toYaml \$container.resources | nindent 12 }}
          {{- end }}
        {{- end }}
EOF
cat <<EOF > workload-helm/templates/service.yaml
{{ if .Values.service }}
apiVersion: v1
kind: Service
metadata:
  name: {{ include "workload.name" . }}
  labels:
    {{- include "workload.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  selector:
    {{- include "workload.selectorLabels" . | nindent 4 }}
  {{- with .Values.service.ports }}
  ports:
    {{- toYaml . | nindent 4 }}
  {{- end }}
{{- end }}
EOF

能够得到如下的 Helm chart 工程

.
├── Chart.yaml
├── templates
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   └── service.yaml
└── values.yaml

咱们能够经过如下的指令渲染真实的部署装备

helm template workload-helm

能够得到如下 YAML 输出

---
# Source: workload-helm/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: release-name
  labels:
    helm.sh/chart: workload-0.3.0
    app.kubernetes.io/name: release-name
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "0.3.0"
    app.kubernetes.io/managed-by: Helm
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: release-name
    app.kubernetes.io/instance: release-name
  ports:
    - name: www
      port: 80
      protocol: TCP
      targetPort: 80
---
# Source: workload-helm/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: release-name
  labels:
    helm.sh/chart: workload-0.3.0
    app.kubernetes.io/name: release-name
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/version: "0.3.0"
    app.kubernetes.io/managed-by: Helm
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: release-name
      app.kubernetes.io/instance: release-name
  template:
    metadata:
      labels:
        app.kubernetes.io/name: release-name
        app.kubernetes.io/instance: release-name
    spec:
      containers:
        - name: my-container
          image: "busybox:latest"
          command:
            - /bin/echo
          args:
            - -c
            - Hello World!
          resources:
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi

KCL

在 KCL 中,咱们供给了与 Helm values.yaml 相似的动态装备参数 kcl.yaml 文件,咱们能够履行如下的指令取得一个典型的 KCL 工程。

  • 创立 workload-kcl 目录来保存 KCL 工程
# Create a directory to hold the KCL project
mkdir workload-kcl
# Create a workload-kcl/kcl.yaml
cat <<EOF > workload-kcl/kcl.yaml
kcl_options:
  - key: containers
    value:
      my-container:
        image:
          name: busybox:latest
        command: ["/bin/echo"]
        args: 
          - "-c"
          - "Hello World!"
        resources:
          limits:
            cpu: 100m
            memory: 128Mi
          requests:
            cpu: 100m
            memory: 128Mi
  - key: service
    value:
      type: ClusterIP
      ports:
        - name: www
          protocol: TCP
          port: 80
          targetPort: 80
EOF
  • 创立如下 KCL 文件来保存 kubernetes 资源
# Create a workload-kcl/deployment.k
cat <<EOF > workload-kcl/deployment.k
apiVersion = "apps/v1"
kind = "Deployment"
metadata = {
    name = "release-name"
    labels = {
        "app.kubernetes.io/name" = "release-name"
        "app.kubernetes.io/instance" = "release-name"
    }
}
spec = {
    selector.matchLabels = metadata.labels
    template.metadata.labels = metadata.labels
    template.spec.containers = [
        {
            name = name
            image = container.image.name
            command = container.command
            command = container.args
            env = container.env
            resources = container.resources
        } for name, container in option("containers") or {}
    ]
}
EOF
cat <<EOF > workload-kcl/service.k
apiVersion = "v1"
kind = "Service"
metadata = {
    name = "release-name"
    labels = {
        "app.kubernetes.io/name" = "release-name"
        "app.kubernetes.io/instance" = "release-name"
    }
}
spec = {
    selector.matchLabels = metadata.labels
    type = option("service", default={})?.type
    ports = option("service", default={})?.ports
}
EOF

上述 KCL 代码中咱们别离声明晰一个 Kubernetes DeploymentService 资源的 apiVersionkindmetadataspec变量,并别离赋值了相应的内容,特别地,咱们将 metadata.labels 字段别离重用在 spec.selector.matchLabelsspec.template.metadata.labels 字段。能够看出,比较于 Helm 模版 或许 YAML,KCL 界说的数据结构愈加紧凑,而且能够经过界说局部变量完成装备重用。

在 KCL 中,咱们能够经过条件语句和 option 内置函数接收动态参数,并设置不同的装备值以生成资源。

能够经过如下的指令得到 DeploymentService YAML 输出:

  • Deployment
$ kcl workload-kcl/deployment.k -Y workload-kcl/kcl.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: release-name
  labels:
    app.kubernetes.io/name: release-name
    app.kubernetes.io/instance: release-name
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: release-name
      app.kubernetes.io/instance: release-name
  template:
    metadata:
      labels:
        app.kubernetes.io/name: release-name
        app.kubernetes.io/instance: release-name
    spec:
      containers:
      - name: my-container
        image: busybox:latest
        command:
        - -c
        - Hello World!
        resources:
          limits:
            cpu: 100m
            memory: 128Mi
          requests:
            cpu: 100m
            memory: 128Mi
  • Service
$ kcl workload-kcl/service.k -Y workload-kcl/kcl.yaml
apiVersion: v1
kind: Service
metadata:
  name: release-name
  labels:
    app.kubernetes.io/name: release-name
    app.kubernetes.io/instance: release-name
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: release-name
      app.kubernetes.io/instance: release-name
  type: ClusterIP
  ports:
  - name: www
    protocol: TCP
    port: 80
    targetPort: 80

此外咱们能够经过 -D 标志设置额定的参数并覆盖 kcl.yaml 文件的装备值

$ kcl workload-kcl/service.k -Y workload-kcl/kcl.yaml -D service=None
apiVersion: v1
kind: Service
metadata:
  name: release-name
  labels:
    app.kubernetes.io/name: release-name
    app.kubernetes.io/instance: release-name
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: release-name
      app.kubernetes.io/instance: release-name
  type: null
  ports: null

小结

能够看出,与 Helm 比较,KCL 经过在装备重用和覆盖的基础上生成代码,减少了装备文件和代码行的数量。与 Helm 相同,它是一个纯客户端解决方案,能够将装备和策略验证尽可能地左移,而不会对集群形成额定的依靠或负担,或许乃至没有 Kubernetes 集群时也能够经过 KCL Schema 等特性对 YAML 进行充分验证和测试。

Helm 能够在 .tpl 文件中界说可重用模板,并支撑其他模板引证它。但是,只要模板界说才干重用。在一个复杂的 Helm 图表项目中,咱们需求界说许多附加的基本模板。与 Helm 繁琐的写作方法比较,KCL 中的一切内容都是变量。指定模板不需求其他语法。任何变量都能够相互引证。

此外,Helm 中还有很多与实际逻辑无关的 {{- include }}, nindenttoYaml 符号字符,咱们需求计算每个 Helm 引证处的空格和缩进。在 KCL 中,无用代码更少,而且不需求很多的 {{*}} 来符号代码块,信息密度更高。

事实上,KCL 和 Helm Chart 并不敌对。咱们乃至能够运用 KCL 编写 Helm 模板或许运用 KCL 来生成 values.yaml,或许为现有的 Helm 图表供给可编程扩展功能,比方为 Helm 开发可选的 KCL Schema 插件来验证已有的 Helm 图表或许为 Helm Chart 编写额定的 Transformer 来 Patch 已有的 Helm Chart。

未来计划

咱们后续计划 KCL 的模型和束缚能够作为一个包来办理(这个包只要 KCL 文件)。例如,Kubernetes 的模型和束缚能够开箱即用。用户能够经过已有的模型生成装备或验证现有装备,而且能够经过 KCL 承继手法简单地扩展用户想要的模型和束缚。

在此阶段,您能够运用 Git 或 OCI Registry as Storage(ORAS) 等东西来办理 KCL 装备版别。

如果您喜爱这篇文章,必定记得收藏 + 关注!!更多精彩内容请拜访:

  • KCL 库房地址:github.com/KusionStack…
  • Kusion 库房地址:github.com/KusionStack…
  • Konfig 库房地址:github.com/KusionStack…

如果您喜爱这些项目,欢迎 GithubStar 鼓舞一下 ,一起欢迎拜访下面的链接参加咱们的社区进行交流

  • github.com/KusionStack…