in ASP.NET Core Kubernetes Docker DevOps ~ 6 min read.

An introduction to deploying applications with Helm
Deploying ASP.NET Core applications to Kubernetes - Part 3

In the first post in this series I described some of the fundamental Kubernetes resources that you need to understand when deploying applications, like pods, deployments, and services. In my previous post I described the YAML manifests that are used to define and create these resources.

In this post, I'll show one approach to deploying those resources to a Kubernetes cluster. Most tutorials on Kubernetes show how to deploy resources by passing YAML files to the the kubectl command line tool. This is fine when you're initially getting started with Kubernetes, but it's less useful when you come to deploy your apps in practice. Instead, in this post I describe Helm and discuss some of the benefits it can provide for managing and deploying your applications.

What is Helm?

From the Helm GitHub repository:

Helm is a tool for managing Kubernetes charts. Charts are packages of pre-configured Kubernetes resources.

So Helm is a tool for managing Kubernetes charts—but what's a chart?!

Helm charts as a unit of deployment

When you deploy an application to Kubernetes, you typically need to configure many different resources. If you consider a publicly-facing ASP.NET Core application, you typically need as a minimum:

  • A deployment, containing a pod consisting of your Dockerised ASP.NET Core application
  • A service, acting as a load balancer for the pods making up your deployment
  • An ingress, to expose the service as an HTTP endpoint

Each of those resources is defined in a separate YAML manifest, but your application logically requires all of those components to function correctly - you need to deploy them as a unit.

A Helm chart is a definition of the resources that are required to run an application in Kubernetes. Instead of having to think about all of the various deployments/services/ingresses that make up your application, you can use a command like

helm install stable/redis

and Helm will make sure all the required resources are installed. The above command will install the standard redis chart. Behind the scenes, a helm chart is essentially a bunch of YAML manifests that define all the resources required by the application. Helm takes care of creating the resources in Kubernetes (where they don't exist) and removing old resources.

The stable/redis chart used above includes 15 different Kubernetes resources. Packaging them in this way is certainly more convenient to install, but its real power comes when you need to update your application.

Parameterising manifests using Helm templates

Let's consider that you have an application which you have Dockerised into an image, my-app, and that you wish to deploy with Kubernetes. Without helm, you would create the YAML manifests defining the deployment, service, and ingress, and apply them to your Kubernetes cluster using kubectl apply. Initially, your application is version 1, and so the Docker image is tagged as my-app:1.0.0. A simple deployment manifest might look something like the following:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
  labels:
    app: my-app
spec:
  replicas: 3
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: "my-app:1.0.0"
        ports:
        - containerPort: 80

Now lets imagine you produce another version of your app, version 1.1.0. How do you deploy that? Assuming nothing needs to be changed with the service or ingress, it may be as simple as copying the deployment manifest and replacing the image defined in the spec section. You would then re-apply this manifest to the cluster, and the deployment would be updated, performing a rolling-update as I described in my first post.

The main problem with this is that all of the values specific to your application – the labels and the image names etc – are mixed up with the "mechanical" definition of the manifest.

Helm tackles this by splitting the configuration of a chart out from its basic definition. For example, instead of baking the name of your app or the specific container image into the manifest, you can provide those when you install the chart into the cluster.

For example, a simple templated version of the previous deployment might look like the following:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-deployment
  labels:
    app: "{{ template "name" . }}"
spec:
  replicas: 3
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  selector:
    matchLabels:
      app: "{{ template "name" . }}"
  template:
    metadata:
      labels:
        app: "{{ template "name" . }}"
    spec:
      containers:
      - name: "{{ template "name" . }}"
        image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
        ports:
        - containerPort: 80

This example demonstrates a number of features of Helm templates:

  • The template is based on YAML, with {{ }} mustache syntax defining dynamic sections.
  • Helm provides various variables that are populated at install time. For example, the {{.Release.Name}} allows you to change the name of the resource at runtime by using the release name. Installing a Helm chart creates a release (this is a Helm concept rather than a Kubernetes concept).
  • You can define helper methods in external files. The {{template "name"}} call gets a safe name for the app, given the name of the Helm chart (but which can be overridden). By using helper functions, you can reduce the duplication of static values (like my-app), and hopefully reduce the risk of typos.
  • You can manually provide configuration at runtime. The {{.Values.image.name}} value for example is taken from a set of default values, or from values provided when you call helm install.

There are many different ways to provide the configuration values needed to install a chart using Helm. Typically, you would use two approaches:

  1. A values.yaml file that is part of the chart itself. This typically provides default values for the configuration, as well as serving as documentation for the various configuration values
  2. Values provided on the command line at runtime when installing a chart.

When providing configuration on the command line, you can either supply a file of configuration values using -f config.yaml:

helm install -f config.yaml --name my-release stable/redis

or you can override individual values using --set myvalue=2:

helm install --set image.tag=5.0.0 --name my-release stable/redis

In both of these examples I also provided a name for the release, my-release. This will be used by Helm to populate the .Release.Name value.

When you run the helm install command above, this creates a Helm release. Helm applies the provided configuration, in this case the image.tag value, to all of the manifests associated with the stable/redis Helm chart. It then applies the manifests to your helm cluster, adding and removing resources as necessary to achieve the desired state.

The added complexity of Helm charts

On the face of it, adding Helm to the already complex set of things to learn before you can deploy to Kubernetes may seem unnecessary. It's definitely not required, but I think it makes a lot of sense to just start with it. As an ASP.NET Core developer, you're likely already familiar with the concept of separating configuration from the implementation, and that's essentially what Helm does.

Another benefit, is that I find that most of the charts for the ASP.NET Core applications I've written end up looking almost identical. The only real differences are in the configuration values. That means creating a helm chart for a new application often involves a simple copy-paste of the chart, and tweaking a couple of values in the configuration file.

In previous versions of Helm, you had to install two tools to use Helm: a client-side tool, and a service inside your Kubernetes cluster. Thankfully, in Helm version 3 they removed the server-side service. That makes the installation process for Helm much simpler, and in general just involves downloading a binary to your local machine.

Helm makes it easy to deploy applications, whether that's your own apps, an app like ghost blog, or an infrastructure "app" like Redis or ElasticSearch. In later posts in these series we'll be tweaking helm charts to work with ASP.NET Core applications, so it's worth getting to grips with them.

The documentation site is very detailed, but can be a little dry initially. Nevertheless, it's a great resource when you start authoring templates.

In the next post we'll get deeper into Helm charts and templates, and will look at creating our own chart for a small ASP.NET Core app. In the meantime, if you haven't already, consider installing Helm and experiment with installing and uninstalling charts in a test Kubernetes cluster. The later posts in this series assume a basic familiarity with the process, rather than providing an in-depth introduction.

Summary

In this post, I provided a brief overview of Helm, and described some of the benefits it provides over manually managing your applications in Kubernetes. Helm charts are packages of Kubernetes resources which are installed into a Kubernetes cluster as a unit. Helm templates, which make up a chart, separate the definition of a resource, which is largely static, from its configuration, which may differ with each installation.

Loading comments powered by Disqus, please wait…
Andrew Lock | .Net Escapades

Stay up to the date with the latest posts!

Oops! Check your details and try again.
Thanks! Check your email for confirmation.