Helm: reusable чарт — named templates, и общий чарт для нескольких приложений

Автор: | 10/18/2020
 

Проект активно развивается, Kubernetes прижился, и всё больше наших сайтов запускается в нём.

И со временем возникла вполне ожидаемая проблема, которая уже озвучивалась в самом начале нашего «путешествия» в Helm: пошаговое создание чарта и деплоймента из Jenkins — как быть с манифестами Kubernetes и шаблонами Helm для нескольких приложений?

Особенно остро она встала сейчас, когда у нас из одного и того же репозитория один и тот же код деплоится в три независимых бекенда трёх независимых приложений.

Сначала было одно, потом появилось второе, сейчас готовится к запуску третье.

В результате чарты сейчас выглядят так:

tree -d k8s/
k8s/
├── app1-chart
│   ├── charts
│   ├── env
│   │   ├── dev
│   │   ├── dev-2
│   │   └── prod
│   └── templates
└── app2-chart
├── charts
├── env
│   ├── dev
│   ├── prod
│   └── stage
└── templates

И теперь копировать сюда тот же самый чарт третий раз?

Ну совсем не хочется.

А потому — посмотрим внимательнее на возможности шаблонизатора Helm-а, и попробуем сделать один чарт для трёх похожих приложений.

Со временем его можно будет расширить, и включить сюда все остальные.

Deployment — структура файла

Что бы продумать структуру общего шаблона — посмотрим, какие блоки общие, а какие будут различаться, и как мы их можем максимально гибко подключать.

Будем рассматривать только файл deployment.yaml — остальные шаблоны потом сделаем по его подобию.

Итак:

  • файл deployment.yaml:
    • metadata:
      • name: общий, значение будем брать из файла values.yaml конкретного проекта
      • annotations: общий, тут у нас только reloader.stakater.com/auto, и у всех он будет один и всегда true (хотя возможны варианты — см. Kubernetes: ConfigMap и Secrets — auto-reload данных в подах)
      • labels: общий, и будет использоваться в нескольких местах, например в деплойменте ниже — spec.template.metadata.labels, и в файле с Kubernetes Cronjobs — вынесем лейблы в _helpers.yaml
    • spec:
      • replicas: общий, значение будем брать из файла values.yaml конкретного проекта
      • strategy:
        • type: общий, значение будем брать из файла values.yaml конкретного проекта
      • selector:
        • matchLabels: общий, и будет использоваться в нескольких местах, например в Cronjobs и Services — вынесем в _helpers.yaml
      • template:
        • metadata:
          • labels: берём из _helpers.yaml
      • spec:
        • containers:
          • name: общий, значение будем брать из файла values.yaml конкретного проекта
          • image: общий, значение будем брать из файла values.yaml конкретного проекта
          • env: вот тут самое интересное:
            1. можем вынести целым блоком в values.yaml каждого проекта
              1. плюсы: не надо будет отдельно описывать сами значения — можем их указать прямо тут, в поле value самих переменных
              2. минусы:
                1. практически у всех одинаковые переменные — дублируем код между проектами
                2. раздутый файл values.yaml, т.к. переменных много
                3. не все переменные идут в виде КЛЮЧ:ЗНАЧЕНИЕ — некоторые будут брать значение через valueFrom.secretKeyRef.<KUBE_SECRET_NAME>, значит значение в values.yaml задать не выйдет
            2. можем включить в общий _helpers.yaml, и будет общий набор переменных, каждая заключена в свой if — если в values.yaml значение найдено, то переменная создаётся в общем шаблоне
            3. и на случай, если у проекта совершенно отдельный набор переменных — то в общем шаблоне перед блоком env можно добавить условие типа if {{ .Values.chartSettings.customEnvs == true }} — будем пропускать включение переменных из _helpers.yaml или values.yaml, и подключать файл шаблона через tpl .File.Get project1/envs.yaml
          • volumeMounts: тоже может различаться, посмотрим потом
          • ports:
          • livenessProbe, readinessProbe: в целом тоже будет общий, с httpGet.path и httpGet.port — можно вынести в _helpers.yaml, но добавить тоже условие customProbes, и при необходимости — подключать через .File.Get
          • resources:
          • volumes: — тоже может различаться, посмотрим потом
          • imagePullSecrets: одинаковый у всех
  • hpa.yaml — описываем HPA
  • network.yaml — Service, Ingress
  • secrets.yamlKubernetes Secrets
  • rbac.yaml — подключение пользовательских групп к создаваемым неймспейсам
  • cronjobs.yaml — Kubernetes Cronjobs
  • _helpers.tpl — и наш «помошник» — сюда ключим Helm Named Templates

