Helm & Argo CD#

Note

Helm is required to follow along with this documentation. If you don’t have Helm installed please follow the documentation here to install it.

Helm is a package manager for k8s. It refers to k8s packages as charts. Charts are a bundle of YAML definitions required to create an instance of a k8s application. Values can be set as variables in the YAML files which allow for more customization of charts and easier sharing and reproducibility. A chart has been created to deploy web based visualization applications and it can be used to define custom deployments based on user input.

Note

For web based applications hosted on the CISL cloud there is a Helm chart example here you can clone and utilize to create a chart for your application quickly. The included Jupyter Notebook file contains code that will create a custom values.yaml file based on user input. It will also run the helm install command for you as well. You do need a kubeconfig file and a namespace to run the example.

Creating Helm Charts#

The Helm Getting Started guide is an excellent resource when it comes to understanding how to create new Helm charts. The helm command includes an easy way to create a new Helm chart template with the following command:

helm create myproject

This command creates a new folder called myproject that contains a few files to start. For most use cases all the files are going to be removed or replaced with the content needed to deploy the objects that we’re interested in. To start let’s remove all the files created in the templates directory.

rm -rf myproject/templates/*

Instead of using the default files, the files required to deploy the k8s object for the application being deployed, a web app in this case, are going to be created and used instead. The web app being deployed in this case requires 3 different templates in total, deployment.yaml, service.yaml, and ingress.yaml.

values.yaml file#

The values.yaml file contains specific values to use in the Helm chart templates that are created. Typically the files created in the myproject/templates/ directory will use variables defined in double curly braces, {{ }}, to reference the variables set in the values.yaml file. These values can also be overridden in the CLI command with the --set flag. The syntax used to define and utilize the variables and values.yaml file will be covered in more detail in the next sections.

A list of values used in a Helm chart can be viewed by running the following command:

helm show values myproject

deployment.yaml file#

A Deployment is where the state of Pods are provided and maintained to the specifications declared in the object definition.

The deployment.yaml file is where the requirements for the application object are defined. This is where the application is named, the image to use is defined, the port to expose is selected, and any other application customizations are set. An example of a deployment.yaml file is as follows:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.webapp.name }}
  namespace: {{ .Release.Namespace }}
  labels:
    app: {{ .Values.webapp.name }}
    group: {{ .Values.webapp.group }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Values.webapp.name }}
  template:
    metadata:
      labels:
        app: {{ .Values.webapp.name }}
    spec:
      containers:
      - name: {{ .Values.webapp.name }}
        image: {{ .Values.webapp.container.image }}
        resources:
          limits:
            memory: {{ .Values.webapp.container.memory }}
            cpu: {{ .Values.webapp.container.cpu }}
        ports:
        - containerPort: {{ .Values.webapp.container.port }}

As mentioned previously this file utilizes variables defined in the values.yaml file and ones specified inline when running the helm install command. Looking at the first variable defined in name: one can breakdown how these variables are defined in the values.yaml file. To start the full variable is {{ .Values.webapp.name }}. The .Values start tells Helm to look in the values.yaml file. It then specifies the webapp line of that file and then under webapp to select the value assigned to name. A full example of a values.yaml file can be seen below:

replicaCount: 1

webapp:
  name: ncote-helm-test
  group: ncote-helm-test
  path: /viztest
  tls:
    fqdn: ncote-helm-test.k8s.ucar.edu
    secretName: incommon-cert-ncote-helm-test
  container: 
    image: ncote/ncar-viz
    port: 5001
    memory: 1G
    cpu: 2

The variables are defined by referencing the different indented layers of the file. For example if we wanted to supply our template files with the container imaged used in the deployment object we would specify it with the following {{ .Values.webapp.container.image }}. For base values like replicaCount in the example we would only have to supply {{ .Values.replicaCount }}. There is an outlier in the {{ .Release.Namespace }} variable because the namespace to utilize for the deployment must be specified in the CLI command and is then fed to this placeholder.

The full list of specific variables defined in the above values.yaml file is as follows:

  • .Values.replicaCount

  • .Values.webapp.name

  • .Values.webapp.group

  • .Values.webapp.path

  • .Values.webapp.tls.fqdn

  • .Values.webapp.tls.secretName

  • .Values.webapp.container.image

  • .Values.webapp.container.port

  • .Values.webapp.container.memory

  • .Values.webapp.container.cpu

Note

These values can be supplied inline with the following flag helm install --set webapp.name=example --set webapp.path=/example. It is recommended to create a values.yaml specific to your application for this instead of supplying the values inline.

service.yaml file#

A Service in k8s is a method to expose a network application that is running in one or more Pods.

An example of a service.yaml file is as follows:

apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.webapp.name }}
  namespace: {{ .Release.Namespace }}
  labels:
    group: {{ .Values.webapp.group }}
spec:
  ports:
  - port: {{ .Values.webapp.container.port }}
  selector:
    app: {{ .Values.webapp.name }}

This reuses the values supplied in the Deployment file. This is to map the Deployment/Pods directly to the Service and provide the Service with the network specifications.

ingress.yaml file#

A k8s Ingress object is one that maps an application exposed by a Service and routes access to external resources outside the k8s cluster, typically this uses HTTP(s)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Values.webapp.name }}
  namespace: {{ .Release.Namespace }}
  labels:
    group: {{ .Values.webapp.group }}
  annotations:
    cert-manager.io/cluster-issuer: "incommon"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
      - {{ .Values.webapp.tls.fqdn }}
      secretName: {{ .Values.webapp.tls.secretName }}
  rules:
  - host: {{ .Values.webapp.tls.fqdn }}
    http:
      paths:
      - path: {{ .Values.webapp.path }}
        pathType: Prefix
        backend:
          service:
            name: {{ .Values.webapp.name }}
            port:
              number: {{ .Values.webapp.container.port }}

Note

The values for cert-manager.io/cluster-issuer and ingressClassName are not variables and instead are explicitly defined. These should not be changed if you are using the NCAR | CISL provided k8s cluster. If you are deploying these to a different cluster you will want to match the cert-manager and ingress controller name to the cluster you are deploying to.

There’s a few important values to call out in the ingress.yaml file that have not been talked about so far. The first new variable is {{ .Values.webapp.tls.fqdn }} and this is the fully qualified domain name (FQDN) for the application. The NCAR provided k8s cluster utilizes ExternalDNS to assign a DNS record to the Ingress and ultimately the application being exposed.

Note

The ExternalDNS provided can only create new records in the .k8s.ucar.edu domain. It can not provision new subdomains. The FQDN value should be set to a hostname that is descriptive of your service followed by .k8s.ucar.edu.

The next important value is also in the tls field and it is {{ .Values.webapp.tls.secretName }}. This secret is the location of the TLS certificate assigned to the FQDN requested by the ingress. This value should be unique to a FQDN, but can be shared among different ingress objects that use different paths on top of the FQDN. Speaking of paths the next value to discuss is {{ .Values.webapp.path }}. This is what is appended at the end of your FQDN. For example a path value of / on top of example.k8s.ucar.edu would give the ingress a URL of [https://example.k8s.ucar.edu/] while a path value of /test would give the ingress a URL of https://example.k8s.ucar.edu/test.

Note

The NCAR | CISL k8s clusters ingress controller is setup to only provide HTTPS traffic over port 443 and utilizes cert-manager to apply valid TLS certificates to supply a trusted and secure connection to the applications exposed.