loading...

Helm: reusable chart — named templates, and a generic chart for multiple applications

setevoy profile image Arseny Zinchenko Originally published at rtfm.co.ua on ・17 min read

Helm: reusable chart — named templates, and a generic chart for multiple applications

Our project is growing, and more and more applications are started on the AWS Elastic Kubernetes Service.

Finally, we’ve faced with the question already mentioned in the Helm: пошаговое создание чарта и деплоймента из Jenkins (Rus) — what to do with Kubernetes manifests and Helm templates when using a lot of similar applications?

Especially now, when we have the same code deployed from the same repository but three similar applications.

At first, we had an only one, then was added another one, and now we are preparing to release the third one.

As result, their chart now looks like the next:

$ tree -d k8s/
k8s/
├── app1-chart
│ ├── charts
│ ├── env
│ │ ├── dev
│ │ ├── dev-2
│ │ └── prod
│ └── templates
└── app2-chart
├── charts
├── env
│ ├── dev
│ ├── prod
│ └── stage
└── templates
Enter fullscreen mode Exit fullscreen mode

So, now I need to copy the same chart again?

Don’t like that idea at all, so let’s take a deeper view of the Helm templates and will try to create one chart which can be used to deploy three similar applications.

Contents

Deployment — the file’s structure

To think about future generic template’s structure let’s check which blocks will be common and shared among all applications and which will be different and will be included to the template.

Here, we will speak mostly about the deployment.yaml file and others will be done similar to it.