Собственно, основная идея такая:

  • общий чарт с шаблонами, которые лежат в templates
    • в templates храним файлы deployment.yaml, hpa.yaml, _helpers.tpl, etc
  • рядом с templates создадим каталог projects
    • и внутри него — каталоги по имени проектов
      • project1
      • project2
      • project3
        • внутри каждого создадим каталог env
          • внутри которого будет dev, stage, prod
            • внутри которых будут отдельные values.yaml и secrets.yaml

Helm Named templates

Отличный пост на тему именованных шаблонов — How To Reduce Helm Chart Boilerplate With Named Templates (больше ссылок — в конце поста).

Официальная документация — тут>>>.

Первым в нашем шаблоне нам встретится блок labels, который мы хотим вынести в _helpers.yaml, и из него подключать в деплоймент и другие шаблоны.

Идея именованных шаблонов в том, что мы один раз пишем какой-то блок, который потом можем включать везде в нашем чарте. Плюс — они позволяют убрать из основного шаблона лишний код, что бы сделать его более красивым и лаконичным.

Файлы именованных шаблонов начинаются с _, и заканчиваются расширением .tpl.

Самый известный пример — _helpers.tpl, который и будем использовать.

Описание каждого шаблона в файле _helpers.tpl начинается с define, заканчивается end.

Имя именованного шаблона как правило включает в себя имя чарта и блока, который этот шаблон добавляет, но никто не мешает использовать любое другое, например в данном случае общее имя у них будет helpers.

Из неудобства — тут в именах нельзя явно использовать значения типа .Chart.Name — но можно создать переменную. Посмотрим — будем ли это делать, пока просто захардкодим имя helpers.

Общие labels

Какие вообще полезные лейблы можно добавить, которые были бы общими?

  • environmentDev, Stage, Prod — будет подставляться из values.yaml
  • appversion — задаётся при билде в Jenkins через --set

Дальше моя фантазия иссякла, а потому спросим Google — «kubernetes recommended labels«, и находим первой же ссылкой Recommended Labels.

Плюс, см. Labels and Annotations в документации самого Helm.

Кроме того, можно посмотреть чарты каких-то готовых приложений, например — sonarqube/templates/deployment.yaml.

Значит, нам надо добавить пока четыре наших кастомных лейблы:

  • application: {{ .Values.appConfig.appName }}, будем задавать в values.yaml каждого приложения
  • version: {{ .Chart.Version }}-{{ .Chart.AppVersion }}, задаются из Jenkins
  • environment: {{ .Values.appConfig.appEnv }},будем задавать в values.yaml каждого приложения
  • managed-by: {{ .Release.Service }}

А если захотим добавить ещё — можно будет это сделать в одном месте, а не обновлять все файлы, в которых используются labels.

define

Возвращаемся к _helpers.tpl, и описываем наши лейблы:

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

include

Далее, с помощью include — указываем, куда именно мы хотим включить эти лейблы в нашем шаблоне деплоймента:

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

indent vs nindent

  • indent — просто задаёт кол-во отступов
  • nindent — начинает новую строку

Т.е. вместо того, что бы писать:

...
  labels: 
    {{- include "helpers.labels" . | indent 4 }}
...

Мы можем сделать всё в одну строку и обойтись без пробелов в самом шаблоне:

...
labels: {{- include "helpers.labels" . | nindent 4 }}
...

Кроме того, играет роль при вставке блоков YAML в общий шаблон — увидим ниже.

