I am beginner in helm chart templating and I'm looking for best practise.
I would like to create a helm chart template enough generics to use same chart for all team project (backend, front-end,..)
On order to make it generics I can let developer to specify list of many cases in values.yml (volumes for deployment, network policy ingress egress etc...).
And i could keep the kubernetes template deployment, service etc.. Enough generics to never mention any specific keys.
So the developer could only modify values yml for add values for their application behavior.
The disadvantage is that the kubernetes generics template will not contain any logic about the application , and the generic template will be hard to maintain (because it will handle every possible case).
The advantage is that the developer doesn't need to understand helm because they will not modify the kubernetes template.
So you have any experience about that?
You can use _*.tpl files to define generic templates, They are located in ./templates/_*.tpl (. being the directory with global Chart.yaml and values.yaml).
Also by defaul in helm global values override local values. Solution to this can be found here - https://github.com/helm/helm/issues/5676
By using these 2 techniques in conjunction you can make generic templates and only use values.yaml to render what you want to render.
For example:
values.yaml:
global:
defaults:
switches:
volumesEnabled: false
ingressEnabled: false
ingress:
host: "generic-host.com"
volumes:
volumeName: "generic-volume-name"
subchart1:
defaultOverrides:
switches:
volumesEnabled: true
volumes:
volumeName: "not-so-generic-name"
subchart2:
defaultOverrides:
switches:
volumesEnabled: true
ingressEnabled: true
Then templates (java is just for grouping templates in one category, you can try to guess in which language my backend microservices are written :) )
./templates/java/_deployment.tpl:
{{- define "templates.java.deployment" }}
{{- $properties := merge .Values.defaultOverrides $.Values.global.defaults -}}
{{*/ generic deployment structure */}}
{{- if $properties.switches.volumesEnabled -}}
volume: {{ $properties.volumes.volumeName }}
{{- end }}
{{*/ generic deployment structure */}}
{{- end }}
./templates/java/_ingress.tpl:
{{- define "templates.java.ingress" }}
{{- $properties := merge .Values.defaultOverrides $.Values.global.defaults -}}
{{- if $properties.switches.ingressEnabled -}}
host: {{ $properties.ingress.host }}
{{*/ generic ingress structure */}}
{{- end }}
{{- end }}
And then subchart templates
./charts/subchart1/templates/deployment.yaml:
{{ include "templates.java.deployment" . }}
./charts/subchart1/templates/ingress.yaml:
{{ include "templates.java.ingress" . }}
subchart2 has exactly the same includes.
In the end we will have:
subchart1:
has deployment
volumeName is overriden from local values with "not-so-generic-name"
ingress is not rendered at all
subchart2:
has deployment
volumeName is default from global values
ingress host is default from global values
But I would say that it's a bad practice to generlize to much, because it will make your templates overly complex. In my case I found 2 distinct groups which have nearly identical manifests within them (basically frontend and backend) and made a set of _*.tpl files for each of them and settings default values for each group respectivelly in global values.yaml.
Given values.yaml:
outer:
inner:
someKey: false
What does the following syntax in a helm template file mean?
{{- if index (default (dict) .Values.outer.inner) "someKey" }}
{{- .... }}
{{- end }}
From context, I can infer what I think it's supposed to do: check if the specified key exists at the specified location.
But where does the default (dict)... syntax come from? Sprig? I can't find it documented in any of these places:
https://v2.helm.sh/docs/chart_template_guide/#template-functions-and-pipelines
https://golang.org/pkg/text/template/#hdr-Functions
http://masterminds.github.io/sprig/
http://masterminds.github.io/sprig/defaults.html
And what does it actually mean?
This particular code avoids a failure if the values outer: {...} doesn't contain an inner key within it.
dict is a Sprig function that creates a new dictionary. It can be called with any (even) number of parameters; (dict) with no parameters creates an empty dictionary.
default x y is the same as y | default x and calls the Sprig default function.
The important thing this is trying to protect against is if .Values.outer doesn't have an inner key within it. If that happened, .Values.outer.inner would be nil, and .Values.outer.inner.someKey would produce an error; the default dict block replaces nil with an empty dictionary, which can be used with index and similar template code.
I'll often write similar template blocks one layer at a time:
{{- $outer := .Values.outer | default dict -}}
{{- $inner := $outer.inner | default dict -}}
{{- if $inner.someKey }}
...
{{- end }}
I'm currently writing a small Ansible playbook whose job is to put in an additional domain in the search list in /etc/resolv.conf.
The second domain to add to the search list must contain part of the hostname of the target hosts. I'm getting the hostname of each of the target hosts during playbook execution using the magic variable {{ inventory_hostname }}.
I then need to extract characters 4 - 6 from the {{ inventory_hostname }} (say 'xyz') such that the second domain to add to the search list is xyz.foo.bar. In bash, this would be obtained with something like:
SERVER=$('hostname':3:3)
env=${SERVER:3:3}
... and the variable 'env' would be equal to 'xyz'.
The playbook works as long as 'xyz' is manually defined.
I am aware that Ansible has regular expression filters which can help with something like this, however I could not figure out a regular expression which does what I need.
For completeness sake, I have tried something like this in ansible:
{{ inventory_hostname|3:3 }}
Any help would be greatly appreciated.
It's almost the same, you can use "{{ inventory_hostname[3:6] }}" to select characters 3 to 6.
For example this task
- debug:
msg: "{{ inventory_hostname[3:6] }}"
Will output
ok: [localhost] => {
"msg": "alh"
}
I am writing a Deployment Manager script which creates a Cloud Function and sets some environment variables.
Everything works well apart from the fact that one of my properties/variables is not recognized by the Deployment Manager correctly. I keep on getting an error.
I have a property is-local that I supply from CMD line.
Its value needs to be false/true or I can also live with yes/no.
In the schema file if I specify the property as boolean and supply the value as false/true then the deployment starts and only the Cloud Function component fails with an error. I have specified the error as Error#1 below.
if I specify the property as string and supply the value as false/true then the deployment starts but fails immediately with an error. I have specified the error as Error#2 below.
main.jinja
{% set PROJECT_NAME = env['project'] %}
{% set CODE_BUCKET = properties['code-bucket'] %}
{% set IS_LOCAL = properties['is-local'] %}
resources:
- name: create-cf
type: create_cloud_function.jinja
properties:
name: test-cf
project: {{ PROJECT_NAME }}
region: europe-west1
bucket: {{ CODE_BUCKET }}
runtime: nodejs10
entryPoint: test
topic: test
environmentVariables: { 'CODE_BUCKET': {{ CODE_BUCKET }}, 'IS_LOCAL': {{IS_LOCAL}} }
main.jinja.schema
imports:
- path: create_cloud_function.jinja
required:
- code-bucket
- is-local
properties:
code-bucket:
type: string
description: Name of the code bucket to host the code for Cloud Function.
is-local:
type: boolean
description: Will Cloud Function run locally or in cloud.
create_cloud_function.jinja
{% set codeFolder = properties['name'] %}
{% set environmentVariables = properties['environmentVariables'] %}
resources:
#- type: cloudfunctions.v1.function
- type: gcp-types/cloudfunctions-v1:projects.locations.functions
name: {{ properties['name'] }}
properties:
parent: projects/{{ properties['project'] }}/locations/{{ properties['region'] }}
location: {{ properties['region'] }}
function: {{ properties['name'] }}
sourceArchiveUrl: gs://$(ref.{{ properties['bucket'] }}.name)/{{ codeFolder }}.zip
entryPoint: {{ properties['entryPoint'] }}
runtime: {{properties['runtime']}}
eventTrigger:
resource: $(ref.{{ properties['topic'] }}.name)
eventType: providers/cloud.pubsub/eventTypes/topic.publish
environmentVariables:
{% for key, value in environmentVariables.items() %}
{{ key }} : {{ value }}
{% endfor %}
Deployment Manager CMD
gcloud deployment-manager deployments create setup --template main.jinja --properties code-bucket:something-random-test-code-bucket,is-local:false
Error#1: - when the property type is boolean in schema file
{"ResourceType":"gcp-types/cloudfunctions-v1:projects.locations.functions","ResourceErrorCode":"400","ResourceErrorMessage":{"code":400,"message":"Invalid value at 'function.environment_variables[1].value' (TYPE_STRING), false","status":"INVALID_ARGUMENT","details":[{"#type":"type.googleapis.com/google.rpc.BadRequest","fieldViolations":[{"field":"function.environment_variables[1].value","description":"Invalid value at 'function.environment_variables[1].value' (TYPE_STRING), false"}]}],"statusMessage":"Bad Request","requestPath":"https://cloudfunctions.googleapis.com/v1/projects/someproject/locations/europe-west1/functions","httpMethod":"POST"}}
Error#2: - when the property type is string in schema file
errors:
- code: MANIFEST_EXPANSION_USER_ERROR
location: /deployments/setup/manifests/manifest-1571821997285
message: |-
Manifest expansion encountered the following errors: Invalid properties for 'main.jinja':
True is not of type 'string' at ['is-local']
Resource: main-jinja Resource: config
Any idea whats the issue here...
I'm unfamiliar with jinja but from my understanding, environment variables cannot be anything else but strings.
Said this, reading Error#1 I'd conclude that, effectively, the var type has to be string.
Then, at the second error we can clearly see that you are trying to put a boolean into a string.
So yeah, you have to play with true / false as strings.
You can define set the value as a string within the jinja file itself. See this post for some details and this page that provides different methods you can use.
In your case, you can edit the create_cloud_function.jinja file and change:
environmentVariables:
{% for key, value in environmentVariables.items() %}
{{ key }} : {{ value }}
to:
environmentVariables:
{% for key, value in environmentVariables.items() %}
{{ key }} : {{ value|string }}
Once the manifest is fully expanded, the value should be considered a string for the purpose of the API call to the Cloud Functions API
Eventually what I had 2 do was pass IS_LOCAL: '''false'''from the command line and {{ key }} : {{ value }} in my jinja file.
According to this documentation about Using environment variables in Jinja, you should use the following syntax to add an environment var to your templates:
{{ env["deployment"] }} # Jinja
And they show the following example:
- type: compute.v1.instance
name: vm-{{ env["deployment"] }}
properties:
machineType: zones/us-central1-a/machineTypes/f1-micro
serviceAccounts:
- email: {{ env['project_number'] }}-compute#developer.gserviceaccount.com
scopes:
- ...
Given that you are providing the value of is-local from CMD line, and according to this documentation:
Boolean values are case insensitive, so TRUE, true, and True are treated the same.
AND
To specify multiple properties, provide comma-separated key:value pairs. It does not matter in what order you specify the pairs. For example:
`gcloud deployment-manager deployments create my-igm
--template vm_template.jinja
--properties zone:us-central1-a,machineType:n1-standard-1,image:debian-9`
You should use TRUE, true, or True for is-local param.
Im new at helm. Im building a splunk helm chart with numerous conf files. I currently use something like this in a configmap ..
apiVersion: v1
kind: ConfigMap
metadata:
name: splunk-master-configmap
data:
indexes.conf: |
# global settings
# Inheritable by all indexes: no hot/warm bucket can exceed 1 TB.
# Individual indexes can override this setting.
homePath.maxDataSizeMB = 1000000
but I would prefer to have the conf files in a seperate folder e.g. configs/helloworld.conf and have come accross "tpl" but am struggling to understand how to implement it. - can anyone advise best practices. On a side note splunk has orders of presidences >> so there may be many indexes.conf files used in various locations. does anyone have any thoughts on how best to implement this?!??!
Cheers.
If the content of the files is static then you could create a files directory in your chart at the same level as the templates directory (not inside it) and reference them like:
kind: ConfigMap
metadata:
name: splunk-master-configmap
data:
{{ (.Files.Glob "files/indexes.conf").AsConfig | indent 2 }}
{{ (.Files.Glob "files/otherfile.conf").AsConfig | indent 2 }}
# ... and so on
Where this would break down is if you want to be able to reference the values of variables inside the files so that the content is controlled from the values.yaml. If you want to expose each value individually then there's an example in the helm documentation using range. But I think a good fit or your case is what the stable/mysql chart does. It has a ConfigMap that takes values as strings:
{{- if .Values.configurationFiles }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "mysql.fullname" . }}-configuration
data:
{{- range $key, $val := .Values.configurationFiles }}
{{ $key }}: |-
{{ $val | indent 4}}
{{- end }}
{{- end -}}
And the values.yaml allows both the files and their content to be set and overridden by the user of the chart:
# Custom mysql configuration files used to override default mysql settings
configurationFiles:
# mysql.cnf: |-
# [mysqld]
# skip-name-resolve
# ssl-ca=/ssl/ca.pem
# ssl-cert=/ssl/server-cert.pem
# ssl-key=/ssl/server-key.pem
It comments out that content and leaves it to the user of the chart to set but you could have defaults in the values.yaml.
You would only need tpl if you needed further flexibility. The stable/keycloak chart lets the user of the chart create their own configmap and point it into the keycloak deployment via tpl. But I think your case is probably closest to the mysql one.
Edit: the tpl function can also be used to take the content of files loaded with Files.Get and effectively make that content part of the template - see How do I load multiple templated config files into a helm chart? if you're interested in this