Kustomize vs Helm vs Kubes: Kubernetes Deploy Tools
Today, we’ll talk about some deployment tools in the Kubernetes world. We’ll talk a little bit about why tools are used on top of kubectl. We’ll compare 3 different tools in the Kubernetes world focused on the deployment side of things: Kustomize, Helm, and Kubes. Then we’ll elaborate on the features that Kubes offers.
Kubectl with Simple Wrappers
Most folks start off with kubectl
commands to create their Kubernetes resources. It’s simple. It’s also important to learn how to use kubectl
commands to establish fundamentals. Eventually, you grow tired of typing the same commands repeatedly, though. So you write a wrapper bash script. Example:
kubectl-wrapper.sh
kubectl apply -f service.yaml
kubectl apply -f deployment.yaml
Bash shines for simple scripts and light glue, but it can quickly get messy as the script takes on more things to do. For example, what happens when you need another env like dev and prod?
You may start structuring things like this:
├── dev
│ ├── deployment.yaml
│ └── service.yaml
└── prod
├── deployment.yaml
└── service.yaml
And write a wrapper script that selects the folder:
kubectl-wrapper.sh
KUBE_ENV=${1:-dev}
kubectl apply -f $KUBE_ENV/service.yaml
kubectl apply -f $KUBE_ENV/deployment.yaml
The issue is duplication of service.yaml
and deployment.yaml
. Instead, it’ll be nice if we use the same YAML and create a different env like dev and prod with it. Things like envsubst
to replace variables from the same “template” YAML files can help. As requirements increases though, the simple bash glue scripts end up getting messy.
Using kubectl
with simple wrapper scripts is like using a simple manual screwdriver. Eventually, you want to be more efficient. We want a power drill. We’ll discuss some power tools next.
- Source Code: Kubectl Examples
Kubectl vs Kustomize
Kustomize started off as a project outside of kubectl. A version of it is now built into the kubectl
command. Kustomize allows you to write a kustomization.yaml
that decorate existing YAML Kubernetes files.
Kubectl Structure
Here’s an example Kubectl project structure:
├── base
│ ├── deployment.yaml
│ ├── kustomization.yaml
│ └── service.yaml
└── overlays
├── dev
│ ├── deployment.yaml
│ ├── kustomization.yaml
│ └── namespace.yaml
└── prod
├── deployment.yaml
├── kustomization.yaml
└── namespace.yaml
Using Kustomize to Create Different Environments
The provided structure allows you to use the same code to create different environments. To create different dev and prod environments, we use overlays:
kubectl apply -k overlays/dev
kubectl apply -k overlays/prod
Kustomize has a purist perspective. It uses YAML only to decorate and build new YAML files.
While we can appreciate the good intentions of trying to keep everything in YAML and avoid context switching, Kustomize puts too much logic into YAML. In a Kubernetes world where the amount of YAML we have to use can sometimes become embarrassing, Kustomize opts to use even more YAML. Feel like when we start seeing YAML that contains verbs representing method calls, we may be going in the wrong direction. It’s hard to solve the problem of YAML with even more YAML.
Additionally, Kustomize features generator methods as a way to remove duplication, but you can only get so far with the methods. You end up with some duplication in the kustomization.yaml
and namespace.yaml
files.
Source code: Kustomize Examples
Kustomize vs Helm
The Helm approach to building YAML files takes an entirely different direction. Instead of a purist YAML approach, Helm uses templating logic.
Helm Structure
Here’s an example Helm project structure:
├── Chart.yaml
├── templates
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ └── service.yaml
└── values.yaml
Templates like deployment.yaml
and service.yaml
reside in the templates folder. The values.yaml
file provides default variables values to substitute. Let’s take a look at part of a template to understand how the templating works:
templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
The templating language is a mixture of the Go template language and the Sprig template library.
Helm’s templating approach allows us to use conditional logic like if statements and methods. It’s much more powerful. At the same time, it’s quite difficult to read. It reminds me of PHP and gets spaghetti-like quickly. Also, since YAML must be properly aligned, it can get error-prone.
Helm Different Environments
To achieve different envs with the same code, we can use different variables. Here are example commands:
helm install chart-dev . --namespace chart-dev --create-namespace -f values/dev.yaml
helm install chart-prod . --namespace chart-prod --create-namespace -f values/prod.yaml
Helm creates the namespace outside of YAML and it’s lifecycle is not managed by Helm. The --create-namespace
option is only necessary once. To use different variable values, you use the -f
option. You can specify as many variables files as you wish.
You have to remember to type the CLI options. The command becomes verbose, and it’s easy to forget to type the right options. Typically, you end up writing bash wrapper script to reduce the risk of errors. For example, it could be something like this:
helm-wrapper.sh
HELM_ENV=${1:-dev}
helm install chart-$HELM_ENV . --namespace chart-$HELM_ENV --create-namespace -f values/$HELM_ENV.yaml
Usage would be like this:
helm-wrapper.sh dev
helm-wrapper.sh prod
Helm’s Greater Scope
Helm does a lot more than build YAML files from templates. Helm also supports hooks, rollbacks, packaging, and server for distribution. It’s a full package manager. So Helm’s scope is far greater than Kustomize, we’re somewhat comparing apples to oranges here.
Kustomize vs Helm vs Kubes
Kubes is another tool that handles deployment. Kubes has some similar concepts to both Kustomize and Helm and improves on them.
Kubes Structure
Here’s a Kubes directory structure.
.kubes/resources
├── base
│ ├── all.yaml
│ └── deployment.yaml
├── shared
│ └── namespace.yaml
└── web
├── deployment
│ ├── dev.yaml
│ └── prod.yaml
├── deployment.yaml
└── service.yaml
Kubes introduces a conventional folder structure. Conventions takes you a long way. Instead of spending time configuring and wiring files together with kustomization.yaml
like with Kustomize files or specifying CLI --namespace
and -f
options like with Helm, commands can become a lot shorter and more memorable.
How Kubes Works
Kubes works in a transparent and straightforward manner. The kubes deploy
command first builds the Docker image. Then it compiles Kubernetes YAML files. Lastly, it merely calls out to kubectl
.
In fact, you can use Kubes to build the files first and then run kubectl
directly. Example:
kubes docker build
kubes docker push
kubes compile # compiles the .kubes/resources files to .kubes/output
Now, use kubectl
directly and apply them in the proper order:
kubectl apply -f .kubes/output/shared/namespace.yaml
kubectl apply -f .kubes/output/web/service.yaml
kubectl apply -f .kubes/output/web/deployment.yaml
The deploy command simply do all 3 steps: build, compile, and apply.
kubes deploy
Layering: Multiple Environments like dev and prod
To deploy and create multiple environments like dev and prod with the same YAML, we use a different KUBES_ENV value:
KUBES_ENV=dev kubes deploy
KUBES_ENV=prod kubes deploy
The same code is used to create different environments. Kubes achieves this with a feature called Layering. The concept is similar to Kustomize overlays. Here’s the general layering processing order that Kubes takes.
- The
.kubes/resources/base
folder is treated as a base layer. It gets processed as pre-layers by Kubes. - Then Kubes will process your
.kubes/resources/ROLE
definitions. - Lastly, Kubes processes any post-layers in the
.kubes/resources/ROLE/KIND
folders.
Let’s focus on deployment.yaml
to explain and understand layering. Here are the files that get layered.
.kubes/resources/base/all.yaml
.kubes/resources/base/deployment.yaml
.kubes/resources/web/deployment.yaml
.kubes/resources/web/deployment/dev.yaml
Each file is merged together and produces a resulting YAML file:
.kubes/output/web/deployment.yaml
The final output deployment.yaml
is the combined layered YAML files.
The same layering processing logic runs for the other files too. Here are all the built output files:
.kubes/output/shared/namespace.yaml
.kubes/output/web/deployment.yaml
.kubes/output/web/service.yaml
With Kustomize, you must write kustomization.yaml
files to stitch together YAML files and define the overlays. With Kubes, it just works. The conventions allow the concept of multiple environments to be baked-in right off the bat. All you do is specify KUBES_ENV.
- Source Code: Kubes Examples
ERB Templating Support
Whereas Kustomize does not allow any templating logic, Helm goes all-in on templating logic. Kubes allows for both. Kubes merges YAML together via layering. Kubes also allows for templating logic via ERB. Here is an example of ERB usage:
.kubes/resources/shared/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: demo-<%= Kubes.env %>
labels:
app: demo
Notice the <%= Kubes.env %>
templating logic. When is KUBES_ENV=dev
, then name: demo-dev
. When is KUBES_ENV=prod
, then name: demo-prod
.
Kubes templating support allows you to use ERB where it makes sense. This provides the best of each world.
Extendable
The templating logic with Kubes is simply Ruby ERB. Kubes has some built-in helpers. For example, Kubes uses the built-in helper docker_image
to automatically substitute the Docker image built from your Dockerfile. You can also extend Kubes and add user-defined custom helper methods.
With Helm, you can also add templating methods with custom helpers. The helper method definitions are awkward looking, though. Example:
templates/_helpers.tpl
{{- define "demo.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "demo.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
With Kubes, custom template helper definitions is just Ruby code. Example:
.kubes/helpers/my_helpers.rb
module MyHelpers
def database_endpoint
case Kubes.env
when "dev"
"dev-db.cbuqdmc3nqvb.us-west-2.rds.amazonaws.com"
when "prod"
"prod-db.cbuqdmc3nqvb.us-west-2.rds.amazonaws.com"
end
end
end
The Power of Ruby
Kubes is written in Ruby. This fact is transparent to the end-user. The starter learning guide take you through a gentle path, where you are using YAML just like you usually would. You also have access to Ruby, but it’s lightly added on top of YAML. Think about it as “Ruby Sprinkles.”
Modern-day DevOps shops use a variety of tools like bash, python, ruby, and go to achieve their goals. As the adage goes: use the right tool for the job. Language shouldn’t matter, but it does. Ruby is one of the most powerful languages to craft tools and glue things together to make your life easier. Ruby is a versatile language and is well-suited to achieve tools like Kubes.
Docker Build
You may have notice that Kubes also handles building the Docker image. Kustomize and Helm do not. Building the Docker image is one less thing for you to do. It streamlines the deploy workflow.
Also, if you wish to use a prebuilt docker image instead, you can also do that with Kubes. See the --image
option or .kubes/config.rb
in the Docker Image Docs. Kubes provides you options.
Hooks: Finer-Grain Control
Kustomize does not support hooks. Helm supports hooks. Like Helm, Kubes also supports hooks, but they provide finer-grain control.
A key difference between these tools is how kubectl apply
gets called. Essentially, both Kustomize and Helm generate a single YAML file and then runs kubectl apply
on it. Instead, Kubes generates separate YAML files and calls kubectl apply
on each file individually. Because of this, kubes hooks can run at fine-grain points. Example:
# hook can run here
kubectl apply -f .kubes/output/shared/namespace.yaml
# hook can run here
kubectl apply -f .kubes/output/web/service.yaml
# hook can run here
kubectl apply -f .kubes/output/web/deployment.yaml
# hook can run here
Note: Kubes also generates a single full.yaml
for your convenience and debugging.
The hooks are more fine-grain. More docs: Kubes Kubectl Hooks
Comparison Table
Here’s a summary comparison table.
Feature | Kustomize | Helm | Kubes |
---|---|---|---|
YAML methods | yes | no | no |
Templating | no | yes | yes |
Multiple Envs | yes | yes | yes |
Packaging | no | yes | no |
Docker Build | no | no | yes |
Summary
In this post, we covered the differences between Kustomize, Helm, and Kubes. Kustomize is built into the kubectl
command is more like a feature. You use kustomization.yaml
files to glue things together. Helm is a full package manager tool that also builds YAML files. Helm uses templating logic. Kubes allows for both YAML merging and templating. It provides additional conveniences like building the Docker image.
We did not cover all the features of these tools, for more info check out their docs sites:
Lastly, source code examples for each of these tools is available here:
Thanks for reading this far. If you found this article useful, I'd really appreciate it if you share this article so others can find it too! Thanks 😁 Also follow me on Twitter.
Got questions? Check out BoltOps.
You might also like
More tools:
-
Kubes
Kubes: Kubernetes Deployment Tool
Kubes is a Kubernetes Deployment Tool. It builds the docker image, creates the Kubernetes YAML, and runs kubectl apply. It automates the deployment process and saves you precious finger-typing energy.
-
Jets
Jets: The Ruby Serverless Framework
Ruby on Jets allows you to create and deploy serverless services with ease, and to seamlessly glue AWS services together with the most beautiful dynamic language: Ruby. It includes everything you need to build an API and deploy it to AWS Lambda. Jets leverages the power of Ruby to make serverless joyful for everyone.
-
Lono
Lono: The CloudFormation Framework
Building infrastructure-as-code is challenging. Lono makes it much easier and fun. It includes everything you need to manage and deploy infrastructure-as-code.