Вернёмся к именованным шаблонам — аналогично поступаем со spec.selector.matchLabels — вынесем его в отдельный шаблон, что бы потом переиспользовать в, например, Services.

В _helpers.tpl создаём:

...
{{- define "helpers.selectorLabels" -}}
application: {{ .Values.appConfig.appName }}
{{- end }}

Пока делаем выборку только по приложению, потом можно будет для каждого отдельного приложения изменить селекторы.

Добавляем в деплоймент:

...
  selector:
    matchLabels:
      {{- include "helpers.selectorLabels" . | nindent 6 }}
  template:
...

Шаблоны в шаблоне

Ну и приступим к самому интересному — вставке блоков шаблонов в шаблон.

Итак, у нас в текущем шаблоне есть блок env:

...
        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
...

Который мы хотим вынести отдельный блок в другом файле, и вставлять оттуда в общий шаблон, который пишем сейчас.

Блок env из values.yaml проекта

Посмотрим, какие варианты у нас есть.

Сначала — набросаем сами переменные в values.yaml.

Создаём дерево каталогов:

mkdir -p projects/newapp/env/dev

И внутри него — файлы values.yaml и secrets.yaml:

touch projects/newapp/env/dev/{values.yaml,secrets.yaml}

Далее, в projects/newapp/env/dev/values.yaml создаём список environments и в него добавим пока одну переменную:

environments:    
  - name: 'DATABASE_HOST'
    value: 'dev.aurora.web.example.com'

Что бы иметь возможно проверить работу шаблона с helm --debug --dry-run — добавляем все остальные значения типа {{ .Values.deployment.replicaCount }}.

Цикл range

Документация — Flow Control.

Хорошие примеры — Helm Template range.

Итак, у нас есть переменная для подов DATABASE_HOST со значением dev.aurora.web.example.com, которая описана в values.yaml в виде элемента списка с двумя парами ключ:значение:

...
  <LIST_NAME>:
    - <VAR_NAME>: <VAR_VALUE>
      <VAR_NAME>: <VAR_VALUE>
...

Которые мы можем вставить в шаблон следующим образом:

...
    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 }}
...

тут:

  • перебираем список environments, разделённый символом — и содержащий строки с ключ:значение
  • перебираем элементы списка, находим VAR_NAME .name — подставляем в name: {{ .name }}
  • перебираем элементы списка, находим VAR_NAME .value — подставляем в value: {{ .value }}

Проверям:

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
...

Кстати — уже видим и наши лейблы.

А вот и наши переменные:

...
env:
- name: DATABASE_HOST
value: dev.aurora.web.example.com
...

Цикл range с переменными

Другой вариант — с использованием переменных, например $key и $value — в таком случае мы не привязываемся к конкретным именам переменных в самом файле values.yaml, а просто переберём их все:

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

Обновляем values.yaml — теперь environments описываем как словарь, и задаём наши переменные просто как ключ:значение, для наглядности — добавим ещё одну переменную, DB_USERNAME:

...
  environments:  
    DATABASE_HOST: 'dev.aurora.web.example.com'
    DB_USERNAME: 'dbuser'
...

Проверяем:

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
...

toYaml

Но вспомним наши переменные в нынешнем файле деплоймента:

...
        - 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
...

Простым циклом тут пройтись уже не получится, а потому третий вариант — описывать в values.yaml весь блок, а потом вставлять его в шаблон, используя toYaml .

Для этого в самом values.yaml описываем весь блок:

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

А затем вставляем в env деплоймента:

...
      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 }}
...

Пробелы