So:

  • the deployment.yaml file:
  • metadata:
  • name: common, a value will be taken from the values.yaml of a specific project
  • annotations: common and we have the only reloader.stakater.com/auto here and it always will be set to true (still, check the Kubernetes: ConfigMap and Secrets – data auto-reload in pods post about other available options)
  • labels: common and will be used in different places, for example in the same file below in the spec.template.metadata.labels and in the template with the Kubernetes Cronjobs, so it will be a good idea to move labels into the _helpers.yaml file
  • spec:
  • replicas: common, a value will be taken from the values.yaml of a specific project
  • strategy:
  • type: common, a value will be taken from the values.yaml of a specific project
  • selector:
  • matchLabels: also common and also will be used in different places, let's move it into the _helpers.yaml
  • template:
  • metadata:
  • labels: will be taken from the _helpers.yaml
  • spec:
  • containers:
  • name: common, a value will be taken from the values.yaml of a specific project
  • image: common, a value will be taken from the values.yaml of a specific project
  • env: the most interesting part of this post:
  1. we can move it into the values.yaml of each project
  2. pros: no need to specify values as they can be set directly here in the value fields of the variables
  3. cons:
  4. our variables are almost identical among all the projects — so they will be duplicated
  5. the values.yaml will be bloated as we have a lot of variables to be added
  6. not all variables are in the KEY:VALUE view — some will take their values from a valueFrom.secretKeyRef.<KUBE_SECRET_NAME>, thus will be unable to set its values directly in the values.yaml
  7. another way is to use the _helpers.yaml file where we will have a set of common variables, each with its own if/then - if the values.yaml of a project will not contain a value for a variable then such a variable will not be added into the generic template and thus will not break a project's deploy by adding a new variable
  8. and for a case when a project will have to have its own set of variables — we can add a condition before the env block by checking something like if {{ .Values.chartSettings.customEnvs == true }} and will skip adding the block from the _helpers.yaml or values.yaml and will use a dedicated file instead with something like tpl .File.Get project1/envs.yaml
  9. volumeMounts: also can differ, will think about it later
  10. ports:
  11. containerPort: will be always, will take its value from the values.yaml (btw, it's not necessary to add them here at all, see the Should I configure the ports in the Kubernetes deployment? post)
  12. livenessProbe, readinessProbe: will be common with the httpGet.path and httpGet.port - so can be moved into the _helpers.yaml but also add an option to include it via an external file with the .File.Get
  13. resources:
  14. requests.cpu, requests.memory - well... I guess will be added for all, but still, it's a question - need to think about it later check the Kubernetes: Evicted pods and Pods Quality of Service post for more details
  15. limits.cpu, limits.memory - the memory will be limited for all... or not... will think later
  16. volumes: also can differ, leave it for later
  17. imagePullSecrets: similar for all
  18. hpa.yaml - for the HPA
  19. network.yaml - Service, Ingress
  20. secrets.yaml - Kubernetes Secrets
  21. rbac.yaml - attaching user groups into a c namespace created by Helm during deployment
  22. cronjobs.yaml - Kubernetes Cronjobs
  23. _helpers.tpl - and our "helper" for the Helm Named Templates

So, basically, the main idea is the following:

  • a general chart with templates in the templates directory
  • in the templates will keep the deployment.yaml, hpa.yaml, _helpers.tpl, etc
  • near the templates will create an additional directory called projects
  • and inside it — catalogs by projects manes
  • project1
  • project2
  • project3
  • and inside each of them will have an env directory
  • with dev, stage, prod directories
  • with dedicated values.yaml and secrets.yaml files

Helm Named templates

Good post about named templates — How To Reduce Helm Chart Boilerplate With Named Templates (more links at the end of this post).

The official documentation is here>>>.

The very first block in our template which we’d like to move into the _helpers.yaml is labels.

The general idea behind the Named Templates in Helm is to write some code only once and then include it in every placed where we need for this code. Also, this helps to reduce a template’s content and make it more readable.

The named templates file name is stared with the underline with the tpl extension.

The most known file is the _helpers.tpl which we will use in this case.

Each template definition in this file is started with the define keyword and ended with the end.

A name for a named template usually includes a chart name and a block which is described the template, but you can use any other. For example, in our file, we will use the helpers. name.

The one inconvenience here is the fact that you can’t use values like .Chart.Name, but you can create a Helm's variable inside. Not sure about this, will see later, for now, will just hardcode the helpers.

Common labels

Well, which labels will be good to have at all?

  • environment — Dev, Stage, Prod — will be set from the values.yaml
  • appversion — will be set during a build in Jenkins via the --set option

For more good ideas we can ask Google — “kubernetes recommended labels”, and the very first link is the Recommended Labels.

Also, check the Labels and Annotations in the Helm’s documentation

Plus, it may be a good idea to take a look at some existing charts, for example, the sonarqube/templates/deployment.yaml.

So, for now, we can add four custom labels:

  • application: {{ .Values.appConfig.appName }}, will be set in the values.yaml for each application
  • version: {{ .Chart.Version }}-{{ .Chart.AppVersion }}, will be set from Jenkins
  • environment: {{ .Values.appConfig.appEnv }}, will be set in the values.yaml for each application
  • managed-by: {{ .Release.Service }} by Helm

And if we will want to add another one — we can do it in one place instead of updating all the places where labels are used.

define

Go back to our _helpers.tpl and describe our labels:

{{- define "helpers.labels" -}}
application: {{ .Values.appConfig.appName }}
version: {{ .Chart.Version }}-{{ .Chart.AppVersion }}
environment: {{ .Values.appConfig.appEnv }}
managed-by: {{ .Release.Service }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode

include

Then, using the include, specify where it must be included in the general deployment's template:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: {{ .Values.appConfig.appName }}-deployment
 labels: {{- include "helpers.labels" . | nindent 4 }}
...
Enter fullscreen mode Exit fullscreen mode

indent vs nindent

  • indent - just will add spaces
  • nindent - will add spaces plus a new line symbol

I.e. instead of writing something like this:

...
 labels: 
 {{- include "helpers.labels" . | indent 4 }}
...
Enter fullscreen mode Exit fullscreen mode

We can set it in one line and avoid typing whitespaces in the template:

...
labels: {{- include "helpers.labels" . | nindent 4 }}
...
Enter fullscreen mode Exit fullscreen mode

Also, this plays a role in adding a YAML block into the template, will see it in action below.

Going back to the named templates — let’s do the same for the spec.selector.matchLabels - move it to a dedicated template so we can re-use it later, for example in Services.

In the _helpers.tpl add a new block:

...
{{- define "helpers.selectorLabels" -}}
application: {{ .Values.appConfig.appName }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode

For now, we do a selection by an application name only, and later we can update it and include any other selectors.

Add it to the deployment:

...
 selector:
 matchLabels:
 {{- include "helpers.selectorLabels" . | nindent 6 }}
 template:
...
Enter fullscreen mode Exit fullscreen mode

Templates inside of a template

And we came to the most interesting part of the task — adding whole blocks into the template’s file.

So, in the currently used template, we have the env block:

...
 env:
 - name: APP_SECRET
 valueFrom:
 secretKeyRef:
 name: app-backend-secrets
 key: backend-app-secret
 - name: AUTH_SECRET
 valueFrom:
 secretKeyRef:
 name: app-backend-secrets
 key: backend-auth-secret
 - name: CLIENT_HOST
 value: {{ .Values.appConfig.clientHost }}
 - name: DATABASE_HOST
 value: {{ .Values.appConfig.db.host }}
 - name: DATABASE_SLAVE_HOST
 value: {{ .Values.appConfig.db.slave }}
 - name: DB_USERNAME
 value: {{ .Values.appConfig.db.user }}
 - name: INSTANA_AGENT_HOST
 valueFrom:
 fieldRef:
 fieldPath: status.hostIP
...
Enter fullscreen mode Exit fullscreen mode

Which we’d like to move to a block in another file and include it in the new template which we are writing now.

The env block from a project's values.yaml file

Let’s see how we can implement this.

At first, need to add environment variables to the values.yaml.

Create the directories tree:

$ mkdir -p projects/newapp/env/dev
Enter fullscreen mode Exit fullscreen mode

And files inside — values.yaml and secrets.yaml:

$ touch projects/newapp/env/dev/{values.yaml,secrets.yaml}
Enter fullscreen mode Exit fullscreen mode

Then, in the projects/newapp/env/dev/values.yaml create a list called environments with the only one variable for now:

environments: 
 - name: 'DATABASE_HOST'
 value: 'dev.aurora.web.example.com'
Enter fullscreen mode Exit fullscreen mode

To be able to test the template with the helm --debug --dry-run - add all other data like {{ .Values.deployment.replicaCount }}.

The range loop

Documentation — Flow Control.

Good examples — Helm Template range.

So, we have an environment variable DATABASE_HOST with the dev.aurora.web.example.com value which is described in the values.yaml which is set as an element of the list with two key:value pairs:

...
 <LIST_NAME>:
 - <VAR_NAME>: <VAR_VALUE>
   <VAR_NAME>: <VAR_VALUE>
...
Enter fullscreen mode Exit fullscreen mode

Which we can then include in the general template with the following code:

...
 spec:
 containers:
 - name: {{ .Values.appConfig.appName }}-pod
 image: {{ .Values.deployment.image.repository }}/{{ .Values.deployment.image.name }}:{{ .Values.deployment.image.tag }}
 env:
 {{- range .Values.deployment.environments }}
 - name: {{ .name }} 
 value: {{ .value }}
 {{- end }}
...
Enter fullscreen mode Exit fullscreen mode

Here:

  • iterate the environments list with the key:value lines
  • iterate each of the element of the list looking for the VAR_NAME .name - this will be set in the name: {{ .name }}
  • iterate each of the element of the list looking for the VAR_NAME .value - this will be set in the value: {{ .value }}

Check it:

helm upgrade — install eks-dev-1-newapp-backend — namespace eks-dev-1-newapp-backend-ns — create-namespace -f projects/newapp/env/dev/values.yaml — debug — dry-run .
…
spec:
replicas: 2
strategy:
type:
selector:
matchLabels:
application: newapp
template:
metadata:
labels:
application: newapp
version: 0.1.0–1.16.0
environment: dev
managed-by: Helm
spec:
containers:
- name: newapp-pod
image: projectname/projectname:latest
env:
- name: DATABASE_HOST
value: dev.aurora.web.example.com
ports:
- containerPort: 3001
…
Enter fullscreen mode Exit fullscreen mode

By the way — we are seeing our labels here.

And here is our environment variables:

…
env:
- name: DATABASE_HOST
value: dev.aurora.web.example.com
…
Enter fullscreen mode Exit fullscreen mode

The range loop with variables

Another solution is to use the same loop but now using $key and $value variables - in this case, we are not tied to names of the variables in the values.yaml file, instead just iterate over each of them:

...
 {{- range $key, $value := .Values.deployment.environments }}
 env:
 - name: {{ $key }}
 value: {{ $value }}
 {{- end }}
...
Enter fullscreen mode Exit fullscreen mode

Update the values.yaml - now our environments are set as a dictionary containing our variables just as a key:value pairs, here is added another one to make the example more illustrative, the DB_USERNAME one:

...
 environments: 
 DATABASE_HOST: 'dev.aurora.web.example.com'
 DB_USERNAME: 'dbuser'
...
Enter fullscreen mode Exit fullscreen mode

Check it:

$ helm upgrade — install eks-dev-1-newapp-backend — namespace eks-dev-1-newapp-backend-ns — create-namespace -f projects/newapp/env/dev/values.yaml — debug — dry-run .
…
spec:
containers:
- name: newapp-pod
image: projectname/projectname:latest
env:
- name: DATABASE_HOST
value: dev.aurora.web.example.com
- name: DB_USERNAME
value: dbuser
ports:
- containerPort: 3001
…
Enter fullscreen mode Exit fullscreen mode

toYaml

But let’s recall our initial template:

...
 - name: DATABASE_HOST
 value: {{ .Values.backendConfig.db.host }}
 - name: DB_USERNAME
 value: {{ .Values.backendConfig.db.user }}
 - name: DB_PASSWORD
 valueFrom:
 secretKeyRef:
 name: projectname-backend-secrets
 key: backend-db-password
...
Enter fullscreen mode Exit fullscreen mode

We can’t use a simple loop here, so the third solution will be to describe the whole env block in the values.yaml, and then include it in the general template by using the toYaml.

Update the values.yaml and set the block here:

environments:
 - name: DATABASE_HOST
 value: 'dev.aurora.web.example.com'
 - name: DB_USERNAME
 value: 'dbuser'
 - name: DB_PASSWORD
 valueFrom:
 secretKeyRef:
 name: projectname-backend-secrets
 key: backend-db-password
Enter fullscreen mode Exit fullscreen mode

And then include it to the general template to the env block:

...
 containers:
 - name: {{ .Values.appConfig.appName }}-pod
 image: {{ .Values.deployment.image.repository }}/{{ .Values.deployment.image.name }}:{{ .Values.deployment.image.tag }}
 env: 
 {{- toYaml .Values.deployment.environments | nindent 8 }}
...
Enter fullscreen mode Exit fullscreen mode

Whitespaces

Pay your attention that before the toYaml and after the {{ we've set the dash symbol "-":

{{- toYaml .Values.deployment.environments | nindent 8 }}
Enter fullscreen mode Exit fullscreen mode

It is used to remove whitespaces before the block to be included, also keep in mind that new lines also are threaded as whitespaces.

If remove the “-" from here we will get an extra newline in the resulted manifest:

…
env:

- name: DATABASE_HOST
value: dev.aurora.web.example.com
- name: DB_USERNAME
…
Enter fullscreen mode Exit fullscreen mode

See the Controlling Whitespace on the Flow Control and more example on the Directives and Whitespace Handling pages.

And here we are again using the nindent to add newlines after each line from the .Values.deployment.environments.

Helm tpl

And one more solution — use the tpl function.

In contrast to the previous one — this time we can use directives like{{ .Release.Name }} in our values.yaml as the code from it will be treated as a part of the template itself.

Update the values.yaml:

environments: |-
 - name: RELEASE_NAME
 value: {{ .Release.Name }}
 - name: DATABASE_HOST
 value: 'dev.aurora.web.example.com'
 - name: DB_USERNAME
 value: 'dbuser'
 - name: DB_PASSWORD
 valueFrom:
 secretKeyRef:
 name: projectname-backend-secrets
 key: backend-db-password
Enter fullscreen mode Exit fullscreen mode

Pay attention to the “|-" - we are describing the block with variables as a simple string and removing newlines.

Add it to the deployment’s template:

...
 containers:
 - name: {{ .Values.appConfig.appName }}-pod
 image: {{ .Values.deployment.image.repository }}/{{ .Values.deployment.image.name }}:{{ .Values.deployment.image.tag }}
 env: 
 {{- tpl .Values.deployment.environments . | nindent 8 }}
...
Enter fullscreen mode Exit fullscreen mode

Here we are passing content of the .Values.deployment.environments to the tml function to include it to the template.

Check it:

$ helm upgrade — install eks-dev-1-newapp-backend — namespace eks-dev-1-newapp-backend-ns — create-namespace -f projects/newapp/env/dev/values.yaml — debug — dry-run .
…
spec:
containers:
- name: newapp-pod
image: projectname/projectname:latest
env:
- name: RELEASE_NAME
value: eks-dev-1-newapp-backend
- name: DATABASE_HOST
value: ‘dev.aurora.web.example.com’
- name: DB_USERNAME
value: ‘dbuser’
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: projectname-backend-secrets
key: backend-db-password
ports:
- containerPort: 3001
…
Enter fullscreen mode Exit fullscreen mode

The env block from the _helpers.tpl

We already used the file, but this time let’s add a condition to check if we have some parameter set in the values.yaml.

if/else — flow control

In the _helpers.yaml define the env block's template:

{{- define "helpers.environments" -}}
- name: RELEASE_NAME
 value: {{ .Release.Name }}
{{- if .Values.appConfig.db.host }}
- name: DATABASE_HOST
 value: 'dev.aurora.web.example.com'
{{- end }}
{{- if .Values.appConfig.db.user }}
- name: DB_USERNAME
 value: 'dbuser'
{{- end }}
{{- if .Values.appConfig.db.password }}
- name: DB_PASSWORD
 valueFrom: 
 secretKeyRef:
 name: projectname-backend-secrets
 key: backend-db-password
{{- end }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode

Here we are using the if/else condition for each environment variable to check if we have a value specified for this variable and it will be found - then the variable will be added to the generic template.

Using this we will able to expand the environment variables list in the helpers.environments without worrying that some projet's values.yaml will have no value for it and this will break its deployment.

Now, include it to the template:

...
 containers:
 - name: {{ .Values.appConfig.appName }}-pod
 image: {{ .Values.deployment.image.repository }}/{{ .Values.deployment.image.name }}:{{ .Values.deployment.image.tag }}
 env: {{- include "helpers.environments" . | nindent 8 }}
...
Enter fullscreen mode Exit fullscreen mode

Check it:

$ helm upgrade — install eks-dev-1-newapp-backend — namespace eks-dev-1-newapp-backend-ns — create-namespace -f projects/newapp/env/dev/values.yaml — debug — dry-run .
…
spec:
containers:
- name: newapp-pod
image: projectname/projectname:latest
env:
- name: RELEASE_NAME
value: eks-dev-1-newapp-backend
- name: DATABASE_HOST
value: ‘dev.aurora.web.example.com’
- name: DB_USERNAME
value: ‘dbuser’
ports:
- containerPort: 3001
…
Enter fullscreen mode Exit fullscreen mode

And pay attention here — the DB_PASSWORD was not set, because we have the {{ .Release.Name }} by default, we have the .Values.appConfig.db.host and .Values.appConfig.db.user in the values.yaml, but the .Values.appConfig.db.password is set with the Helm Secrets and is stored in the secrets.yaml file which we are not using now at all.

So, the if/else condition playing here, and Helm didn't create the DB_PASSWORD variable.

The env block from an external file

And the latest solution which I was able to find is to include the env from a file in a project's directory.

Let’s add a new option to the values.yaml to disable include from the _helpers.tpl, call it customEnvs, and add another one to specify a path to a file with environment variables - customEnvsFile:

...
deployment:

 customEnvs: true
 customEnvsFile: 'projects/newapp/templates/environments.yaml'
....
Enter fullscreen mode Exit fullscreen mode

Now, into the deployment template add a condition to check the customEnvs is will be set to false - then via {{ else }} we will include our previous template from the _helpers.yaml:

...
 env:
 {{- if .Values.deployment.customEnvs }}
 {{- .Files.Get .Values.deployment.customEnvsFile | nindent 8 }}
 {{- else }}
 {{- include "helpers.environments" . | nindent 8 }}
 {{ end -}}
 ports:
...
Enter fullscreen mode Exit fullscreen mode

And this will work but only because I’ve removed the .Release.Name from the projects/newapp/templates/environments.yaml:

- name: DATABASE_HOST
 value: 'dev.aurora.web.example.com'
- name: DB_USERNAME
 value: 'dbuser'
- name: DB_PASSWORD
 valueFrom:
 secretKeyRef:
 name: projectname-backend-secrets
 key: backend-db-password
Enter fullscreen mode Exit fullscreen mode

This can be solved with already familiar tpl function.

Go back to the projects/newapp/templates/environments.yaml - set the .Release.Name back:

- name: RELEASE_NAME
 value: {{ .Release.Name }}
- name: DATABASE_HOST
 value: 'dev.aurora.web.example.com'
...
Enter fullscreen mode Exit fullscreen mode

In the deployment template, add the tpl, and .Files.Get and its argument wrap into parentheses:

...
 {{- if .Values.deployment.customEnvs }}
 {{- tpl ( .Files.Get .Values.deployment.customEnvsFile ) . | nindent 8 }}
 {{- else }} 
 {{- include "helpers.environments" . | nindent 8 }}
 {{ end -}}
...
Enter fullscreen mode Exit fullscreen mode

Check it:

$ helm upgrade — install eks-dev-1-newapp-backend — namespace eks-dev-1-newapp-backend-ns — create-namespace -f projects/newapp/env/dev/values.yaml — debug — dry-run .
…
spec:
containers:
- name: newapp-pod
image: projectname/projectname:latest
env:
- name: RELEASE_NAME
value: eks-dev-1-newapp-backend
- name: DATABASE_HOST
value: ‘dev.aurora.web.example.com’
- name: DB_USERNAME
value: ‘dbuser’
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: projectname-backend-secrets
key: backend-db-password
ports:
- containerPort: 3001
…
Enter fullscreen mode Exit fullscreen mode

With volumes, volumesMounts we can use the same approach.

In general, the solution with the _helpers.yaml looks usable - will test it on the new project and will see how it's going.

The whole Deployment template now:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: {{ .Values.appConfig.appName }}-deployment
 labels: {{- include "helpers.labels" . | nindent 4 }}
 annotations:
 {{- if .Values.deployment.deploymentAnnotations }}
 {{- toYaml .Values.deployment.deploymentAnnotations | nindent 6 }}
 {{- end }}
 reloader.stakater.com/auto: "true"
spec:
 replicas: {{ .Values.deployment.replicaCount }}
 strategy:
 type: {{ .Values.deployment.delpoyStrategy }}
 selector:
 matchLabels:
 {{- include "helpers.selectorLabels" . | nindent 6 }}
 template:
 metadata:
 labels: {{- include "helpers.labels" . | nindent 8 }}
 spec:
 containers:
 - name: {{ .Values.appConfig.appName }}-pod
 image: {{ .Values.deployment.image.repository }}/{{ .Values.deployment.image.name }}:{{ .Values.deployment.image.tag }}
 env: 
 {{- if .Values.deployment.customEnvs }}
 {{- tpl ( .Files.Get .Values.deployment.customEnvsFile ) . | nindent 8 }}
 {{- else }}
 {{- include "helpers.environments" . | nindent 8 }}
 {{ end -}}
 ports:
 - containerPort: {{ .Values.appConfig.port }}
 {{- with .Values.deployment.livenessProbe }}
 livenessProbe:
 httpGet:
 path: {{ .path }}
 port: {{ .port }}
 initialDelaySeconds: {{ .initDelay }}
 {{- end }}
 {{- with .Values.deployment.readinessProbe }}
 readinessProbe:
 httpGet:
 path: {{ .path }}
 port: {{ .port }}
 initialDelaySeconds: {{ .initDelay }}
 {{- end }}
 resources:
 requests:
 cpu: {{ .Values.deployment.resources.requests.cpu | quote }}
 imagePullSecrets:
 - name: bttrm-docker-secret
Enter fullscreen mode Exit fullscreen mode

Excluding a template from the chart

And the final thought — how can we exclude a template file from the chart at all.

For example, let’s check the cronjobs.yaml file:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
 name: {{ .Values.appConfig.appName }}-cron
 labels: {{- include "helpers.labels" . | nindent 4 }}
spec: 
 schedule: {{ .Values.cronjobs.schedule | quote }}
 startingDeadlineSeconds: {{ .Values.cronjobs.startingDeadline }}
 concurrencyPolicy: {{ .Values.cronjobs.concurrencyPolicy }}
 jobTemplate:
 spec:
 template:
 metadata:
 labels: {{- include "helpers.labels" . | nindent 12 }}
 spec:
 containers:
 - name: {{ .Values.appConfig.appName }}-cron
 image: {{ .Values.deployment.image.repository }}/{{ .Values.deployment.image.name }}:{{ .Values.deployment.image.tag }}
 env:
 {{- if .Values.deployment.customEnvs }}
 {{- tpl ( .Files.Get .Values.deployment.customEnvsFile ) . | nindent 14 }}
 {{- else }}
 {{- include "helpers.environments" . | nindent 14 }}
 {{ end -}}
 command: ["npm"]
 args: ["run", "cron:app"]
 restartPolicy: Never
 imagePullSecrets:
 - name: bttrm-docker-secret
Enter fullscreen mode Exit fullscreen mode

By the way — here are our labels and the env block used from the _helpers.yaml.

But not every project have cronjobs.

So, we can add a new parameter to the values.yaml - cronjobs.enabled:

...
################
### Cronjobs ###
################ 

cronjobs: 

 enabled: false
 schedule: '*/15 * * * *'
 startingDeadline: 10
 concurrencyPolicy: 'Forbid'
...
Enter fullscreen mode Exit fullscreen mode

And then wrap the whole template’s content in one if/else condition check:

{{- if .Values.cronjobs.enabled }}
apiVersion: batch/v1beta1
kind: CronJob
metadata:
 name: {{ .Values.appConfig.appName }}-cron
 labels: {{- include "helpers.labels" . | nindent 4 }}
...
 imagePullSecrets:
 - name: bttrm-docker-secret
{{- end }}
Enter fullscreen mode Exit fullscreen mode

Check it:

$ helm upgrade — install eks-dev-1-newapp-backend — namespace eks-dev-1-newapp-backend-ns — create-namespace -f projects/newapp/env/dev/values.yaml — debug — dry-run .
…
 — -
Source: project-backend/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: newapp-deployment
…
imagePullSecrets:
- name: bttrm-docker-secret
Enter fullscreen mode Exit fullscreen mode

No cronjobs were added.

Add --set cronjobs.enabled=true - and they will be added to the release:

$ helm upgrade — install eks-dev-1-newapp-backend — namespace eks-dev-1-newapp-backend-ns — create-namespace -f projects/newapp/env/dev/values.yaml — debug — dry-run . — set cronjobs.enabled=true
…
imagePullSecrets:
- name: bttrm-docker-secret
 — -
Source: project-backend/templates/cronjobs.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: newapp-cron
…
imagePullSecrets:
Enter fullscreen mode Exit fullscreen mode
  • name: bttrm-docker-secret

“That’s all folks!” ©

Useful links

Originally published at RTFM: Linux, DevOps и системное администрирование.


Discussion

pic
Editor guide