Helm Values Inheritance One-Liner
A Helm template one-liner that adds an entire new feature. Sub charts can now inherit default values from their parent chart. This allow for per environment (develop/stage/production) default values, only overridden in the occasional service.
The One-Liner
It goes like this:
{{- $merged := merge (deepCopy .Values) (deepCopy (default (dict) .Values.global)) -}}
It can be easier understood by looking at the naive implementation:
{{- $merged := merge .Values .Values.global -}}
We create the template variable $merged
, that is just like .Values
, but where .Values.global
provide the defaults.
The purpose of deepCopy
and default
is to dodge null pointer exceptions and avoid modifying the .Values
them selves.
The deepCopy
template function was added in Helm 2.16
, so make sure you have upgraded to at least that (or ideally Helm 3).
Understanding .Values.global
To understand this one-liner it's important to understand .Values.global
. This global
value holds special meaning to Helm. It can be accessed from any chart or subchart.
So where it's usually the case that parent charts override values in sub charts, the .Values.global
goes in the other direction, and allows a subchart to read values from the parent.
Read more in the Helm docs: https://helm.sh/docs/chart_template_guide/subcharts_and_globals/#global-chart-values
Usage Example with Environment Parent Chart
Say you have one helm chart per environment. For example your company has develop
, stage
, and production
environments and you are using one parent helm chart for each.
Each such parent environment Helm chart has numerous dependency sub charts. Each sub chart might be a microservice.
It's probably the case that the values.yaml
in these sub charts contains some repetition. Your PostgreSQL database connection information for example, or the URL for Elastic APM. Such values that are similar in most sub charts are also bound to be configured the same way in the whole environment.
For example the PostgreSQL hostname might be the very same for all micro services in develop
. They are also the same in production
, just another hostname there.
It's then quite convenient to just specify this hostname once in the .global
section of the parent chart.
Parent Chart values.yaml:
global:
postgres:
hostname: the-development-hostname.com
port: 5432
...
Sub Chart templates/service.yaml:
{{- $merged := merge (deepCopy .Values) (deepCopy (default (dict) .Values.global)) -}}
apiVersion: apps/v1
kind: Deployment
...
env:
- name: DB_HOST
value: {{$merged.postgres.host}}
- name: DB_PORT
value: {{$merged.postgres.port}}
So where you would normally have written {{.Values.postgres.host}}
you now just replace .Values
with $merged
for the default value inheritance to kick in like {{$merged.postgres.host}}
.
Parent Chart Impact
Leveraging this technique you can simplify a parent chart values.yaml
that looks like this:
service1:
image:
tag: 1.2.3
postgres:
hostname: the-development-hostname.com
port: 5432
service2:
image:
tag: 5.0.0
postgres:
hostname: the-development-hostname.com
port: 5432
service3:
image:
tag: 3.3.0
postgres:
hostname: the-development-hostname.com
port: 5432
To just this:
global:
postgres:
hostname: the-development-hostname.com
port: 5432
service1:
image:
tag: 1.2.3
service2:
image:
tag: 5.0.0
service3:
image:
tag: 3.3.0
Placement
So I usually place this line at the very top of every templates/service.yaml
sub chart file:
{{- $merged := merge (deepCopy .Values) (deepCopy (default (dict) .Values.global)) -}}
That way the $merged
variable is available in the whole rest of the file.
Automatic Version
The one-liner described so far is opt in. The .Values
must be explicitly replaced with $merged
.
By removing the variable assignment and deepCopy
on the left side we achieve a different effect:
{{- merge .Values (deepCopy (default (dict) .Values.global)) -}}
The .Values
are now mutated in place instead and the inheritance takes effect everywhere automatically.