Kubernetes library
The last section has shown that using a library for creating Kubernetes objects can drastically simplify the code you need to write. However, there is a huge amount of different kinds of objects and the Kubernetes API is evolving (and thus changing) quite rapidly.
Writing and maintaining such a library could be a full-time job on it’s own. Luckily, it is possible to generate such a library from the Kubernetes OpenAPI specification! Even better, it has already been done for you.
k8s-libsonnet
The library is called k8s-libsonnet
(replacing the discontinued ksonnet-lib
),
currently available at https://github.com/jsonnet-libs/k8s-libsonnet.
As k8s-libsonnet
has broken compatibility in a few places with ksonnet-lib
(for good
reason), we have instrumented the widely used ksonnet-util
library with a
compatibility layer to improve the developer and user experience:
https://github.com/grafana/jsonnet-libs/tree/master/ksonnet-util
If you do not have any strong reasons against it, just adopt the wrapper as
well, it will ease your work. Many of the original ksonnet-util
enhancements
have already made their way into k8s-libsonnet
.
The docs for k8s-libsonnet
library can be found here:
https://jsonnet-libs.github.io/k8s-libsonnet/
Installation
Like every other external library, k8s-libsonnet
can be installed using
jsonnet-bundler
.
However, Tanka already did this for you during project
creation (tk init
):
tk initjb install github.com/jsonnet-libs/k8s-libsonnet/1.21@main github.com/grafana/jsonnet-libs/ksonnet-util
This created the following structure in /vendor
:
Directoryvendor
Directorygithub.com
Directorygrafana
Directoryjsonnet-libs
Directoryksonnet-util
- kausal.libsonnet # Grafana’s wrapper
- …
Directoryjsonnet-libs
Directoryk8s-libsonnet
Directory1.21
- main.libsonnet # k8s-libsonnet entrypoint
- …
Directory1.21/ -> github.com/jsonnet-libs/k8s-libsonnet/1.21
- …
Directoryksonnet-util/ -> github.com/grafana/jsonnet-libs/ksonnet-util
- …
Aliasing
Because of how jb
works, the library can be imported as
github.com/jsonnet-libs/k8s-libsonnet/1.21/main.libsonnet
. Most external
libraries (including our wrapper) expect it as a simple k.libsonnet
(without
the package prefix).
To support both, Tanka automatically created an alias file for you:
/lib/k.libsonnet
that just imports the actual library, exposing it under this
alternative name as well.
Using it
First we need to import it in main.jsonnet
:
local k = import "kubernetes.libsonnet";local k = import "github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet";local grafana = import "grafana.libsonnet";local prometheus = import "prometheus.libsonnet";{ /* ... */ }
Now that we have installed the correct version, let’s use it in
/environments/default/grafana.libsonnet
instead of our own helper:
local k = import "github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet";
{ // use locals to extract the parts we need local deploy = k.apps.v1.deployment, local container = k.core.v1.container, local port = k.core.v1.containerPort, local service = k.core.v1.service, // defining the objects: new(name, port):: { // deployment constructor: name, replicas, containers deployment: deploy.new(name, replicas=1, containers=[ // container constructor container.new(, "grafana/grafana") + container.withPorts( // add ports to the container [port.new("ui", 3000)] // port constructor ), ]),
// instead of using a service constructor, our wrapper provides // a handy helper to automatically generate a service for a Deployment service: k.util.serv.util.serviceFor(self.deployment) + service.mixin.spec.withType("NodePort"), }}
Full example
Now that creating the individual objects does not take more than 5 lines, we can
merge it all back into a single file (main.jsonnet
) and take a look at the
whole picture:
local k = import "github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet";
{ local deployment = k.apps.v1.deployment, local container = k.core.v1.container, local port = k.core.v1.containerPort, local service = k.core.v1.service,
prometheus: { deployment: deployment.new( name="prometheus", replicas=1, containers=[ container.new("prometheus", "prom/prometheus") + container.withPorts([port.new("api", 9090)]), ], ), service: k.util.serviceFor(self.deployment), }, grafana: { deployment: deployment.new( name="grafana", replicas=1, containers=[ container.new("grafana", "grafana/grafana") + container.withPorts([port.new("ui", 3000)]), ], ), service: k.util.serviceFor(self.deployment) + service.mixin.spec.withType("NodePort"), },}
That’s a pretty big improvement, considering how verbose and error-prone it was before!
Bonus: Config object
While this is already a huge improvement, we can do a bit more. There is still some repetition in the main.jsonnet
file.
The most straightforward way to address this is by creating a hidden object that holds all actual values in a single place to be consumed by the actual resources.
Luckily, Jsonnet has the key:: "value"
stanza for private fields. Such are only available during compiling and will be removed from the actual output.
Such an object could look like this:
{ _config:: { grafana: { port: 3000, name: "grafana", }, prometheus: { port: 9090, name: "prometheus" } }}
We can then replace hardcoded values with a reference to this object:
local k = import "github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet";
{ // <- This is $ _config:: { grafana: { port: 3000, name: "grafana", }, prometheus: { port: 9090, name: "prometheus" } }
local deployment = k.apps.v1.deployment, local container = k.core.v1.container, local port = k.core.v1.containerPort, local service = k.core.v1.service,
prometheus: { deployment: deployment.new( name="prometheus", replicas=1, // $ refers to the outermost object name=$._config.prometheus.name, replicas=1, containers=[ container.new("prometheus", "prom/prometheus") + container.withPorts([port.new("api", 9090)]), container.new($._config.prometheus.name, "prom/prometheus") + container.withPorts([port.new("api", $._config.prometheus.port)]), ], ), service: k.util.serviceFor(self.deployment), }, grafana: { deployment: deployment.new( name="grafana", replicas=1, name=$._config.grafana.name, replicas=1, containers=[ container.new("grafana", "grafana/grafana") + container.withPorts([port.new("ui", 3000)]), container.new($._config.grafana.name, "grafana/grafana") + container.withPorts([port.new("ui", $._config.grafana.port)]), ], ), service: k.util.serviceFor(self.deployment) + service.mixin.spec.withType("NodePort"), },}