Deploying an application to Kubernetes with Helm
Introduction
This Tech Bite will show you how to deploy a simple frontend application to a Kubernetes cluster and the benefits of using Helm charts.
Plain k8s approach
In order to deploy our application and make it usable we have to generate 3 Kubernetes objects – Deployment, Service and Ingress. In the classical approach we need to create three files for each object.
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-test labels: app.kubernetes.io/name: nginx-test app.kubernetes.io/instance: ngx-test spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: nginx-test app.kubernetes.io/instance: ngx-test template: metadata: labels: app.kubernetes.io/name: nginx-test app.kubernetes.io/instance: ngx-test spec: serviceAccountName: default securityContext: {} containers: - name: nginx-test securityContext: {} image: "nginx" imagePullPolicy: Always ports: - name: http containerPort: 80 protocol: TCP
service.yaml
apiVersion: v1 kind: Service metadata: name: nginx-test labels: app.kubernetes.io/name: nginx-test app.kubernetes.io/instance: ngx-test spec: type: ClusterIP ports: - port: 80 targetPort: 80 protocol: TCP name: http selector: app.kubernetes.io/name: nginx-test app.kubernetes.io/instance: ngx-test
ingress.yaml
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: nginx-test labels: app.kubernetes.io/name: nginx-test app.kubernetes.io/instance: ngx-test annotations: cert-manager.io/cluster-issuer: letsencrypt-nginx spec: tls: - hosts: - "nginx-test.example.com" secretName: tls-nginx rules: - host: "nginx-test.example.com" http: paths: - path: / backend: serviceName: nginx-test servicePort: 80
However, this problem can be solved by merging those three yaml files into a single one and executing kubectl apply -f frontend-app.yaml
to spin up needed k8s objects.
But what if we have multiple environments? To which one should the application be deployed? For every environment we need to change the pod name, ingress host address etc… In order to replace those values we will need to create a yaml file for every environment with almost the same data.
The solution is simple – we are going to use Helm which is “The package manager for Kubernetes”.
Helm
As we have stated before, Helm is the package manager for Kubernetes applications which allows us to define blueprints for application deployment. All the necessary resources are going to be packed into Helm Charts and deployed to the cluster.
Let’s get to work and create a Helm chart for our application.
helm create test-app
After helm create command is executed we will get a test-app folder and the structure is going to be similar to this:
We are going to put the Kubernetes objects that we need to generate in the templates/
folder (deployment.yaml
, service.yaml
and ingress.yaml
). You will also notice the values.yaml
file which in combination with Helm templates generates our k8s objects. Our files, ready for chart deployment, should look like this:
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "test-app.fullname" . }}-ui labels: {{ include "test-app.labels" . | indent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app.kubernetes.io/name: {{ include "test-app.name" . }}-ui app.kubernetes.io/instance: {{ .Release.Name }} template: metadata: labels: app.kubernetes.io/name: {{ include "test-app.name" . }}-ui app.kubernetes.io/instance: {{ .Release.Name }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ template "test-app.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }}-ui securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP
service.yaml
apiVersion: v1 kind: Service metadata: name: {{ include "test-app.fullname" . }}-ui labels: {{ include "test-app.labels" . | indent 4 }} spec: type: {{ .Values.service.ui.type }} ports: - port: {{ .Values.service.ui.port }} targetPort: 80 protocol: TCP name: http selector: app.kubernetes.io/name: {{ include "test-app.name" . }}-ui app.kubernetes.io/instance: {{ .Release.Name }}
ingress.yaml
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: {{ $fullName }}-ui labels: {{ include "test-app.labels" . | indent 4 }} {{- with .Values.ingress.ui.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: {{- if .Values.ingress.ui.tls }} tls: {{- range .Values.ingress.ui.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.ui.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ . }} backend: serviceName: {{ $fullName }}-ui servicePort: {{ $svcPort }} {{- end }} {{- end }} {{- end }}
In our deployment.yml
template file you can see variable definitions between two pairs of curly braces. These values are just references to the values.yaml file. For example, image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}”
says that image name is going to take image.repository
and image.tag
values and generate full image name. A snippet of these values is shown below.
Once we have all the files set in the templates/
folder we can proceed with generating and deploying the chart to the k8s cluster.
helm install test-app app/
If the command was successfully executed, after running kubectl get po,svc,ing
we should see our objects up and running. In case we have dev and prod environments for the application, we can simply create values-dev.yml
and values-prod.yml
and reference them when creating charts.
helm install --namespace dev --create-namespace --values values-dev.yaml test-app app/
for DEV environment
helm install --namespace prod --create-namespace --values values-prod.yaml test-app app/
for PROD environment
--namespace
– defines namespace of the environment to which we are deploying
--create-namespace
– generates new namespace if it is not existing
--values
– location to values file
You can find the full example code on Github. Follow this link.
“Deploying an application to Kubernetes with Helm” Tech Bite was brought to you by Mujo Hadžimehanović, DevOps Engineer at Atlantbh.
Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.