December 5, 2017

Day 5 - Do you want to build a helm chart?

By: Paul Czarkowski (@pczarkowski)

Edited By: Paul Stack (@stack72)

Kubernetes Kubernetes Kubernetes is the new Docker Docker Docker

“Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications.”

https://kubernetes.io

Remember way back in 2015 when all anybody would talk about was Docker even though nobody actually knew what it was or what to do with it? That’s where Kubernetes is right now. Before we get into Helm charts, a quick primer on Kubernetes is a good idea.

Kubernetes provides scheduling and management for your containerized applications as well as the networking and other necessary plumbing and surfaces its resources to the developer in the form of declarative manifests written in YAML or JSON.

A Pod is the smallest deployable unit that can be deployed with Kubernetes. It contains one or more collocated containers that share the same [internal] IP address. Generally a pod is just a single container, but if your application requires a sidecar container to share the same IP or a shared volume then you would declare multiple containers in the same pod.

A Pod is unmanaged and will not be recreated if the process inside the container ends. Kubernetes has a number of resources that build upon a pod to provide different types of lifecycle management such as a Deployment that will ensure the correct number of replicas of your Pod are running.

A Service provides a stable name, IP address, and DNS (if KubeDNS is enabled) across a number of pods and acts as a basic load balancer. This allows pods to easily communicate to each other and can also provide a way for Kubernetes to expose your pod externally.

Helm is a Package Manager for Kubernetes. It doesn’t package the containers (or Pods) directly but instead packages the kubernetes manifests used to build those Pods. It also provides a templating engine that allows you to deploy your application in a number of different scenarios and configurations.

[Helm] Charts are easy to create, version, share, and publish — so start using Helm and stop the copy-and-paste madness. – https://helm.sh/

Let’s Build a Helm Chart!

In order to follow along with this tutorial you will need to install the following:

If you are on a Mac you should be able to use the following to install the necessary bits:

$ brew cask install minikube
$ brew install kubernetes-helm

If you already have a Kubernetes manifest its very easy to turn it into a Helm Chart that you can then iterate over and improve as you need to add more flexibility to it. In fact your first iteration of a Helm chart can be your existing manifests tied together with a simple Chart.yaml file.

Prepare Environment

Bring up a test Kubernetes environment using Minikube:

$ minikube start
Starting local Kubernetes v1.7.5 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.

Wait a minute or so and then install Helm’s tiller service to Kubernetes:

$ helm init
$HELM_HOME has been configured at /home/XXXX/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Happy Helming!

If it fails out you may need to wait a few more minutes for minikube to become accessible.

Create a path to work in:

$ mkdir -p ~/development/my-first-helm-chart

$ cd ~/development/my-first-helm-chart

Create Example Kubernetes Manifest.

Writing a Helm Chart is easier when you’re starting with an existing set of Kubernetes manifests. One of the easiest ways to get a basic working manifest is to ask Kubernetes to run something and output the resultant manifest to a file.

Run a basic nginx Deployment and expose it via a NodePort Service:

$ mkdir -p templates

$ kubectl run example --image=nginx:alpine \
    -o yaml > templates/deployment.yaml
    
$ kubectl expose deployment example --port=80 --type=NodePort \
    -o yaml > templates/service.yaml

Minikube has some helper functions to let you easily find the URL of your service. run curl against your service to ensure that its running as expected:

$ minikube service example --url
http://192.168.99.100:30254

$ curl $(minikube service example --url)
...
<title>Welcome to nginx!</title>
...

You’ll see you now have two Kubernetes manifests saved. We can use these to bootstrap our helm charts:

$ tree
└── templates
    ├── deployment.yaml
    └── service.yaml

Explore the deployment.yaml file in a text editor. Following is an abbreviated version of it with comments to help you understand what some of the sections mean:

# These first two lines appear in every Kubernetes manifest and provide
# a way to declare the type of resource and the version of the API to
# interact with.
apiVersion: apps/v1beta1
kind: Deployment
# under metadata you set the resource's name and can assign labels to it
# these labels can be used to tie resources together. In the service.yaml
# file you'll see it refers back to this `run: example` label.
metadata:
  labels:
    run: example
  name: example
# how many replicas of the declared pod should I run ?
spec:
  replicas: 1
  selector:
    matchLabels:
      run: example
# the Pod that the Deployment will manage the lifecycle of.
# You can see once again the use of the label and the containers
# to run as part of the pod.
  template:
    metadata:
      labels:
        run: example
    spec:
      containers:
      - image: nginx:alpine
        imagePullPolicy: IfNotPresent
        name: example

Explore the service.yaml file in a text editor. Following is an abbreviated version of it:

apiVersion: v1
kind: Service
metadata:
  labels:
    run: example
  name: example
spec:
# The clusterIP is the IP address that other nodes can use to access the pods
# Since we didn't specify and IP Kubernetes picked one for us.
  clusterIP: 10.0.0.62
# The Port mappings for the service.
  ports:
  - nodePort: 32587
    port: 80
    protocol: TCP
    targetPort: 80
# Any pods that have this label will be exposed by this service.    
  selector:
    run: example
# All Kubernetes worker nodes will expose this service to the outside world
# on the port specified above as `nodePort`.
  type: NodePort

Delete the resources you just created so that you can move on to creating the Helm Chart:

$ kubectl delete service,deployment example
service "example" deleted
deployment "example" deleted

Create and Deploy a Basic Helm Chart

The minimum set of things needed for a valid helm chart is a set of templates (which we just created) and a Chart.yaml file which we need to create.

Copy and paste the following into your text editor of choice and save it as Chart.yaml:

Note: the file should be capitalized as shown above in order for Helm to use it correctly.

apiVersion: v1
description: My First Helm Chart
name: my-first-helm-chart
version: 0.1.0

