That's my logoGrafana TankaFlexible, reusable and concise configuration for Kubernetes
Edit page
IntroductionInstallation
Tutorial
Writing Jsonnet
Libraries
Advanced features
Garbage collectionHelm supportConsuming Helm Charts from JsonnetVendoring Helm ChartsTroubleshootingKustomize supportOutput filteringExporting as YAMLInline environmentsServer-Side Apply
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.

Consuming Helm Charts from Jsonnet

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

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

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

local tanka = import "github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet";
local helm = tanka.helm.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.

Under the hood, this feature invokes the helm template CLI command. The following options control how the command is invoked:

...

{
  grafana: helm.template("grafana", "./charts/grafana", {
    namespace: "monitoring",
    values: {
      persistence: { enabled: true }
    },
    // Equivalent to: --api-versions v1 --api-versions apps/v1
    apiVersions: ['v1', 'apps/v1']
    // Equivalent to: --kube-version v1.20.0
    kubeVersion: 'v1.20.0'
    // Equivalent to: --no-hooks
    noHooks: true,
}

Tanka will install Custom Resource Definitions (CRDs) automatically, if the Helm Chart requires them and ships them in crds/. This is equivalent to helm template --include-crds. This can be disabled using includeCrds: false:

{
  grafana: helm.template("grafana", "./charts/grafana", {
    includeCrds: false
  })
}

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 can use the add-repo command:

# Add the official Grafana repository
$ tk tool charts add-repo grafana https://grafana.github.io/helm-charts

Another way is to modify chartfile.yaml directly:

version: 1
repositories:
  - name: stable
    url: https://charts.helm.sh/stable
+ - name: grafana
+   url: https://grafana.github.io/helm-charts

Installing multiple versions of the same chart: If you wish to install multiple versions of the same chart, you can write them to a specific directory.
You can do so with a :<directory> suffix in the add command, or by modifying the chartfile manually.

tk tool charts add stable/mysql@1.6.7:1.6.7
tk tool charts add stable/mysql@1.6.8:1.6.8

The resulting chartfile will look like this:

version: 1
directory: charts
repositories:
- name: stable
  url: https://charts.helm.sh/stable
requires:
- chart: stable/mysql
  directory: 1.6.7
  version: 1.6.7
- chart: stable/mysql
  directory: 1.6.8
  version: 1.6.8

Install charts from chartfile: To install charts from an existing chartfile, use the following:

$ tk tool charts vendor

Optionally, you can also pass the --prune flag to remove vendored charts that are no longer in the chartfile.

OCI Registry Support

Tanka supports pulling charts from OCI registries. To use one, the chart name must be split into two parts: the registry and the chart name.

As example, if you wanted to pull the oci://public.ecr.aws/karpenter/karpenter:v0.27.3 image, your chartfile would look like this:

version: 1
directory: charts
repositories:
- name: karpenter
  url: oci://public.ecr.aws/karpenter
requires:
- chart: karpenter/karpenter
  directory: v0.27.3
  version: v0.27.3

Registry login is not supported yet.

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 tanka-util:

local tanka = import "github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet";
local helm = tanka.helm.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 }}