导入配置



为了节省时间精力和减少犯错,CUE 有些命令可以导入和最小化已存在的配置生成 CUE 代码。

导入配置

cue import 将会导入 Yaml 和 JSON 的文件和目录生成 CUE 代码, 有一些参数可以控制 CUE 如何执行和组织输出。

让我们继续使用 Cuetorials 的 Kubernetes 例子,在 cuetorials.yaml 所在的目录执行下面的命令:

cue import -f -o cuetorials.cue -l 'strings.ToLower(kind)' -l 'metadata.name' -p cuetorials cuetorials.yaml
  • -f 覆盖输出文件
  • -o 设置输出的文件名
  • -l 告诉 CUE 如何命名值以避免冲突,这里我们将会用 <kind>: <name>: {...}
  • -p 指定 CUE 的 package: package cuetorials
  • 最后一个参数是我们的原始文件,但是也可以是通配符或者目录(./...)

cuetorials.cue

package cuetorials

deployment: cuetorials: {
	apiVersion: "apps/v1"
	kind:       "Deployment"
	metadata: {
		name:      "cuetorials"
		namespace: "websites"
		labels: app: "cuetorials"
	}
	spec: {
		selector: matchLabels: app: "cuetorials"

		strategy: {
			type: "RollingUpdate"
			rollingUpdate: {
				maxSurge:       1
				maxUnavailable: 0
			}
		}
		minReadySeconds: 5

		template: {
			metadata: labels: app: "cuetorials"
			spec: containers: [{
				name:            "website"
				image:           "us.gcr.io/hof-io--develop/cuetorials.com:manual"
				imagePullPolicy: "Always"
				ports: [{
					containerPort: 80
					protocol:      "TCP"
				}]

				readinessProbe: {
					httpGet: port: 80
					initialDelaySeconds: 6
					failureThreshold:    3
					periodSeconds:       10
				}
				livenessProbe: {
					httpGet: port: 80
					initialDelaySeconds: 6
					failureThreshold:    3
					periodSeconds:       10
				}
			}]
		}
	}
}
service: cuetorials: {
	apiVersion: "v1"
	kind:       "Service"
	metadata: {
		name:      "cuetorials"
		namespace: "websites"
		labels: app: "cuetorials"
	}
	spec: {
		selector: app: "cuetorials"
		type: "ClusterIP"
		ports: [{
			port:       80
			targetPort: 80
		}]
	}
}
ingress: cuetorials: {
	apiVersion: "extensions/v1beta1"
	kind:       "Ingress"
	metadata: {
		name:      "cuetorials"
		namespace: "websites"
		labels: app: "cuetorials"
		annotations: {
			"kubernetes.io/tls-acme":                         "true"
			"kubernetes.io/ingress.class":                    "nginx"
			"nginx.ingress.kubernetes.io/force-ssl-redirect": "true"
			"cert-manager.io/cluster-issuer":                 "letsencrypt-prod"

			"kubernetes.io/configuration-snippet": """
				location ~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
				  expires 1h;
				  access_log off;
				  add_header Cache-Control \"public\";
				}
				location / {
				  expires -1;
				}

				"""
		}
	}

	spec: {
		tls: [{
			hosts: [
				"cuetorials.com",
			]
			secretName: "cuetorials-tls"
		}]

		rules: [{
			host: "cuetorials.com"
			http: paths: [{
				backend: {
					serviceName: "cuetorials"
					servicePort: 80
				}
			}]
		}]
	}
}

Go 生成 CUE

CUE 可以将 Go 的类型转换为 CUE 的定义,当你写好了代码并想验证 Yaml 或 JSON 能不能解析成对应的类型时非常有用。 j 你也可以通过 CUE 的结构定义生成 Go 的辅助函数来验证这些类型(详情:Cuelang docs , 需要写 Go 代码)

在我们使用 Go 生成 CUE 之前,我们需要新建一个文件,用于 CUE modules。

我们将在 module & package 这一节讨论 module 和 package, 目前我们先创建这个文件 cue.mod/module.cue

module: "github.com/hofstadter-io/cuetorials"

然后用下面的命令初始化 go.mod 文件

go mod init  "github.com/hofstadter-io/cuetorials.com" 

有了这些之后, 接下来我们要做:

  1. 把 Kubernetes API 包的代码下载下来(需要在本地)
  2. 从 Go 导入生成 CUE 定义
  3. 将 Kubernetes CUE 代码应用到我们的 Cuetorials 的 CUE 代码
  4. 验证我们的配置是否符合 Kubernetes 的规范
