Grafana TankaFlexible, reusable and concise configuration for Kubernetes
Edit page
IntroductionInstallation
Tutorial
Writing Jsonnet
Libraries
Advanced features
Garbage collectionHelm supportConsuming Helm Charts from JsonnetVendoring Helm ChartsTroubleshootingOutput filteringExporting as YAML
References
Frequently asked questionsKnown issues

Helm Support

The Helm project is the biggest ecosystem of high quality, well maintained application definitions for Kubernetes.

Even though Grafana Tanka uses the Jsonnet language for resource definition, you can still consume Helm resources, as described below.

Warning: Keep in mind this feature is considered EXPERIMENTAL

Consuming Helm Charts from Jsonnet

Helm support is provided using the github.com/grafana/jsonnet-libs/helm-util library. Install it with:

jb install github.com/grafana/jsonnet-libs/helm-util

The following example shows how to extract the individual resources of the grafana Helm Chart:

local helm = (import "github.com/grafana/jsonnet-libs/helm-util/helm.libsonnet").new(std.thisFile);

{
  grafana: helm.template("grafana", "./charts/grafana", {
    namespace: "monitoring",
    values: {
      persistence: { enabled: true }
    }
  })
}

The Chart itself is required to be vendored at a relative path, in this case ./charts/grafana.

Important: You MUST include the .new(std.thisFile) part in the import. This is what tells Tanka where you actually call helm.template() from, so it can find your vendored Charts.


Once invoked, the $.grafana key holds the individual resources of Helm Chart as a regular Jsonnet object that looks roughly like so:

{
  cluster_role_binding_grafana_clusterrolebinding: {/* ... */},
  cluster_role_grafana_clusterrole: {/* ... */},
  config_map_grafana: {/* ... */},
  config_map_grafana_test: {/* ... */},
  deployment_grafana: {/* ... */},
  // ...
}

Above can be manipulated in the same way as any other Jsonnet data.

Vendoring Helm Charts

Tanka, like Jsonnet, is hermetic. It always yields the same resources when the project is strictly self-contained.

Helm however keeps Charts and repository configuration somewhere around ~/.config/helm, which violates above requirement.

To comply with this requirement, Tanka expects Helm Charts to be found inside the bounds of a project. This means, you MUST put your Charts somewhere next to the file that calls helm.template(), so that it can be referred to using a relative path.

Vendor Location

Where to actually put them inside the project is up to you, but keep in mind you need to refer to them using relative paths.

We recommend always writing libraries that wrap the actual Helm Chart, so the consumer does not need to be aware of it. Whether you put these into your local lib/ directory or publish and vendor them into the vendor/ directory is up to you.

A library usually looks like this:

  /jsonnetfile.json
  /main.libsonnet

When adopting Helm inside it, we recommend vendoring at the top level, as such:

  /jsonnetfile.json
  /main.libsonnet
+ /charts
+ /charts/<someChart>

This way, you can refer to the charts as ./charts/<someChart> from inside main.libsonnet. By keeping the chart as close to the consumer as possible, the library is kept portable.

Charttool

Helm does not make vendoring incredibly easy by itself. helm pull provides the required plumbing, but it does not record its actions in a reproducible manner.

Therefore, Tanka ships a special utility at tk tool charts, which automates helm pull:

# Create a chartfile.yaml in the current directory, e.g. in lib/myLibrary
$ tk tool charts init

$ # Install the MySQL chart at version 1.6.7 from the stable repository
$ tk tool charts add stable/mysql@1.6.7

Adding charts: To add a chart, use the following:

$ tk tool charts add <repo>/<name>@<version>

This will also call tk tool charts vendor, so that the charts/ directory is updated.


Adding Repositories: By default, the stable repository is automatically set up for you. If you wish to add another repository, you currently need modify chartfile.yaml directly to add it:

version: 1
repositories:
  - name: stable
    url: https://kubernetes-charts.storage.googleapis.com
+ - name: grafana
+   url: https://https://grafana.github.io/helm-charts

Troubleshooting

Helm executable missing

Helm support in Tanka requires the helm binary installed on your system and available on the $PATH. If Helm is not installed, you will see this error message:

evaluating jsonnet: RUNTIME ERROR: Expanding Helm Chart: exec: "helm": executable file not found in $PATH

To solve this, you need to install Helm. If you cannot install it system-wide, you can point Tanka at your executable using TANKA_HELM_PATH

opts.calledFrom unset

This occurs, when Tanka was not told where it helm.template() was invoked from. This most likely means you didn't call new(std.thisFile) when importing helm-util:

local helm = (import "github.com/grafana/jsonnet-libs/helm-util/helm.libsonnet").new(std.thisFile);
                                                                                 ↑ This is important

Failed to find Chart

helmTemplate: Failed to find a Chart at 'stable/grafana': No such file or directory.
helmTemplate: Failed to find a Chart at '/home/user/stuff/tanka/environments/default/grafana': No such file or directory.

Tanka failed to locate your Helm chart on the filesystem. It looked at the relative path you provided in helm.template(), starting from the directory of the file you called helm.template() from.

Please check there is actually a valid Helm chart at this place. Referring to charts as <repo>/<name> is disallowed by design.

Two resources share the same name

To make customization easier, helm.template() returns the resources not as the list it receives from Helm, but instead converts this into an object.

For the indexing key it uses kind_name by default. In some rare cases, this might not be enough to distinguish between two resources, namely when the same resource exists in two namespaces.

To handle this, pass a custom name format, e.g. to also include the namespace:

custom: helm.template('foo', './charts/foo', {
  nameFormat: '{{ print .namespace "_" .kind "_" .metadata.name | snakecase }}'
})

The literal default format used is {{ print .kind "_" .metadata.name | snakecase }}