Tillerless Helm v2

Helm really became a de-facto as Kubernetes Package Manager.

Helm is the best way to find, share, and use software built for Kubernetes as it states on https://helm.sh.

That's true and sounds very cool.

Since Helm v2, helm got a server part called The Tiller Server which is an in-cluster server that interacts with the helm client, and interfaces with the Kubernetes API server. The server is responsible for the following (copypasta from https://helm.sh):

  • Listening for incoming requests from the Helm client
  • Combining a chart and configuration to build a release
  • Installing charts into Kubernetes, and then tracking the subsequent release
  • Upgrading and uninstalling charts by interacting with Kubernetes

Default Helm + Tiller setup

By default helm init installs a Tiller deployment to Kubernetes clusters and communicates via gRPC, and it is up to you to make Tiller more secure.

helm plus tiller setup

In a nutshell, the client is responsible for managing charts, and the server is responsible for managing releases.

It looks as a nice setup. Which of course works very well, but has some security issues, and the main one is that tiller runs in your Kubernetes cluster with full administrative rights - which is a risk is somebody gets unauthorised access to the cluster. If we bring multi tiller setup e.g. each team gets it's tiller then it gets worse with TLS and etc issues.

Helm v3

We had Helm Summit last February where the community voted that Helm v3 should be Tillerless. You can read the Helm v3 Design Proposal here. Also Matt Butcher has written a nice article A First Look at the Helm v3 Plan which I recommend for more background.

Looking to the Helm v3 proposal it looks to have some nice features, and is going to be way more secure, but it is not released yet (you can test pre-alpha version already). But in the meantime we need to live on and use Helm v2 for our releases.

"Tillerless" Helm v2 setup

It looks like it is not so hard to have Tillerless Helm v2. So let me go to more details and explain how to make it work.

It is so easy to run helm and tiller on your workstation or in CI/CD pipelines without installing tiller to your Kubernetes cluster:

$ export TILLER_NAMESPACE=my-team-namespace
$ export HELM_HOST=localhost:44134
$ tiller --storage=secret
[main] 2018/07/23 15:52:29 Starting Tiller v2.10.0 (tls=false)
[main] 2018/07/23 15:52:29 GRPC listening on :44134
[main] 2018/07/23 15:52:29 Probes listening on :44135
[main] 2018/07/23 15:52:29 Storage driver is Secret
[main] 2018/07/23 15:52:29 Max history per release is 0

tillerless helm v2 setup

That's it, tiller uses the same kubeconfig as helm to connect to your cluster and as you see above you can pass the namespace which tiller will use to store helm releases. You can have many namespaces that way and just pass it when tiller starts. And a big plus not a single tiller instance running in your Kubernetes cluster. Your user's RBAC rules have to allow to store secrets/configmaps (tiller's storage) in that namespace and that's it, no more service accounts and other RBAc rules for your tiller. :)

Note: Initialize helm with helm init --client-only, flag --client-only is a must as otherwise you will get Tiller installed in to Kubernetes cluster.

"Tillerless" Helm v2 plugin

So how to make it even easier to install? I have made a simple "Tillerless" Helm plugin. If you aren't familiar with Helm plugins read it here.

Let's install the plugin:

$ helm plugin install https://github.com/rimusz/helm-tiller
Installed plugin: tiller

It has four simple commands start, start-ci, stop and run.

How to use this plugin locally

For the local development or accessing remote Kubernetes cluster use helm tiller start:

$ helm tiller start
Client: &version.Version{SemVer:"v2.10.0", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
[main] 2018/07/23 16:08:07 Starting Tiller v2.10.0 (tls=false)
[main] 2018/07/23 16:08:07 GRPC listening on :44134
[main] 2018/07/23 16:08:07 Probes listening on :44135
[main] 2018/07/23 16:08:07 Storage driver is Secret
[main] 2018/07/23 16:08:07 Max history per release is 0
Server: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
Tiller namespace: kube-system
bash-3.2$

It will start the tiller locally and kube-system namespace will be used to store helm releases (the default tiller setup).

Also we can provide the namespace for the tiller:

$ helm tiller start my-team-namespace
Client: &version.Version{SemVer:"v2.10.0", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
[main] 2018/07/23 16:08:38 Starting Tiller v2.10.0 (tls=false)
[main] 2018/07/23 16:08:38 GRPC listening on :44134
[main] 2018/07/23 16:08:38 Probes listening on :44135
[main] 2018/07/23 16:08:38 Storage driver is Secret
[main] 2018/07/23 16:08:38 Max history per release is 0
Server: &version.Version{SemVer:"v2.10.0", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
Tiller namespace: my-team-namespace
bash-3.2$

It also opens a new bash shell with pre-set HELM_HOST=localhost:44134 so helm knows how to connect to tiller.

Now you are ready to deploy/update your helm releases.

You're done? Then just run:

$ exit
$ helm tiller stop
/Users/user/.helm/plugins/helm-tiller
Stopping Tiller..

Again you can pass another namespace and then deploy/update your other team helm releases.

How to use this plugin in CI/CD pipelines

But what about using the plugin in your CI/CD pipelines?

You have two options here.

The first one is very similar the one we looked at above, but this one doesn't start the pre-set bash shell.

$ helm tiller start-ci
$ export HELM_HOST=localhost:44134

Then your helm will know where to connect to Tiller and you do not need to make any changes in your CI pipelines.

And at the end of the pipeline include:

$ helm tiller stop

Nice, isn't it!

The second one is easy as to run helm tiller run which starts/stops tiller before/after the specified command:

$ helm tiller run helm list
$ helm tiller run my-team-namespace -- helm list
$ helm tiller run my-team-namespace -- bash -c 'echo running helm; helm list'

Let's check it out:

$ helm tiller run test -- bash -c 'echo running helm; helm list'
Installed Helm version v2.10.0
Installing Tiller v2.10.0 ...
x tiller
args=bash -c echo running helm; helm list
Client: &version.Version{SemVer:"v2.10.0", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
[main] 2018/07/25 17:33:54 Starting Tiller v2.10.0 (tls=false)
[main] 2018/07/25 17:33:54 GRPC listening on :44134
[main] 2018/07/25 17:33:54 Probes listening on :44135
[main] 2018/07/25 17:33:54 Storage driver is Secret
[main] 2018/07/25 17:33:54 Max history per release is 0
Server: &version.Version{SemVer:"v2.10.0", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
Tiller namespace: test
running helm
[storage] 2018/07/25 17:33:55 listing all releases with filter
NAME	REVISION	UPDATED                 	STATUS  	CHART     	NAMESPACE
keel	1       	Sun Jul 22 15:42:43 2018	DEPLOYED	keel-0.5.0	default
Stopping Tiller...

Amazing! :)

Bonus feature: The plugin checks for installed helm and tiller versions, if they do not match then it downloads the correct tiller version file.

Using with GCP Cloud Build

Also I have made a few examples of using Tillerless Helm plugin in GCP Cloud Build pipelines with Helm tool builder.

Migration from Helm v2 to v3

Helm v3 got released, check it out my blog post How to migrate from Helm v2 to Helm v3.