Is it possible to abstract boilerplate YAML from helm subchart templates?

11/1/2019

Is there a way to move boilerplate YAML from subchart templates to the parent _helpers.tpl or values.yaml ?

helm project

    MyHelmApp
    ├── Chart.yaml
    ├── values.yaml
    ├── templates
    │   ├── _helpers.tpl
    │   ├── configmap.yaml
    │   └── app-ingress-rules.yaml
    └── charts
        ├── app1
        │   ├── Chart.yaml
        │   ├── templates
        │   │   ├── _helpers.tpl
        │   │   ├── deployment.yaml
        │   │   └── service.yaml
        │   └── values.yaml
        ├── app2
        │   ├── Chart.yaml
        │   ├── templates
        │   │   ├── _helpers.tpl
        │   │   ├── deployment.yaml
        │   │   └── service.yaml
        │   └── values.yaml
        └── app3
            ├── Chart.yaml
            ├── templates
            │   ├── _helpers.tpl
            │   ├── deployment.yaml
            │   └── service.yaml
            └── values.yaml

To elaborate: I have 3 app subcharts and they each have boilerplate YAML in their templates. The values in those templates are defined in the parent chart's values.yaml, and the variable paths are identical. For instance:

app1, app2, and app3 deployment.yaml's all contain...

          livenessProbe:
            httpGet:
              path: {{ .Values.health.livenessProbe.path }}
              port: {{ .Values.health.livenessProbe.port }}
            initialDelaySeconds: {{ .Values.health.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.health.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.health.livenessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.health.livenessProbe.successThreshold }}
            failureThreshold: {{ .Values.health.readinessProbe.failureThreshold }}

          readinessProbe:
            tcpSocket:
              port: {{ .Values.health.readinessProbe.port }}
            initialDelaySeconds: {{ .Values.health.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.health.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.health.readinessProbe.timeoutSeconds }}
            successThreshold: {{ .Values.health.livenessProbe.successThreshold }}
            failureThreshold: {{ .Values.health.readinessProbe.failureThreshold }}

values.yaml (in the parent chart)

app1:
  health:
    livenessProbe:
      path: /system_health
      port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20
      timeoutSeconds: 5
      successThreshold: 1
      failureThreshold: 3
    readinessProbe:
      port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
      timeoutSeconds: 5
      successThreshold: 1
      failureThreshold: 3

app2:
  health:
    livenessProbe:
      path: /system_health
      port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20
      timeoutSeconds: 5
      successThreshold: 1
      failureThreshold: 3
    readinessProbe:
      port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
      timeoutSeconds: 5
      successThreshold: 1
      failureThreshold: 3

(etc)

Back to the question: I'd like to grab what's identical in the subchart templates and move it to one centralized place like the parent chart's _helpers.tpl or values.yaml; is this possible? Can you provide an example of how you'd do this?

-- Mario Aguilera
kubernetes
kubernetes-helm
yaml

1 Answer

11/2/2019

Helm renders all of the YAML it's given, from all parent and dependency charts together, in a single execution environment with a shared template namespace. In theory, the app1 chart can depend on a template that's defined in the parent's _helpers.tpl, and in the specific layout you show, it will work.

Because of this environment setup, it's also possible to write a "chart" that doesn't actually produce any YAML of its own, but just contains templates. Helm 3 will include "library chart" as a specific concept. A better layout still would be to have a library with your shared templates, and reference that.

MyHelmApp
\-- charts
  +-- app1
  | +-- Chart.yaml
  | \-- templates/...
  \-- common
    +-- Chart.yaml
    \-- templates
      \-- _helpers.tpl
          (but no *.yaml)

Now MyHelmApp depends on app1, app2, and app3, and each of those depend on common. This would let you install any of those independently from their siblings.

Helm doesn't have a way to "push" fragments of YAML into objects in other locations (Kustomize, part of relatively recent Kubernetes, can does this). Each object has to declare for itself any potential customization that could be allowed. So in each chart you'd have to declare

spec:
{{ include "common.probes" . | indent 2 }}

The common chart just defines templates, and when they're invoked they'd use the version of .Values that's localized to the subchart.

{{- define "common.probes" -}}
livenessProbe:
  httpGet:
    path: {{ .Values.health.livenessProbe.path }}
    et: cetera
{{ end -}}
-- David Maze
Source: StackOverflow