Проект активно развивается, Kubernetes прижился, и всё больше наших сайтов запускается в нём.
И со временем возникла вполне ожидаемая проблема, которая уже озвучивалась в самом начале нашего «путешествия» в Helm: пошаговое создание чарта и деплоймента из Jenkins — как быть с манифестами Kubernetes и шаблонами Helm для нескольких приложений?
Особенно остро она встала сейчас, когда у нас из одного и того же репозитория один и тот же код деплоится в три независимых бекенда трёх независимых приложений.
Сначала было одно, потом появилось второе, сейчас готовится к запуску третье.
В результате чарты сейчас выглядят так:
[simterm]
$ tree -d k8s/
k8s/
├── app1-chart
│ ├── charts
│ ├── env
│ │ ├── dev
│ │ ├── dev-2
│ │ └── prod
│ └── templates
└── app2-chart
├── charts
├── env
│ ├── dev
│ ├── prod
│ └── stage
└── templates
[/simterm]
И теперь копировать сюда тот же самый чарт третий раз?
Ну совсем не хочется.
А потому — посмотрим внимательнее на возможности шаблонизатора 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конкретного проекта
- type: общий, значение будем брать из файла
selector:matchLabels: общий, и будет использоваться в нескольких местах, например в Cronjobs и Services — вынесем в_helpers.yaml
template:metadata:labels: берём из_helpers.yaml
spec:containers:name: общий, значение будем брать из файлаvalues.yamlконкретного проектаimage: общий, значение будем брать из файлаvalues.yamlконкретного проектаenv: вот тут самое интересное:- можем вынести целым блоком в
values.yamlкаждого проекта- плюсы: не надо будет отдельно описывать сами значения — можем их указать прямо тут, в поле
valueсамих переменных - минусы:
- практически у всех одинаковые переменные — дублируем код между проектами
- раздутый файл
values.yaml, т.к. переменных много - не все переменные идут в виде КЛЮЧ:ЗНАЧЕНИЕ — некоторые будут брать значение через
valueFrom.secretKeyRef.<KUBE_SECRET_NAME>, значит значение вvalues.yamlзадать не выйдет
- плюсы: не надо будет отдельно описывать сами значения — можем их указать прямо тут, в поле
- можем включить в общий
_helpers.yaml, и будет общий набор переменных, каждая заключена в свойif— если вvalues.yamlзначение найдено, то переменная создаётся в общем шаблоне - и на случай, если у проекта совершенно отдельный набор переменных — то в общем шаблоне перед блоком
envможно добавить условие типаif {{ .Values.chartSettings.customEnvs == true }}— будем пропускать включение переменных из_helpers.yamlилиvalues.yaml, и подключать файл шаблона черезtpl .File.Get project1/envs.yaml
- можем вынести целым блоком в
volumeMounts: тоже может различаться, посмотрим потомports:containerPort: будет всегда и у всех, берём изvalues.yaml(кстати — порты не обязательны вообще, см. Should I configure the ports in the Kubernetes deployment?)
livenessProbe,readinessProbe: в целом тоже будет общий, сhttpGet.pathиhttpGet.port— можно вынести в_helpers.yaml, но добавить тоже условиеcustomProbes, и при необходимости — подключать через.File.Getresources:requests.cpu,requests.memory— будет у всех… ли… — надо думать (см. Kubernetes: Evicted поды и Quality of Service для подов)limits.cpu,limits.memory— память точно будем ограничивать всем (или?) — надо думать
volumes: — тоже может различаться, посмотрим потомimagePullSecrets: одинаковый у всех
hpa.yaml— описываем HPAnetwork.yaml— Service, Ingresssecrets.yaml— Kubernetes Secretsrbac.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
Какие вообще полезные лейблы можно добавить, которые были бы общими?
- environment — Dev, 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.
Создаём дерево каталогов:
[simterm]
$ mkdir -p projects/newapp/env/dev
[/simterm]
И внутри него — файлы values.yaml и secrets.yaml:
[simterm]
$ touch projects/newapp/env/dev/{values.yaml,secrets.yaml}
[/simterm]
Далее, в 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 }}
Проверям:
[simterm]
$ 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
...
[/simterm]
Кстати — уже видим и наши лейблы.
А вот и наши переменные:
[simterm]
...
env:
- name: DATABASE_HOST
value: dev.aurora.web.example.com
...
[/simterm]
Цикл 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'
...
Проверяем:
[simterm]
$ 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
...
[/simterm]
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 }}
Который используется для удаления символов пробела перед вставляемым блоком, при этом новая строка также считается пробелом.
Если его убрать — то в результате мы получим лишнюю новую строку:
[simterm]
...
env:
- name: DATABASE_HOST
value: dev.aurora.web.example.com
- name: DB_USERNAME
...
[/simterm]
См. 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, и указываем включить его в текущий шаблон.
Проверяем:
[simterm]
$ 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
...
[/simterm]
Блок 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 }}
...
Проверяем:
[simterm]
$ 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
...
[/simterm]
Обратите внимание, что 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 -}}
...
Проверяем:
[simterm]
$ 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
...
[/simterm]
С 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 }}
Проверяем:
[simterm]
$ 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
[/simterm]
Кронов нет.
Добавляем --set cronjobs.enabled=true — и они будут добавлены в релиз:
[simterm]
$ 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
[/simterm]
Готово.
Ссылки по теме
- The Art of the Helm Chart: Patterns from the Official Kubernetes Charts
- Writing Reusable Helm Charts
- How To Reduce Helm Chart Boilerplate With Named Templates
- Helm from basics to advanced
- Создание пакетов для Kubernetes с Helm: структура чарта и шаблонизация
- Helm Templates Cheat Sheet
- Introduction to Helm — Templates
- Helm Templates
- Helm Tricks: Input Validation With ‘Required’ And ‘Fail’
- Helm Chart and Template Basics — Part 1