CUE is an open source data validation language and inference engine with its roots in logic programming.
To be honest, I didn’t understand the description at first either. Let’s look at a small example.
-
txt
-
Schema
-
CUE
When defining JSON data, we usually treat Data and Schema separately. CUE combines the two by specifying both the data field type and the specific value, i.e. CUE does not make a distinction between “type” and “value”, both string
and "Moscow"
will be Both string
and "Moscow"
will be treated as values, but there is an order of inclusion between them, where "Moscow"
can be subsumed under string
, and then string
will take precedence over "Moscow"
in the lattice.
More about CUE can be found in the official documentation and definitions, so I won’t go into it here.
Using CUE for template rendering
CUE has a lot of cool usage scenarios, and first let’s focus on the profile rendering capabilities within it.
We borrow here from KubeVela
example in the documentation
|
|
As you can see, you only need to pass in parameter
to get output
containing Service
and Ingress
.
This way of writing immediately reminds us of a similar tool, Helm, an early CNCF graduation project that has slowly evolved into a de facto industry standard in the field of k8s configuration definition. So what are the similarities and differences between writing configuration file rendering with CUE compared to Helm?
CUE vs Helm
The most intuitive feeling is that CUE is much smoother than Helm in template writing, mainly because of the difference in their initial design ideas; Helm relies on the plain text rendering capabilities of Go and String, while for CUE, JSON is a first-class citizen. So CUE (which is also a JSON superset) has a natural advantage in rendering yaml (JSON superset) files like K8S.
Talk is cheap, show me the code.
Let’s look at a few common scenarios together.
Named Templates
-
Helm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# values.yaml app_name: "example_code" other_attr1: "foo" other_attr2: "bar" # labels.tpl {{- define "foo.labels" -}} label1: {{ .Values.app_name | required "app_name is required" }} label2: {{ .Values.other_attr2 }} {{ include "foo.selectorLabels" . }} {{- end }} {{- define "foo.selectorLabels" -}} label3: {{ printf "%s-%s" .Values.app_name .Values.other_attr1 }} {{- end }}
-
CUE
-
Helm can define reusable templates in .tpl files and support other templates to refer to it, and only the defined templates can be reused, so you need to define a lot of additional base templates in complex Chart projects.
-
Compared to Helm’s cumbersome writing style, all content in CUE is JSON objects, no additional syntax is needed to specify the template, and any JSON objects can be referenced to each other.
Blank lines and indentations
-
Helm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "foo.deploymentName" . }} labels: {{- include "foo.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "foo.selectorLabels" . | nindent 6 }} strategy: {{- include "foo.updateStrategy" . | nindent 4 }} revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "foo.labels" . | nindent 8 }}
-
CUE
-
Helm has a lot of markup characters like
{{- include }}
andnindent
that are irrelevant to the actual logic, requiring spaces and indentation to be counted at each reference. -
Whereas in CUE there is much less useless coding, no need for too many
{{ * }}
to mark up blocks of code, much higher information density, and complete liberation in terms of indentation and spaces.
values.yaml self-referencing
A long-standing headache in Helm is the inability to gracefully implement the values.yaml
referencing problem.
Let’s look at the following example.
In the usual case, Chart users need to fill in the content for both variables separately, increasing the possibility of errors.
Although we can achieve this by defining templates.
However, in practice, all references require additional include
and the maintenance of the definitions is very labor-intensive (always make sure that blank lines and indentation are not wrong).
In CUE, however, cross-referencing seems natural and comfortable.
Importing Kubernetes packages
Another big killer of CUE is the ability to generate description cue files against native Kubernetes source code, and all k8s resource-related configuration files can naturally have schema checks. A specific tutorial can be found at Kubernetes tutorial.
In an example like this one, the deployment
we define will be checked by apps.#Deployment
, easily detecting fields that are not legal. In theory, you could generate cue files for multiple versions of k8s resources and use them all as model definitions for deployment
, allowing for multi-version compatibility of resource definitions.
With all the benefits mentioned, replace all Helm Chart with CUE now?
Not yet, not yet.
Because Helm is a Package Manager
that has some application management features such as Helm install
, Helm rollback
, etc. in addition to Chart rendering, while CUE is just a template. So in some cases where we only use Helm’s templates, we can consider migrating to CUE, but in other cases, we should use Helm.