We now have the the most basic Helm Chart possible:

$ tree
.
├── Chart.yaml
└── templates
    ├── deployment.yaml
    └── service.yaml

Next you should be able to install this helm chart giving it a release name of first and using the current directory as the source of the Helm Chart:

$ helm install -n example .
NAME:   example
LAST DEPLOYED: Wed Nov 22 10:55:11 2017
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1beta1/Deployment
NAME     DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
example  1        1        1           0          1s

==> v1/Service
NAME     CLUSTER-IP  EXTERNAL-IP  PORT(S)       AGE
example  10.0.0.62   <nodes>      80:32587/TCP  1s

Just as you did earlier you can use minikube to get the URL:

$ curl $(minikube service example --url)
...
<title>Welcome to nginx!</title>

Congratulations! You’ve just created and deployed your first Helm chart. However its a little bit basic, the next step is to add some templating to the manifests and update the deployment.

Add variables to your Helm Chart

In order to render templates you need a set of variables. Helm charts can come with a values.yaml file which declares a set of variables and their default values that can be used in your templates. Create a values.yaml file that looks like this:

replicaCount: 2
image: "nginx:alpine"

These values can be accessed in the templates using the golang templating engine. For example the value replicaCount would be written as {{ .Values.replicaCount }}. Helm also provides information about the Chart and Release that can be handy to utilize.

Update your templates/deployment.yaml to utilize our values:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  labels:
    run: "{{ .Release.Name }}"
    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    release: "{{ .Release.Name }}"
  name: "{{ .Release.Name }}"
  namespace: default
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      run: "{{ .Release.Name }}"
  template:
    metadata:
      labels:
        run: "{{ .Release.Name }}"
    spec:
      containers:
      - image: "{{ .Values.image }}"
        name: "{{ .Release.Name }}"

Edit your templates/service.yaml to look like:

apiVersion: v1
kind: Service
metadata:
  name: "{{ .Release.Name }}"
  labels:
    run: "{{ .Release.Name }}"
    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    release: "{{ .Release.Name }}"
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: "{{ .Release.Name }}"
  type: NodePort

Once your files are written out you should be able to update your deployment:

$ helm upgrade example .
Release "example" has been upgraded. Happy Helming!
LAST DEPLOYED: Wed Nov 22 11:12:25 2017
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Service
NAME     CLUSTER-IP  EXTERNAL-IP  PORT(S)       AGE
example  10.0.0.79   <nodes>      80:31664/TCP  14s

==> v1beta1/Deployment
NAME     DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
example  2        2        2           2          14s

You’ll notice that your Deployment now shows as having two replicas of your pod demonstrating that the replicas value provided has been applied:

$ kubectl get deployments
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
example   2         2         2            2           2m

$ kubectl get pods       
NAME                       READY     STATUS    RESTARTS   AGE
example-5c794cbb55-cvn4k   1/1       Running   0          2m
example-5c794cbb55-dc7gf   1/1       Running   0          2m

$ curl $(minikube service example --url)
...
<title>Welcome to nginx!</title>

You can override values on the command line when you install (or upgrade) a Release of your Helm Chart. Create a new release of your helm chart setting the image to apache instead of nginx:

$ helm install -n apache . --set image=httpd:alpine --set replicaCount=3
NAME:   apache
LAST DEPLOYED: Wed Nov 22 11:20:06 2017
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1beta1/Deployment
NAME    DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
apache  3        3        3           0          0s

==> v1/Service
NAME    CLUSTER-IP  EXTERNAL-IP  PORT(S)       AGE
apache  10.0.0.220  <nodes>      80:30841/TCP  0s

Kubernetes will now show two sets of Deployments and Services and their corresponding pods:

$ kubectl get svc,deployment,pod                                        
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
svc/apache       NodePort    10.0.0.220   <none>        80:30841/TCP   1m
svc/example      NodePort    10.0.0.79    <none>        80:31664/TCP   8m
svc/kubernetes   ClusterIP   10.0.0.1     <none>        443/TCP        58m

NAME             DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/apache    3         3         3            3           1m
deploy/example   2         2         2            2           8m

NAME                          READY     STATUS    RESTARTS   AGE
po/apache-5dc6dcd8b5-2xmpn    1/1       Running   0          1m
po/apache-5dc6dcd8b5-4kkt7    1/1       Running   0          1m
po/apache-5dc6dcd8b5-d2pvt    1/1       Running   0          1m
po/example-5c794cbb55-cvn4k   1/1       Running   0          8m
po/example-5c794cbb55-dc7gf   1/1       Running   0          8m

By templating the manifests earlier to use the Helm release name in the labels for the Kubernetes resources the Services for each release will only talk to its corresponding Deployments:

$ curl $(minikube service example --url)
...
<title>Welcome to nginx!</title>

$ curl $(minikube service apache --url)
<html><body><h1>It works!</h1></body></html>

Clean Up

Delete your helm deployments:

$ helm delete example --purge          
release "example" deleted

$ helm delete apache --purge          
release "apache" deleted

$ minikube delete
Deleting local Kubernetes cluster...
Machine deleted.

Summary

Congratulations you have deployed a Kubernetes cluster on your laptop using minikube and deployed a basic application to Kubernetes by creating a Deployment and a Service. You have also built your very first Helm chart and used the Helm templating engine to deploy different versions of the application.

Helm is a very powerful way to package up your Kubernetes manifests to make them extensible and portable. While it is quite complicated its fairly easy to get started with it and if you’re like me you’ll find yourself replacing the Kubernetes manifests in your code repos with Helm Charts.

There’s a lot more you can do with Helm, we’ve just scratched the surface. Enjoy using and learning more about them!

No comments:

Post a Comment