I was recently working on a solution to run DbUp scripts in a Kubernetes cluster.
A little bit of background information: We have an ASP.Net framework application which uses a SQL Server database. To upgrade the database with the latest changes, we use DbUp. We have a console app which uses DbUp to upgrade the database before each release.
Our ASP.Net application is in Azure Kubernetes Services (AKS) in a hybrid cluster. We have Linux and Windows nodes in the cluster. The application itself is in Windows nodes but we need Linux nodes to run Helm, Nginx ingress controller and any other Linux based tools which we may require.
To manage our application releases, we use helm charts. If you are not familiar with helm chart, you probably need to read about it first and continue reading this article :), but in short, it is essentially a simple way to manage Kubernetes packages. Here is the link to get more information.
Now get back to the problem which we wanted to solve. We want to run the DbUp console app just before we release our application using helm charts. One way could be adding a separate step in our release pipeline to run DbUp just before releasing the ASP.Net app. However, we wanted to have everything in helm if we could, therefore a better way is using helm chart hooks.
As mentioned here:
"Helm provides a hook mechanism to allow chart developers to intervene at certain points in a release's life cycle." What it means is you can ask Helm to let you jump in and run something at a certain point. For example before a release or after a release. That gave me the idea that helm chart hooks could be great for our use case. So all I needed was adding a Kubernetes job and hook it up to helm pre-install and pre-upgrade hooks. In this way, I could make sure that our DbUp code gets run before any release. If for any reason this step fails, the release doesn't even start.
Here is what the job template which I used:
{{- if .Values.dbup.upgradeDatabase }}
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-dbup"
labels:
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/instance: "{{ .Release.Name }}-dbup"
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
annotations:
"helm.sh/hook": pre-install, pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
spec:
template:
metadata:
name: "{{ .Release.Name }}-dbup"
labels:
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/instance: "{{ .Release.Name }}-dbup"
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
restartPolicy: Never
containers:
- name: dbup
image: "{{ .Values.imageName }}"
env:
- name: "ConnectionString"
valueFrom:
secretKeyRef:
key: ConnectionString
name: "{{ .Release.Name }}-dbup"
nodeSelector:
"beta.kubernetes.io/os": windows
{{- end }}
As you can see from the above code I need to create a secret to keeps my connectionstring. Here it is:
{{- if .Values.dbup.upgradeDatabase }}
apiVersion: v1
kind: Secret
metadata:
name: "{{ .Release.Name }}-dbup"
annotations:
"helm.sh/hook": pre-install, pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
data:
ConnectionString: {{ .Values.connectionString | b64enc }}
{{- end }}
I use "dbup.upgradeDatabase" flag in helm values files as a way to trigger the database upgrade.
Note that you can specify a weight for helm chart hooks. In this way you let Helm know which resource to create first. The weights will be ordered ascending. That's why I set -10 for the secret and -5 for the job. Because I want to make sure Helm will create the secret before the job.
These resources will be created before any release (by specifying helm.sh/hook) and they will be destroyed after release succeeds or fails (by specifying helm.sh/hook-delete-policy).
Top comments (0)