Before discussing the architecture behind HULL it is useful to take a look at the motivation that spurred the creation of it.
In the context of Kubernetes and Helm there are basically two groups of people that work with Helm charts:
- the chart creators/maintainers
- the chart users
Let us first take at the problems that might arise for both groups before taking a look at the HULL architecture to see how some problems can be remedied.
While there is much freedom given to the chart designers and maintainers in how to abstract the configuration of the packaged applications, Helm unfortunately offers no fast, easy and maintenance free way to handle recurring demands. It is fully focussed on having custom YAML templates for all objects in a chart in order to produce output. But considering that Helm chart maintainers might create and have to maintain a large number of Helm charts this way of working can become difficult.
First of, getting started on building helm charts is challenging - especially for someone not experienced with the concepts of Kubernetes, Helm, YAML and Go Templating and all the relations between them which are needed to create sound helm charts. Furthermore, YAML and string templating don't play nice together which is something rightfully criticized. It can become very finicky to produce valid YAML output with templating expressions. Typically starting a new helm chart from scratch requires a significant amount of copying and pasting templates from existing charts and adapting them to the new products needs. This is a time consuming and error prone process.
Once charts have been created and published they need to be maintained. A maintainer maintaining a single Helm chart can reasonably do his work but let us assume it is required of her/him to maintain many helm charts. Then the amount of (for the large part) duplicated YAML blocks in templates grows very fast. Assuming you need to fix a conceptional issue you likely need to do this in a variety of files each time which is tedious and time consuming.
Initially, chart users are faced with the same steep learning curve as the maintainers. They also need to be familiar with the underlying concepts to create a proper deployment configuration. This is worsened by the fact that each helm chart is basically a unique artifact with it's unique underlying implications of how it should be configured.
But any more advanced user of Helm will be aware of the objects as they are represented by the Kubernetes API. In order to specify the objects with the properties they have in mind they need to understand the individual chart creators assumptions on how to configure the chart and the relations behind. As an analogy, it is comparable to someone who wants to speak english (Kubernetes YAML) being told he has to learn portuguese (Helm YAML) and all regional portuguese dialects (individual Helm chart interfaces) to do so.
The best practices guidelines that are available do cover some ground to align chart writing and foster common understanding but still leave most design aspects to the maintainers. From personal experience it is almost always needed to inspect the files in the /templates
folder to learn about the effects of changing configuration parameters in the values.yaml
.
Moreover, depending on the requirements and preferences of the chart maintainer it is often the case that features required for a particular user are not implemented in the chart at all. To give an example of this, let's take a look at specifying imagePullSecrets
for deployments within a helm chart:
- It might not be possible to specify them with the given chart in case the chart maintainers did not opt for implementing them, maybe being focused on public cloud deployments only. In this case you need to submit a pull request, modify locally, fork or something else to fulfill your requirement.
- The helm chart might have very different expectations on how they should be specified. Looking at some public helm charts available:
-
The
cerebro
helm chart athttps://github.com/helm/charts/blob/master/stable/cerebro/templates/deployment.yaml
requires you to specifyimagePullSecrets
as an array/list of values (excluding thename
keys) per image:{{- if .Values.image.pullSecrets }} imagePullSecrets: {{- range .Values.image.pullSecrets }} - name: {{ . }} {{- end }} {{- end }}
-
The
sonarqube
helm chart athttps://github.com/helm/charts/blob/master/stable/cerebro/templates/deployment.yaml
requires you to specifyimagePullSecrets
as a string. This means you can only specify one:{{- if .Values.image.pullSecret }} imagePullSecrets: - name: {{ .Values.image.pullSecret }} {{- end }}
-
The
prometheus
helm chart athttps://github.com/helm/charts/blob/master/stable/prometheus/templates/deployments/alertmanager.yaml
however wantsimagePullSecrets
provided as an array of key-value pairs with repeatedname
keys:{{- if .Values.imagePullSecrets }} imagePullSecrets: {{ toYaml .Values.imagePullSecrets | indent 2 }} {{- end }}
-
This is not helpful to the user to have an intuition of what he needs to do. The HULL library can step in to solve these issues to a large degree.
There are various groups of people that work with Helm. There might be a group of people concerned with maintaining and installing a single specific chart to the best of their effort. In this case the regular Helm workflow might be suited for them well enough.
But there are also people who maintain and/or consume a larger number of charts which likely are overwhelmed by the various individual design approaches to creating Helm charts. To this group, using the HULL library can omit the tedious YAML template creation and placeholder logic by offering a common interface to specifying objects directly, allowing the consumer to specify complete Kubernetes objects out-of-the-box and support frequent use-cases by a light abstraction layer.
At the core of the HULL library is the interplay between values.yaml
, values.schema.json
, hull.yaml
and the template files in /templates
that come with HULL.
For the HULL related functionalities only entries under the hull
sub-key are relevant. No other top level key is relevant for HULL so it can co-exist with any other Helm chart configuration properties.
The values.schema.json
of each HULL library release is built from the respective version of the Kubernetes API JSON Schema which is:
- extended with the HULL specific properties to provide the HULL specific functionalities and
- minor modifications to the strict K8S JSON schema to allow co-existence with the HULL specific properties.
This means that misconfigurations of the values.yaml
hull
subsection are either visible on input directly or catched on rendering the objects.
The hull.yaml
The hull.yaml
needs to be placed in the parents charts /templates
folder. It contains a loop over all handled object types and their specific configurational properties. Within the loop all objects of the handled object types (as specified in values.yaml
) are iterated over and the corresponding rendering function is called for the specified and enabled object.
The /templates
The templates folder in HULL contains only functions as it is mandatory for Helm library charts.
The end to end process using HULL contains the following phases:
-
Define the input to the Helm chart in
values.yaml
and maybe additionalvalues.yaml
's which are superimposed. This can be heavily supported by IDE`s that offer live input validation via JSON schema. -
If you are satisfied with the specification of objects you can test render your chart to check the result or install it in a test cluster. Behind the scenes the following takes place:
-
JSON Schema validation of merged
values.yaml
via thevalues.schema.json
included in HULL. It is based on the strict Kubernetes API schema and modified to incorporate the schemas of the additional HULL objects.⚠️ Any schema violations will prevent further processing.⚠️ -
The
hull.yaml
in the templates folder is processed.First the complete
values.yaml
dictionary is traversed and any transformations provided are evaluated. This includes all objects including_HULL_OBJECT_TYPE_DEFAULT_
instances andsources
which are not rendered themselves.Next an iteration over all implemented object types and defined and enabled object instances hands over to the object instance definition to the appropriate rendering template. Different basic rendering templates are triggered for:
- objects with a simple structure:
serviceaccount
storageclass
customresource
priorityclass
endpoints
- objects with a simple structure depending on RBAC enablement:
rolebinding
clusterrolebinding
- objects with a role based structure depending on RBAC enablement:
role
clusterrole
- objects with a
spec
field:perstistentvolume
persistentvolumeclaim
servicemonitor
poddisruptionbudget
resourcequota
networkpolicy
ingressclass
- objects which are based on pod definitions
deployment
job
daemonset
statefulset
- objects which are based on webhook definitions
mutatingwebhookconfiguration
validatingwebhookconfiguration
- objects with individual templates:
configmap
secret
registry
service
ingress
cronjob
horizontalpodautoscaler
Then each object is processed individually:
- Apply object type defaults from the
_HULL_OBJECT_TYPE_DEFAULT_
instances and merge referencedsources
into the object source specification. - Create the metadata section of the object
- Process all properties handled by HULL
- Add remaining Kubernetes API schema properties that were defined
- objects with a simple structure:
-
The output is all defined objects concatenated in one file with
template
command or handed over to the Kubernetes cluster API for deployment withinstall
command.
-
Back to README.md