# "go get" 获取 Kubernetes API 相关的代码
go get k8s.io/api/...

# "cue go get" 导入到 CUE 的定义
cue get go k8s.io/api/...

# 检查 "cue get go" 在 cue.mod/gen 文件夹生成了什么
tree cue.mod/gen/

现在将以下几行加入到你的 cuetorials.cue 文件中:

package cuetorials // alread there

import (
	apps "k8s.io/api/apps/v1"
	core "k8s.io/api/core/v1"
	extn "k8s.io/api/extensions/v1beta1"
)

deployment: [string]: apps.#Deployment
service: [string]:    core.#Service
ingress: [string]:    extn.#Ingress

// ...

现在试着运行 cue eval cuetorials.cue,很可能会看到很多错误。

这是因为 Kubernetes 从几个地方接受 string 和 integer,为了修复这些问题,在 integer 前添加 IntVal: <#> (或 StrVal:,如果你使用的是包含 string 的其他 yaml)

  • deployment >>> rollingUpdate
  • deployment >>> httpGet
  • service >>> targetPort
  • ingress >>> servicePort

cue eval 没问题之后(打印出 CUE 的值)你可以运行 cue trim 来删除冗余的代码(基于最小化和默认值)。

在 CUE 的学习路径上有关于 高级修剪,基于 anti-unification

package cuetorials

import (
	apps "k8s.io/api/apps/v1"
	core "k8s.io/api/core/v1"
	extn "k8s.io/api/extensions/v1beta1"
)

deployment: [string]: apps.#Deployment
service: [string]:    core.#Service
ingress: [string]:    extn.#Ingress

deployment: cuetorials: {
	apiVersion: "apps/v1"
	kind:       "Deployment"
	metadata: {
		name:      "cuetorials"
		namespace: "websites"
		labels: app: "cuetorials"
	}
	spec: {
		selector: matchLabels: app: "cuetorials"

		strategy: {
			type: "RollingUpdate"
			rollingUpdate: {
				maxSurge: IntVal:       1
				maxUnavailable: IntVal: 0
			}
		}
		minReadySeconds: 5

		template: {
			metadata: labels: app: "cuetorials"
			spec: containers: [{
				name:            "website"
				image:           "us.gcr.io/hof-io--develop/cuetorials.com:manual"
				imagePullPolicy: "Always"
				ports: [{
					containerPort: 80
					protocol:      "TCP"
				}]

				readinessProbe: {
					httpGet: port: IntVal: 80
					initialDelaySeconds: 6
					failureThreshold:    3
					periodSeconds:       10
				}
				livenessProbe: {
					httpGet: port: IntVal: 80
					initialDelaySeconds: 6
					failureThreshold:    3
					periodSeconds:       10
				}
			}]
		}
	}
}
service: cuetorials: {
	apiVersion: "v1"
	kind:       "Service"
	metadata: {
		name:      "cuetorials"
		namespace: "websites"
		labels: app: "cuetorials"
	}
	spec: {
		selector: app: "cuetorials"
		type: "ClusterIP"
		ports: [{
			port: 80
			targetPort: IntVal: 80
		}]
	}
}
ingress: cuetorials: {
	apiVersion: "extensions/v1beta1"
	kind:       "Ingress"
	metadata: {
		name:      "cuetorials"
		namespace: "websites"
		labels: app: "cuetorials"
		annotations: {
			"kubernetes.io/tls-acme":                         "true"
			"kubernetes.io/ingress.class":                    "nginx"
			"nginx.ingress.kubernetes.io/force-ssl-redirect": "true"
			"cert-manager.io/cluster-issuer":                 "letsencrypt-prod"

			"kubernetes.io/configuration-snippet": """
				location ~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
				  expires 1h;
				  access_log off;
				  add_header Cache-Control \"public\";
				}
				location / {
				  expires -1;
				}

				"""
		}
	}

	spec: {
		tls: [{
			hosts: [
				"cuetorials.com",
			]
			secretName: "cuetorials-tls"
		}]

		rules: [{
			host: "cuetorials.com"
			http: paths: [{
				backend: {
					serviceName: "cuetorials"
					servicePort: IntVal: 80
				}
			}]
		}]
	}
}

你可能会说,“这个奇怪的 int 或 string 类型的端口并不能匹配我的 Kubernetes 文件”,确实会这样。

我们会在"集成"这一节会介绍如何让 OpenAPI 的规范生效和更多相关的内容。

我们绝不会将你的邮箱分享给任何人。
2022 Hofstadter, Inc