Проект активно развивается, 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.Get
resources
: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