Helm Charts are ubiquitous. They are the de facto method of packaging and deploying software on Kubernetes. Like a software library, a helm chart allows the developers to not have to reinvent the wheel each time they stand up a new application. As your organization or product grows, you almost certainly will end up building out internal helm charts that allow developers to move quickly and benefit from the best practices of the rest of the organization. It will allow developers or end users of your software to get best practices from a reliability, scalability, and deployment perspective. I've written 10+ helm charts, at least one of which has hundreds of installations internally to Mission Lane. I've also contributed to a variety of Open Source helm charts including: Open Telemetry Collector, K6 Operator, BentoML/Yatai, and more. Here are my 6 tips for writing a great helm chart.

Don't Change Well-Known API Spec

Don't move imagePullSecrets to image.pullSecrets. Don't — like I once did — go from resources.limits.cpu/memory to resources.cpu.limits/requests and resources.memory.limits/requests. Don't change how environment variables are passed in. No matter how you think the API spec is improved by your change, it will cause more confusion, require more rewrites, and invalidates all external documentation.

Don't change convention. No matter how good your change to the API is, if you change it from the well-known spec, you are invalidating all the external documentation, all the external tutorials, and all the external tooling. It means you have to recreate all of that information and tooling if you want people to use your changed API effectively. It isn't worth it. Don't change the well-known api spec.

Generate the schema.json

Since at least 2020, helm charts have support the use of a schema.json to allow for schema validation, IDE auto completion, and type validation. Use it.

Here is a great tutorial on starting off the schema. However, maintaining it at scale isn't quite that easy.

If you want to automatically generate a schema, you can do so using: https://github.com/knechtionscoding/helm-schema-gen. Autogeneration works best when you do it the first time. After the first time, you almost certainly will have to manage the schema by hand to get the most out of it.

Add types to each of the values. Ensure that required is set. Remember that your type needs to include a null value of some kind if it is optional. Ensure you have useful descriptions on each of the values.

The easier you make it to use your helm chart, the more other engineers will use it. Make it as easy as possible. Giving them schema validation in IDE and when running a helm template is a big step towards that.

Generate Documentation

Use helm-docs to generate documentation for your helm chart. Documentation will keep it up to date, consistent, and useable. Make the docs generation a pre-commit hook or part of the CI pipeline to ensure that it stays up to date.

Set Sane Default Values

Default values will be the accepted values for well more than 80% of users of your helm chart. Ensuring they make sense and are sane is vitally important. Much has been written about sane defaults. But broadly, you should set defaults that will match the values most of your users will need — especially when setting out.

It can be tempting to use "profiles," which are basically group settings or values together. For example, you might be tempted to make a tier system: Tier 0 (99.99% SLA), Tier 1 (99.9% SLA), Tier 2 (99% SLA), and then change the defaults based on the value of the profile. Don't do this in your chart. Do this in a CLI that templates out an instantiation of your helm chart. If you do this inside the values and set a bunch values based on a "profile," you end up losing a lot of customization that is required to make usage of your chart easy and scalable.

Allow for API Version Overrides

One of the most useful features of helm is the Capabilities function. If you haven't used it, capabilities allows you to ask the k8s cluster for the version of an API that it supports. The capabilities function prevents you from having to manage a separate version -> capabilities mapping for each API. Most of the time, it is implemented as an if/else condition with a default value. But there's a downside to using Capabilities — specifically that it only works if you are using helm install. If you are using helm template (to do a diff first) or to pipe to a different tool or if you are using an intermediary like kustomize, then Capabilities doesn't work because Helm doesn't have direct access to the cluster API. In cases like this [what], please allow for an override for the APIVersion in the values.yaml. Renovate's helm chart has a great example

Write Unit Tests

Most people writing helm charts don't get to this step. However, writing tests is really important when you start writing complex interactions between conditions. Being able to ensure that the correct variables get populated, the correct objects created (especially when you group them together under a single conditional), correct annotations or labels exist, correct naming schemas and names are outputted, or any number of other options are required, especially if you chart gets to be rather large and changes affect multiple objects at the same time.

Luckily, someone has written a tool that allows for unit tests of helm charts. Having used it for nearly 3 years, written about 400 tests in it, and found numerous bugs in my own charts with it, it's become an essential tool. Helm-unittest is genuinely easy to use, and allows you to write small, composable tests that ensure your objects look the way you expect them too. It doesn't allow you to test functionality — the built in helm test command can help you do that — but I find that to be less important anyway. Your functional tests should be either available in your actual code base, or tend to be so simple as to really be pointless. Unit tests, however, allow you to ensure that you've written the helm templates with the result that is expected.

Writing these unit tests becomes increasingly important the more people you have contributing to your helm chart, the more people using your helm chart, and the more people trying to understand how it functions.

Write Good Release Notes

This one should be self-explanatory. Don't make your chart have the equivalent of an mobile app release notes: "bug fixes." Use a systematic method of generating release notes, make sure that users know what changed and why, and publish them along side your release. I prefer conventional commits as a method of writing my commit messages, and for generating my versions, however, you can pick whatever style you want. Make sure engineers using your chart know what is changing between versions.

Always allow for the following 4 customizations

  1. Adding extra containers to the pods. There's a variety of reasons why a user of the chart would want to add an extra container or more to the pod. A sidecar for db connections, an init container, a service mesh, a secrets container, etc. Each of these represent real use cases to modify the number of containers — so make it easy.
  2. Adding labels and annotations globally (to every object in one place), and to each object individually. Requirements around labeling and annotations will only get more stringent as companies embrace admission controllers like Kyverno or Gatekeeper, require accurate accounting for spend, and work to ensure ownership of services. Let users pass in custom labels and annotations easily.
  3. Allow for custom env variables. Follow the API spec and allow for custom env variables to passed to all containers and to each container specifically. Having to figure out how and why to get environment variables that may be required into a container can be a pain if it isn't easily available via envVars.
  4. Ensure resources can be customized for each pod. Don't hard code which resources can be configured either. Don't only allow for CPU/Memory. As teams embrace ephemeral-storage or network requests/limits you want your chart to be forwards compatible.

Conclusion

Hopefully you've learned how to level up your helm chart to make it go from good to great. The tips aren't complex, but they can require effort and rigor to enforce. To summarize the tips:

  1. Don't change the well-known API spec
  2. Generate the schema.json
  3. Generate documentation
  4. Set sane default values
  5. Allow for API version overrides
  6. Write unit tests
  7. Write good release notes
  8. Always allow for standard customizations