Обратите внимание, что перед toYaml после {{ имеется символ тире «-«:

{{- toYaml .Values.deployment.environments | nindent 8 }}

Который используется для удаления символов пробела перед вставляемым блоком, при этом новая строка также считается пробелом.

Если его убрать — то в результате мы получим лишнюю новую строку:

...
env:
- name: DATABASE_HOST
value: dev.aurora.web.example.com
- name: DB_USERNAME
...

См. Controlling Whitespace во Flow Control и примеры на странице Directives and Whitespace Handling.

Кроме того — мы тут снова используем nindent, что бы добавить символ новой строки для каждой строки, полученной из .Values.deployment.environments.

Helm tpl

И ещё один вариант — использование tpl функции.

В отличии от других способов — тут мы можем в values.yaml использовать директивы типа {{ .Release.Name }}, т.к. подставляемый блок будет обработан шаблонизатором, как часть самого шаблона.

Обновим 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

Обратите внимание на |- — описываем блок с переменными как string, и убираем newline в конце.

Теперь добавляем в шаблон:

...
      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 }}
...

Тут мы в функцию tml передаём содержимое .Values.deployment.environments, и указываем включить его в текущий шаблон.

Проверяем:

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
...

Блок env из _helpers.tpl

Тут всё более-менее нам уже известно, но добавим проверку — есть ли значение для переменной в values.yaml.

if/else — flow control

В _helpers.yaml описываем шаблон:

{{- 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 }}

Тут с помощью if для каждой переменной мы проверяем — имеется ли значение, и если оно будет найдено — то переменная и значение будут добавлены в общий шаблон.

Таким образом — можем спокойно расширять список переменных в helpers.environments, не волнуясь о том, что в каком-то проекте не будет значения для новой переменной из подключаемого шаблона, и изменение этого общего шаблона поломает деплой этого приложения.

Включаем его в шаблон:

...
      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 }}
...

Проверяем:

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
...

Обратите внимание, что DB_PASSWORD не создана, т.к. {{ .Release.Name }} есть по-умолчанию, .Values.appConfig.db.host и .Values.appConfig.db.user — есть в values.yaml, а .Values.appConfig.db.password у нас хранится в Helm Secrets и файле secrets.yaml, который мы сейчас не используем вообще.

Соответсвенно, при генерации манифеста — Helm пропускает создание этой переменной.

Блок env из файла

Ну и третий вариант, который придумался — подключать содержимое блока env в общий шаблон из файла проекта.

Добавим в values.yaml параметр, который будет отключать include шаблона из _helpers.tpl, назовём его customEnvs, и заодно параметр, который позволит передавать путь к файлу с переменными — customEnvsFile:

...
deployment:

  customEnvs: true
  customEnvsFile: 'projects/newapp/templates/environments.yaml'
....

Теперь в деплоймент добавляем проверку условия, если оно не срабатывает — то через {{ else }} выполняем подстановку из _helpers.yaml:

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

И всё хорошо, но только потому, что сейчас из projects/newapp/templates/environments.yaml убран вызов .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

Решаем с помощью той жу функции tpl, которую уже использовали.

Возвращаемся к projects/newapp/templates/environments.yaml — добавляем .Release.Name:

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

В деплойменте меняем вставку — добавляем tpl,  а сам .Files.Get и его аргумент заворачиваем в скобки:

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

Проверяем:

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
...

С volumes, volumesMounts постуаем аналогично.

В целом схема с использованием _helpers.yaml выглядит вполне рабочей — попробуем применить на одном проекте, и будем посмотреть.

Поностью шаблон деплоймента теперь выглядит так:

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

Исключение шаблона из чарта

И напоследок — пример того, как можно исключить шаблон из чарта вообще.

Например, возьмём файл cronjobs.yaml:

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

Обратите внимание, что мы тут используем наш _helpers.yaml, что облечает жизнь — те же лейблы, те же переменные.

Но — не каждый проект использует кроны. Как исключить из чарта?

В values.yaml добавляем параметр cronjobs.enabled:

...
################
### Cronjobs ###
################ 
             
cronjobs:     
          
  enabled: false
  schedule: '*/15 * * * *'
  startingDeadline: 10
  concurrencyPolicy: 'Forbid'
...

А затем — оборачиваем весь шаблон в единый блок if:

{{- 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 }}

Проверяем:

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

Кронов нет.

Добавляем --set cronjobs.enabled=true — и они будут добавлены в релиз:

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:
- name: bttrm-docker-secret

Готово.

Ссылки по теме