I liked YAML a lot, until it gets longer and longer, and even longer.
There are tools to make YAML ‘DRY’, the popular ones are Helm and Kustomize. But none of them can say it got the job done.
To be honest, I didn’t like Helm much from the start. Helm uses templating syntax similar to Jinja2, or PHP, or ASP 20 years ago. It tried to solve a recent problem using an ancient approach: generate text and substitute placeholders with values from variables. Obviously it works and a lot of people are using it, but this bothers me as there are issues that Helm can hardly resolve due to this design:
- It’s a mixture of YAML and scripts. When working on it, developer has to maintain YAML indentations in the code fragments too, to ensure YAML can be generated as expected.
- The values of a Helm chart are the parts that can be customized when re-using this chart, this hides the complexity of a full chart from end users but if what the user wanted wasn’t in the values, the user will have to fork the whole chart.
I liked Kustomize a bit more because it doesn’t break the elegance of YAML, ie. the base and overlay Kustomize templates are both valid YAML files. But this is also its Achilles’ heel because YAML is data, not code, it’s Ok to have a few variables, but loops, functions, modules? Those are too much to ask.
I was introduced to Jsonnet a while ago but why should I go back to JSON when I’m better with YAML? However I happened to get to know Grafana Tanka project, after a few minutes’ read I think Tanka has solved most problems which Helm and Kustomize did not. After I watch this presentation(to my surprise the views on this video is shockingly low) I decided to give it a go!
The first step is to install the tanka binary. I personally installed it as a golang module. The
go get command looks like:
$ GO111MODULE=on go get github.com/grafana/tanka/cmd/tk ... # install JsonnetBundler as it's recommended $ GO111MODULE=on go get github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb ... tk --version tk version dev
Then we can initialize a hello-world project for tanka:
$ mkdir tanka-hello $ cd tanka-hello $ tk init GET https://github.com/ksonnet/ksonnet-lib/archive/0d2f82676817bbf9e4acf6495b2090205f323b9f.tar.gz 200 GET https://github.com/grafana/jsonnet-libs/archive/4d4b5b1ce01003547a110f93cc86b8b7afb282a6.tar.gz 200 Directory structure set up! Remember to configure the API endpoint: `tk env set environments/default --server=https://127.0.0.1:6443`
The initial directory structure looks like this:
$ tree . ├── environments │ └── default │ ├── main.jsonnet # <-- this is where we will do the hello-world │ └── spec.json ├── jsonnetfile.json ├── jsonnetfile.lock.json ├── lib │ └── k.libsonnet └── vendor ├── github.com │ ├── grafana │ │ └── jsonnet-libs │ │ └── ksonnet-util │ │ ├── grafana.libsonnet │ │ ├── kausal.libsonnet │ │ ├── k-compat.libsonnet │ │ ├── legacy-custom.libsonnet │ │ ├── legacy-noname.libsonnet │ │ ├── legacy-subtypes.libsonnet │ │ ├── legacy-types.libsonnet │ │ └── util.libsonnet │ └── ksonnet │ └── ksonnet-lib │ └── ksonnet.beta.4 │ ├── k8s.libsonnet │ └── k.libsonnet ├── ksonnet.beta.4 -> github.com/ksonnet/ksonnet-lib/ksonnet.beta.4 └── ksonnet-util -> github.com/grafana/jsonnet-libs/ksonnet-util
To make this demo more intuitive, I ran an
inotifywait command in left pane and
vim environments/default/main.jsonnet in right pane
# the inotifywait command explained: $ inotifywait -q -m -e moved_to environments/default/ \ # every time the file is saved in vim, an event will be triggered here |while read -r filename event; do \ # the changed filename and event name are read here but we don't really need to do anything about them echo -e '\n\n'; tk show environments/default/ # print out generated YAML file done
As shown in the screenshot, after I understood minimum set of tanka syntax, I can get a Deployment YAML out of a few lines of Jsonnet code.