Terraform vs. Helm for Kubernetes

DZone 's Guide to

Terraform vs. Helm for Kubernetes

Time for a competition! When it comes to Helm, Terraform, and K8s, which infrastructure provisioning tool beats the other out and why? Let's take both for a test drive.

· Cloud Zone ·
Free Resource

I have been an avid user of Terraform and use it to do many things in my infrastructure, be it provisioning machines or setting up my whole stack. When I started working with Helm, my first impression was “Wow! This is like Terraform for Kubernetes resources!” What re-kindled these thoughts again was when, a few weeks ago, Hashicorp announced support for Kubernetes provider via Terraform. So one can use Terraform to provision their infrastructure as well as to manage Kubernetes resources. So I decided to take both for a test drive and see what works better in one vs. the other. Before we get to the meat, a quick recap of similarities and differences. For brevity in this blog post, when I mention Terraform, I am referring to the Terraform Kubernetes provider.

You might also enjoy Linode's Beginner's Guide to Terraform.

There are some key similarities:

  • Both allow you to describe and maintain your Kubernetes objects as code. Helm uses the standard manifests along with Go-templates, whereas terraform uses the JSON/HCL file format.
  • Both allow usage of variables and overwriting those variables at various levels such as file and command line, and Terraform additionally supports environment variables.
  • Both support modularity (Helm has sub-charts while Terraform has modules).
  • Both provide a curated list of packages (Helm has stable and incubator charts, while Terraform has recently started the Terraform Module Registry, though there are no Terraform modules in the registry that work on Kubernetes as of this post).
  • Both allow installation from multiple sources such as local directories and git repositories.
  • Both allow dry runs of actions before actually running them (helm has a –dry-run flag, while Terraform has the plan subcommand).

With this premise in mind, I set out to try and understand the differences between the two. I took a simple use case with following objectives:

  • Install a Kubernetes cluster (Possible with Terraform only)
  • Install GuestBook
  • Upgrade GuestBook
  • Roll back the upgrade

Setup: Provisioning the Kubernetes Cluster

In this step, we will create a Kubernetes cluster via Terraform using these steps: 

  • Clone this git repo
  • The kubectl, Terraform, ssh, and Helm binaries should be available in the shell you are working with.
  • Create a file called `terraform.tfvars` with the following content:
do_token = "YOUR_DigitaOcean_Access_Token"
# You will need a token for kubeadm and can be generated using following command:
# python -c 'import random; print "%0x.%0x" % (random.SystemRandom().getrandbits(3*8), random.SystemRandom().getrandbits(8*8))'

# private_key_file = "/c/users/harshal/id_rsa"
private_key_file = "PATH_TO_YOUR_PRIVATE_KEY_FILE"
# public_key_file = "/c/users/harshal/id_rsa.pub"
public_key_file = "PATH_TO_YOUR_PUBLIC_KEY_FILE"

Now we will run a set of commands to provision the cluster:

  • terraform get so Terraform picks up all modules

  • terraform init so Terraform will pull all required plugins

  • terraform plan to validate whether everything shall run as expected.

  • terraform apply -target=module.do-k8s-cluster

This will create a 1-master, 3-worker Kubernetes cluster and copy a file called `admin.conf` to `${PWD}`, which can be used to interact with the cluster via kubectl. Run `export KUBECONFIG=${PWD}/admin.conf` to use this file for this session. You can also copy this file to `~/.kube/config` if required. Ensure your cluster is ready by running `kubectl get nodes`. The output should show all nodes in Ready status.

master Ready 4m v1.8.2
node1 Ready 2m v1.8.2
node2 Ready 2m v1.8.2
node3 Ready 2m v1.8.2

Terraform Kubernetes Provider

Install GuestBook

Once you run terraform apply -target=module.gb-app, verify that all pods and services are created by running kubectl get all. You should now be able to access GuestBook on node port 31080. You will notice that we have implemented GuestBook using Replication Controllers and not Deployments. That is because the Kubernetes provider in Terraform does not support beta resources. More discussion on this can be found here. Under the hood, we are using simple declaration files and mainly rc.tf and services.tf files in the gb-module directory, which should be self-explanatory.

Update GuestBook

Since the application is deployed via Replication Controllers, changing the image is not enough. We would need to scale down old pods and scale up new pods. So we will scale down the RC to 0 pods and then scale it up again with the new image. Run: 

terraform apply -var 'fe_replicas=0' && terraform apply -var 'fe_image=harshals/gb-frontend:1.0' -var 'fe_replicas=3'

