Introduction
The Helm charts repository has seen some amazing growth over the last year. We now have over 200 applications that you can install into your Kubernetes clusters with a single command. Along with the growth in the number of applications that are maintained in the Charts repository, there has been huge growth in the number of contributions that are being received. The Charts repo gives the community a place to centralize their understanding of the best practices of how to deploy an application as well which configuration knobs should be available for those applications.
As I mentioned in my talk at Kubecon, these knobs (provided by the Helm values.yaml file) are on a spectrum. You start out on the left and inevitably work your way towards the right:
When a chart is first introduced, it is likely to have the use case of its author met. From there, other folks take the chart for a spin and figure out where it could be more flexible in the way its configured so that it can work for their use cases. With the addition of more configuration values, the chart becomes harder to test and reason about as both a user and maintainer. This tradeoff is one that is hard to manage and often ends with charts working their way towards more and more flexible chart values.
A class of changes that we have seen often in the Chart repository is customizations that enable low level changes to Kubernetes manifests to be plumbed through to the values file. Examples of this are:
By now, most of our popular charts allow this flexibility as it has been added by users who need to make these tweaks for the chart to be viable in their environments. The hallmark of these types of values is that they plumb through a K8s API specific configuration to the values.yaml that the user interacts with. At this point, advanced users are happy to be able to tweak what they need but the values.yaml file becomes longer and more confusing to reason about without in-depth Kubernetes knowledge.
There is a good portion of the PRs for upstream charts that are to add these last-mile customizations to charts. While these are very welcome and help others not have to fork or change charts downstream, there is another way to make upstream charts work better in your environment.
Kustomize is a project that came out of the CLI Special interest group. Kustomize “lets you customize raw, template-free YAML files for multiple purposes, leaving the original YAML untouched and usable as is.” So given this functionality of customizing raw Kubernetes YAML, how we can we leverage it for customization of upstream Helm charts?
The answer lies in the helm template command which allows you to use Helms templating and values.yaml parameterization but instead of installing into the cluster just spits out the manifests to standard out. Kustomize can patch our chart’s manifests once they are fully rendered by Helm.
Tutorial
So what does this look like in practice? Lets customize the upstream Jenkins chart a bit. While this chart includes tons of ways to parameterize it using the extensive values file, I’ll try to accomplish similar things using the default configuration and Kustomize.
In this example I want to:
- Override the default plugins and include the Google Compute Engine plugin
- Increase the size of the Jenkins PVC
- Bump the memory and CPU made available to Jenkins (it is Java after all)
- Set the default password
- Ensure that the -Xmx and -Xms flags are passed via the JAVA_OPTS environment variable
Let’s see what the flow would look like:
- First, I’ll render the upstream jenkins/chart to a file called jenkins-base.yaml:
git clone https://github.com/kubernetes/charts.git mkdir charts/stable/my-jenkins cd charts/stable helm template -n ci jenkins > my-jenkins/jenkins-base.yaml
- Now I’ll have helm template individually render out the templates I’d like to override
helm template -n ci jenkins/ -x templates/jenkins-master-deployment.yaml > my-jenkins/master.yaml helm template -n ci jenkins/ -x templates/home-pvc.yaml > my-jenkins/home-pvc.yaml helm template -n ci jenkins/ -x templates/config.yaml > my-jenkins/config.yaml
- Next, I can customize each of these resources to my liking. Note here that anything that is unset in my patched manifests will take the value of the base manifests (jenkins-base.yaml) that come from the chart defaults. The end result can be found in this repo:
https://github.com/viglesiasce/jenkins-chart-kustomize/tree/master
- Once we have the patches we’d like to apply we will tell Kustomize what our layout looks like via the kustomization.yaml file.
resources: - jenkins-base.yaml patches: - config.yaml - home-pvc.yaml - master.yaml
- Now we can install our customized chart into our cluster.
cd my-jenkins kustomize build | kubectl apply -f -
I hit an issue here because Helm renders empty resources due to the way that charts enable/disable certain resources from being deployed. Kustomize does not handle these empty resources properly so I had to remove them manually from my jenkins-base.yaml.
- Now we can port forward to our Jenkins instance and login at http://localhost:8080 with username admin and password foobar:
export JENKINS_POD=$(kubectl get pods -l app=ci-jenkins -o jsonpath='{.items[0].metadata.name}') kubectl port-forward $JENKINS_POD 8080
- Now I can check that my customizations worked. First off, I was able to log in with my custom password. YAY.
Next, in the Installed Plugins list, I can see that the Google Compute Engine plugin was installed for me. Double YAY.
Tradeoffs
Downsides
So this seems like a great way to customize upstream Helm charts but what am I missing out on by doing this? First, Helm is no longer controlling releases of manifests into the cluster. This means that you cannot use helm rollback or helm list or any of the helm release related commands to manage your deployments. With Helm+Kustomize model, you would do a rollback by reverting your commit and reapplying your previous manifests or by rolling forward to a configuration that works as expected and running your changes through your CD pipeline again.
Helm hooks are a bit wonky in this model since helm template doesn’t know what stage of a release you are currently on, it will dump all the hooks into your manifest. For example, in this example you’ll see that the Jenkins chart includes a test hook Pod that gets created but fails due to the deployment not being ready. Generally test hooks are run out of band of the installation.
Upsides
One of the nice things about this model is that I can now tweak a chart’s implementation without needing to submit the change upstream in order to keep in sync. For example, I can add my bespoke labels and annotations to resources as I see fit. When a chart that I depend on gets updated I can simply re-compute my base using helm template and leave my patches in tact.
Another benefit of using Helm with Kustomize is that I can keep my organization-specific changes separate from the base application. This allows for developers to be able to more clearly see the per-environment changes that are going on as they have less code to parse.
One last benefit is that since release management is no longer handled by Helm, I don’t have to have a Tiller installed and I can use tooling like kubectl, Weave Flux or Spinnaker to manage my deployments.
What’s Next
I’d like to work with the Helm, Charts and Kustomize teams to make this pattern as streamlined as possible. If you have ideas on ways to make these things smoother please reach out via Twitter.
Have you tried out https://github.com/replicatedhq/ship – seems like a streamed-lined way of doing what you laid out in this blog post.
Pingback: An Introduction to Kustomize - Scott's Weblog - The weblog of an IT pro focusing on cloud computing, Kubernetes, Linux, containers, and networking
Pingback: An Introduction to Kustomize - s0x
Pingback: Краткое введение в Kustomize – flant-blog