Verify the updated application at node port 31080.

Roll Back the Application

Again, without deployments, rolling back RC is a little more tedious. We scale down the RC to 0 and then bring back the old image. Run:

terraform apply -var 'fe_replicas=0' && terraform apply

This will bring the pods back to their default version and replica count.


Install GuestBook

Now we will perform the installation of GuestBook on the same cluster in a different namespace using Helm. Ensure you are pointing to the correct cluster by running `export KUBECONFIG=${PWD}/admin.conf`

Since we are running Kubernetes 1.8.2 with RBAC, run the following commands to give tiller the required privileges and initialize Helm:

kubectl -n kube-system create sa tiller
kubectl create clusterrolebinding tiller --clusterrole cluster-admin --serviceaccount=kube-system:tiller
helm init --service-account tiller --upgrade

(Courtesy: https://gist.github.com/mgoodness/bd887830cd5d483446cc4cd3cb7db09d )

Run the following command to install GuestBook on namespace “helm-gb”:

helm install --name helm-gb-chart --namespace helm-gb ./helm_chart_guestbook

Verify that all pods and services are created by running helm status helm-gb-chart

Upgrade GuestBook

In order to perform a similar upgrade via Helm, run following command:

helm upgrade helm-gb-chart --set frontend.image=harshals/gb-frontend:1.0

Since the application is using Deployments, the upgrade is a lot easier. To view the upgrade taking place and old pods being replaced with new ones, run:

helm status helm-gb-chart


The revision history of the chart can be viewed via helm history helm-gb-chart

Run the following command to perform the rollback:

helm rollback helm-gb-chart 1

Run helm history helm-gb-chart to get rollback confirmation as shown below:

Image title


Be sure to clean up the cluster by running terraform destroy.

Pros and Cons

We already saw the similarities between helm and terraform pertaining to the management of Kubernetes resources. Now let’s look at what works well and doesn’t with each of them.

Terraform Pros

  • Use of the same tool and code base for infrastructure as well as cluster management, including the Kubernetes resources. So a team already comfortable with Terraform can easily extend it to be used with Kubernetes.
  • Terraform does not install any component inside the Kubernetes cluster, whereas Helm installs tiller. This can be seen as a positive, but tiller does some real-time management of running pods ,which we will talk in a bit.

Terraform Cons

  • No support for beta resources. While many implementations are already working with beta resources such as Deployment, Daemonset, and StatefulSet, not having these available via Terraform reduces the incentive for working with it.
  • In case of a scenario where there is a dependency between two providers (Module do-k8s-cluster creates admin.conf and the Kubernetes provider of module gb-app refers to it), a single Terraform action will not work. The order of execution has to be maintained and run in the following format: `terraform apply -target=module.do-k8s-cluster && terraform apply -target=gb-app`. This becomes tedious and hard to manage when there are multiple modules that could have provider-based dependencies. This is still an open issue within Terraform, and more details can be found here.
  • Terraform's Kubernetes provider is still fairly new.

Helm Pros

  • Since Helm makes API calls to the tiller, all Kubernetes resources are supported. Moreover, Helm templates have advanced constructs such as flow control and pipelines, resulting in a lot more flexible deployment template.
  • Upgrades and rollbacks are very well-implemented and easy to handle in Helm. Also, running tiller inside the cluster managed the runtime resources effectively.
  • The Helm charts repository has a lot of useful charts for various applications.

Helm Cons

  • Helm becomes an additional tool to be maintained, apart from existing tools for infrastructure creation and configuration management.


In terms of sheer capabilities, Helm is far more mature as of today and makes it really simple for the end user to adopt and use it. A great variety of charts also give you a head start, and you don’t have to re-invent the wheel. Helm’s tiller component provides a lot of capabilities at runtime that aren't present in Terraform due to inherent nature of the way it is used.

On the other hand, Terraform can provision machines, clusters, and seamlessly manage resources, making it a single tool to learn and manage all of your infrastructure. That being said, for managing the applications/resources inside a Kubernetes cluster, you have to do a lot of work — and lack of support for beta objects makes it all the more impossible.

I would personally go about using Terraform for provisioning cloud resources and Kubernetes and Helm for deploying applications. Time will tell if Terraform gets better at application/resources provisioning in Kubernetes clusters.

cloud, helm, kubernetes cluster, terraform, tutorial

Published at DZone with permission of Harshal Shah , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}