- PVSM.RU - https://www.pvsm.ru -

Создание пакетов для Kubernetes с Helm: структура чарта и шаблонизация

Создание пакетов для Kubernetes с Helm: структура чарта и шаблонизация - 1

Про Helm и работу с ним «в общем» мы рассказали в прошлой статье [1]. Теперь подойдём к практике с другой стороны — с точки зрения создателя чартов (т.е. пакетов для Helm). И хотя эта статья пришла из мира эксплуатации, она получилась больше похожей на материалы о языках программирования — такова уж участь авторов чартов. Итак, чарт — это набор файлов…

Файлы чарта можно разделить на две группы:

  1. Файлы, необходимые для генерации манифестов Kubernetes-ресурсов. К ним относятся шаблоны из директории templates и файлы со значениями (значения по умолчанию хранятся в values.yaml). Также к данной группе относятся файл requirements.yaml и директория charts — всё это используется для организации вложенных чартов.
  2. Сопроводительные файлы, содержащие информацию, которая может быть полезна при поиске чартов, знакомстве с ними и их использовании. Большая часть файлов этой группы является необязательной.

Подробнее о файлах обеих групп:

  • Chart.yaml — файл с информацией о чарте;
  • LICENSE — необязательный текстовый файл с лицензией чарта;
  • README.md — необязательный файл с документацией;
  • requirements.yaml — необязательный файл со списком чартов-зависимостей;
  • values.yaml — файл со значениями по умолчанию для шаблонов;
  • charts/ — необязательная директория со вложенными чартами;
  • templates/ — директория с шаблонами манифестов Kubernetes-ресурсов;
  • templates/NOTES.txt — необязательный текстовый файл с примечанием, которое выводится пользователю при инсталяции и обновлении.

Чтобы лучше разобраться в содержимом этих файлов, можно обратиться к официальному руководству разработчика чарта [2] или поискать соответствующие примеры в официальном репозитории [3].

Создание чарта по большому счёту сводится к организации правильно оформленного набора файлов. И главная сложность в этом «оформлении» — использование достаточно продвинутой системы шаблонов для достижения нужного результата. Для рендера манифестов Kubernetes-ресурсов используется стандартный Go-шаблонизатор [4], расширенный функциями Helm.

Напоминание: Разработчики Helm анонсировали, что в следующей крупной версии проекта — Helm 3 — появится поддержка Lua-скриптов, которые можно будет использовать одновременно с Go-шаблонами. Останавливаться подробнее на этом моменте не буду — об этом (и других изменениях в Helm 3) можно почитать здесь [5].

К примеру, вот так в Helm 2 выглядит шаблон Kubernetes-манифеста Deployment'а блога на WordPress из прошлой статьи [1]:

deployment.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: {{ template "fullname" . }}
  labels:
    app: {{ template "fullname" . }}
    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    release: "{{ .Release.Name }}"
    heritage: "{{ .Release.Service }}"
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    metadata:
      labels:
        app: {{ template "fullname" . }}
        chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
        release: "{{ .Release.Name }}"
    spec:
      {{- if .Values.image.pullSecrets }}
      imagePullSecrets:
      {{- range .Values.image.pullSecrets }}
        - name: {{ . }}
      {{- end}}
      {{- end }}
      containers:
      - name: {{ template "fullname" . }}
        image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy | quote }}
        env:
        - name: ALLOW_EMPTY_PASSWORD
        {{- if .Values.allowEmptyPassword }}
          value: "yes"
        {{- else }}
          value: "no"
        {{- end }}
        - name: MARIADB_HOST
        {{- if .Values.mariadb.enabled }}
          value: {{ template "mariadb.fullname" . }}
        {{- else }}
          value: {{ .Values.externalDatabase.host | quote }}
        {{- end }}
        - name: MARIADB_PORT_NUMBER
        {{- if .Values.mariadb.enabled }}
          value: "3306"
        {{- else }}
          value: {{ .Values.externalDatabase.port | quote }}
        {{- end }}
        - name: WORDPRESS_DATABASE_NAME
        {{- if .Values.mariadb.enabled }}
          value: {{ .Values.mariadb.db.name | quote }}
        {{- else }}
          value: {{ .Values.externalDatabase.database | quote }}
        {{- end }}
        - name: WORDPRESS_DATABASE_USER
        {{- if .Values.mariadb.enabled }}
          value: {{ .Values.mariadb.db.user | quote }}
        {{- else }}
          value: {{ .Values.externalDatabase.user | quote }}
        {{- end }}
        - name: WORDPRESS_DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
            {{- if .Values.mariadb.enabled }}
              name: {{ template "mariadb.fullname" . }}
              key: mariadb-password
            {{- else }}
              name: {{ printf "%s-%s" .Release.Name "externaldb" }}
              key: db-password
            {{- end }}
        - name: WORDPRESS_USERNAME
          value: {{ .Values.wordpressUsername | quote }}
        - name: WORDPRESS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: {{ template "fullname" . }}
              key: wordpress-password
        - name: WORDPRESS_EMAIL
          value: {{ .Values.wordpressEmail | quote }}
        - name: WORDPRESS_FIRST_NAME
          value: {{ .Values.wordpressFirstName | quote }}
        - name: WORDPRESS_LAST_NAME
          value: {{ .Values.wordpressLastName | quote }}
        - name: WORDPRESS_BLOG_NAME
          value: {{ .Values.wordpressBlogName | quote }}
        - name: WORDPRESS_TABLE_PREFIX
          value: {{ .Values.wordpressTablePrefix | quote }}
        - name: SMTP_HOST
          value: {{ .Values.smtpHost | quote }}
        - name: SMTP_PORT
          value: {{ .Values.smtpPort | quote }}
        - name: SMTP_USER
          value: {{ .Values.smtpUser | quote }}
        - name: SMTP_PASSWORD
          valueFrom:
            secretKeyRef:
              name: {{ template "fullname" . }}
              key: smtp-password
        - name: SMTP_USERNAME
          value: {{ .Values.smtpUsername | quote }}
        - name: SMTP_PROTOCOL
          value: {{ .Values.smtpProtocol | quote }}
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
        livenessProbe:
          httpGet:
            path: /wp-login.php
          {{- if not .Values.healthcheckHttps }}
            port: http
          {{- else }}
            port: https
            scheme: HTTPS
          {{- end }}
{{ toYaml .Values.livenessProbe | indent 10 }}
        readinessProbe:
          httpGet:
            path: /wp-login.php
          {{- if not .Values.healthcheckHttps }}
            port: http
          {{- else }}
            port: https
            scheme: HTTPS
          {{- end }}
{{ toYaml .Values.readinessProbe | indent 10 }}
        volumeMounts:
        - mountPath: /bitnami/apache
          name: wordpress-data
          subPath: apache
        - mountPath: /bitnami/wordpress
          name: wordpress-data
          subPath: wordpress
        - mountPath: /bitnami/php
          name: wordpress-data
          subPath: php
        resources:
{{ toYaml .Values.resources | indent 10 }}
      volumes:
      - name: wordpress-data
      {{- if .Values.persistence.enabled }}
        persistentVolumeClaim:
          claimName: {{ .Values.persistence.existingClaim | default (include "fullname" .) }}
      {{- else }}
        emptyDir: {}
      {{ end }}
    {{- if .Values.nodeSelector }}
      nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
      {{- end -}}
    {{- with .Values.affinity }}
      affinity:
{{ toYaml . | indent 8 }}
    {{- end }}
    {{- with .Values.tolerations }}
      tolerations:
{{ toYaml . | indent 8 }}
    {{- end }}

Теперь — об основных принципах и особенностях шаблонизации в Helm. Большая часть приведённых ниже примеров взята из чартов официального репозитория [3].

Шаблонизация

Шаблоны: {{ }}

Всё, что связано с шаблонизацией, оборачивается в двойные фигурные скобки. Текст вне фигурных скобок при рендере остаётся неизменным.

Значение контекста: .

При рендере файла или partial'а (подробнее о переиспользовании шаблонов рассказывается в следующих разделах статьи) прокидывается значение, которое становится доступным внутри через переменную контекста — точку. При передаче в качестве аргумента структуры точка используется для доступа к полям и методам этой структуры.

Значение переменной изменяется в процессе рендера в зависимости от контекста, в котором она используется. Большинство блочных операторов переопределяет переменную контекста внутри основного блока. Основные операторы и их особенности будут рассмотрены ниже, после знакомства с базовой структурой Helm.

Базовая структура Helm

При рендере манифестов в шаблоны прокидывается структура со следующими полями:

  • Поле .Values — для доступа к параметрам, которые определяются при инсталяции и обновлении релиза. К ним относятся значения опций --set, --set-string и --set-file, а также параметры файлов со значeниями, файл values.yaml и файлы, соответствующие значениям опций --values:
    containers:
    - name: main
      image: "{{ .Values.image }}:{{ .Values.imageTag }}"
      imagePullPolicy: {{ .Values.imagePullPolicy }}
    
  • .Release — для использования данных релиза [6] о выкате, инсталяции или обновлении, имени релиза, namespace и значений ещё нескольких полей, которые могут пригодиться при генерации манифестов:
    metadata:
      labels:
        heritage: "{{ .Release.Service }}"
        release: "{{ .Release.Name }}"
    subjects:
    - namespace: {{ .Release.Namespace }}
    
  • .Chart — для доступа к информации о чарте [7]. Поля соответствуют содержимому файла Chart.yaml:
    labels:
      chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    
  • Структура .Files — для работы с хранящимися в директории чарта файлами; со структурой и доступными методами можно ознакомиться по ссылке [8]. Примеры:
    data:
      openssl.conf: |
    {{ .Files.Get "config/openssl.conf" | indent 4 }}
    

    data:
    {{ (.Files.Glob "files/docker-entrypoint-initdb.d/*").AsConfig | indent 2 }}
    
  • .Capabilities — для доступа к информации о кластере [9], в котором выполняется выкат:
    {{- if .Capabilities.APIVersions.Has "apps/v1beta2" }}
    apiVersion: apps/v1beta2
    {{- else }}
    apiVersion: extensions/v1beta1
    {{- end }}
    

    {{- if semverCompare "^1.9-0" .Capabilities.KubeVersion.GitVersion }}
    apiVersion: apps/v1
    {{- else }}
    

Операторы

Начнём, конечно, с операторов if, else if и else:

{{- if .Values.agent.image.tag }}
image: "{{ .Values.agent.image.repository }}:{{ .Values.agent.image.tag }}"
{{- else }}
image: "{{ .Values.agent.image.repository }}:v{{ .Chart.AppVersion }}"
{{- end }}

Оператор range предназначен для работы с массивами и картами. Если в качестве аргумента передаётся массив и он содержит элементы, то для каждого элемента последовательно выполняется блок (при этом значение внутри блока становится доступным через переменную контекста):

{{- range .Values.ports }}
- name: {{ .name }}
  port: {{ .containerPort }}
  targetPort: {{ .containerPort}}
{{- else }}
...
{{- end}}

{{ range .Values.tolerations -}}
- {{ toYaml . | indent 8 | trim }}
{{ end }}

Для работы с картами предусмотрен синтаксис с переменными:

{{- range $key, $value := .Values.credentials.secretContents }}
  {{ $key }}: {{ $value | b64enc | quote }}
{{- end }}

Похожее поведение — у оператора with: eсли переданный аргумент существует, то выполняется блок, а переменная контекста в блоке соответствует значению аргумента. Например:

{{- with .config }}
  config:
  {{- with .region }}
    region: {{ . }}
  {{- end }}
  {{- with .s3ForcePathStyle }}
    s3ForcePathStyle: {{ . }}
  {{- end }}
  {{- with .s3Url }}
    s3Url: {{ . }}
  {{- end }}
  {{- with .kmsKeyId }}
    kmsKeyId: {{ . }}
  {{- end }}
{{- end }}

Для переиспользования шаблонов может быть задействована связка из define [name] и template [name] [variable], где переданное значение становится доступным через переменную контекста в блоке define:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ template "kiam.serviceAccountName.agent" . }}
...
{{- define "kiam.serviceAccountName.agent" -}}
{{- if .Values.serviceAccounts.agent.create -}}
  {{ default (include "kiam.agent.fullname" .) .Values.serviceAccounts.agent.name }}
{{- else -}}
  {{ default "default" .Values.serviceAccounts.agent.name }}
{{- end -}}
{{- end -}}

Пара особенностей, которые стоит учитывать при использовании define, или, проще говоря, partial'ов:

  • Объявленные partial'ы являются глобальными и могут использоваться во всех файлах директории templates.
  • Основной чарт компилируется вместе с зависимыми чартами, поэтому при существовании двух одноимённых partial'ов будет использоваться последний загруженный. При именовании partial'а принято добавлять имя чарта для избежания подобных конфликтов: define "chart_name.partial_name".

Переменные: $

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

{{ $provider := .Values.configuration.backupStorageProvider.name }}
...
{{ if eq $provider "azure" }}
envFrom:
- secretRef:
    name: {{ template "ark.secretName" . }}
{{ end }}

При рендере файла или partial'а $ имеет такое же значение, что и точка. Но в отличие от переменной контекста (точки), значение $ не изменяется в контексте блочных операторов, что позволяет одновременно работать со значением контекста блочного оператора и базовой структурой Helm (или значением, переданным в partial, если говорить об использовании $ внутри partial'а). Иллюстрация отличия:

context: {{ . }}
dollar: {{ $ }}
with: 
{{- with .Chart }}
  context: {{ . }}
  dollar: {{ $ }}
{{- end }}

template:
{{- template "flant" .Chart -}}

{{ define "flant" }}
  context: {{ . }}
  dollar: {{ $ }}
  with: 
  {{- with .Name }}
    context: {{ . }}
    dollar: {{ $ }}
  {{- end }}
{{- end -}}

В результате обработки этого шаблона получится следующее (для наглядности в выводе структуры заменены на соответствующие псевдоимена):

context: #Базовая структура helm
dollar: #Базовая структура helm
with:
  context: #.Chart
  dollar: #Базовая структура helm

template:
  context: #.Chart
  dollar: #.Chart
  with:
    context: habr
    dollar: #.Chart

А вот реальный пример использования данной особенности:

{{- if .Values.ingress.enabled -}}
{{- range .Values.ingress.hosts }}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: {{ template "nats.fullname" $ }}-monitoring
  labels:
    app: "{{ template "nats.name" $ }}"
    chart: "{{ template "nats.chart" $ }}"
    release: {{ $.Release.Name | quote }}
    heritage: {{ $.Release.Service | quote }}
  annotations:
    {{- if .tls }}
    ingress.kubernetes.io/secure-backends: "true"
    {{- end }}
    {{- range $key, $value := .annotations }}
    {{ $key }}: {{ $value | quote }}
    {{- end }}
spec:
  rules:
    - host: {{ .name }}
      http:
        paths:
        - path: {{ default "/" .path }}
          backend:
            serviceName: {{ template "nats.fullname" $ }}-monitoring
            servicePort: monitoring
{{- if .tls }}
  tls:
  - hosts:
    - {{ .name }}
    secretName: {{ .tlsSecret }}
{{- end }}
---
{{- end }}
{{- end }}

Отступы

При разработке шаблонов могут оставаться лишние отступы: пробелы, табуляции, переводы строк. С ними файл попросту выглядит более читабельным. Можно либо отказаться от них, либо использовать специальный синтаксис для удаления отступов вокруг используемых шаблонов:

  • {{- variable }} обрезает предшествующие пробелы;
  • {{ variable -}} обрезает последующие пробелы;
  • {{- variable -}} — оба варианта.

Пример файла, результатом обработки которого будет строка habr flant helm:

habr
{{- " flant " -}}
helm

Встроенные функции

Со всеми функциями, встроенными в шаблон, можно ознакомиться по следующей ссылке [10]. Здесь же я расскажу только о некоторых из них.

Функция index предназначена для доступа к элементам массива или карт:

definitions.json: |
    {
      "users": [
        {
          "name": "{{ index .Values "rabbitmq-ha" "rabbitmqUsername" }}",
          "password": "{{ index .Values "rabbitmq-ha" "rabbitmqPassword" }}",
          "tags": "administrator"
        }
      ]
    }

Функция принимает произвольное количество аргументов, что позволяет работать с вложенными элементами:

$map["key1"]["key2"]["key3"] => index $map "key1" "key2" "key3"

Например:

httpGet:
{{- if (index .Values "pushgateway" "extraArgs" "web.route-prefix") }}
  path: /{{ index .Values "pushgateway" "extraArgs" "web.route-prefix" }}/#/status
{{- end }}

Булевые операции реализованы в шаблонизаторе как функции (а не как операторы). Все аргументы для них вычисляются при передаче:

{{ if and (index .Values field) (eq (len .Values.field) 10) }}
...
{{ end }}

При отсутствии поля field рендер шаблона завершится с ошибкой (error calling len: len of untyped nil): второе условие проверяется, несмотря на то, что первое не выполнилось. Стоит взять это на заметку, а подобные запросы решать за счёт разбиения на несколько проверок:

{{ if index . field }}
  {{ if eq (len .field) 10 }}
  ...
  {{ end }}
{{ end }}

Pipeline — это уникальная функция Go-шаблонов, позволяющая объявлять выражения, которые выполняются подобно конвейеру в shell. Формально конвейер представляет собой цепочку команд, разделенных символом |. Команда может быть простым значением или вызовом функции. Результат каждой команды передаётся в качестве последнего аргумента следующей команде, а результатом конечной команды в конвейере является значение всего конвейера. Примеры:

data:
  openssl.conf: |
{{ .Files.Get "config/openssl.conf" | indent 4 }}

data:
  db-password: {{ .Values.externalDatabase.password | b64enc | quote }}

Дополнительные функции

Sprig [11] — библиотека, состоящая из 70 полезных функций для решения широкого спектра задач. Из соображений безопасности в Helm исключены функции env и expandenv, которые предоставляли бы доступ к переменным окружения Tiller.

Функция include, как и стандартная функция template, используется для переиспользования шаблонов. В отличие от template, функцию можно использовать в pipeline, т.е. передавать результат в другую функцию:

metadata:
  labels:
{{ include "labels.standard" . | indent 4 }}

{{- define "labels.standard" -}}
app: {{ include "hlf-couchdb.name" . }}
heritage: {{ .Release.Service | quote }}
release: {{ .Release.Name | quote }}
chart: {{ include "hlf-couchdb.chart" . }}
{{- end -}}

Функция required даёт разработчикам возможность объявлять обязательные значения, необходимые для рендеринга шаблона: если значение существует, при рендере шаблона оно используется, в противном же случае рендер завершается с указанным разработчиком сообщением об ошибке:

sftp-user: {{ required "Please specify the SFTP user name at .Values.sftp.user" .Values.sftp.user | b64enc | quote }}
sftp-password: {{ required "Please specify the SFTP user password at .Values.sftp.password" .Values.sftp.password | b64enc | quote }}
{{- end }}
{{- if .Values.svn.enabled }}
svn-user: {{ required "Please specify the SVN user name at .Values.svn.user" .Values.svn.user | b64enc | quote }}
svn-password: {{ required "Please specify the SVN user password at .Values.svn.password" .Values.svn.password | b64enc | quote }}
{{- end }}
{{- if .Values.webdav.enabled }}
webdav-user: {{ required "Please specify the WebDAV user name at .Values.webdav.user" .Values.webdav.user | b64enc | quote }}
webdav-password: {{ required "Please specify the WebDAV user password at .Values.webdav.password" .Values.webdav.password | b64enc | quote }}
{{- end }}

Функция tpl позволяет рендерить строку как шаблон. В отличие от template и include, функция позволяет выполнять шаблоны, которые передаются в переменных, а также рендерить шаблоны, хранящиеся не только в директории templates. Как это выглядит?

Выполнение шаблонов из переменных:

containers:
{{- with .Values.keycloak.extraContainers }}
{{ tpl . $ | indent 2 }}
{{- end }}

… а в values.yaml имеем следующее значение:

keycloak:
  extraContainers: |
    - name: cloudsql-proxy
      image: gcr.io/cloudsql-docker/gce-proxy:1.11
      command:
        - /cloud_sql_proxy
      args:
        - -instances={{ .Values.cloudsql.project }}:{{ .Values.cloudsql.region }}:{{ .Values.cloudsql.instance }}=tcp:5432
        - -credential_file=/secrets/cloudsql/credentials.json
      volumeMounts:
        - name: cloudsql-creds
          mountPath: /secrets/cloudsql
          readOnly: true

Рендер файла, хранящегося вне директории templates:

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ template "mysqldump.fullname" . }}
  labels:
    app: {{ template "mysqldump.name" . }}
    chart: {{ template "mysqldump.chart" . }}
    release: "{{ .Release.Name }}"
    heritage: "{{ .Release.Service }}"
spec:
  backoffLimit: 1
  template:
{{ $file := .Files.Get "files/job.tpl" }}
{{ tpl $file . | indent 4 }}

… в чарте, по пути files/job.tpl, имеется следующий шаблон:

spec:
  containers:
  - name: xtrabackup
    image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
    imagePullPolicy: {{ .Values.image.pullPolicy | quote }}
    command: ["/bin/bash", "/scripts/backup.sh"]
    envFrom:
    - configMapRef:
        name: "{{ template "mysqldump.fullname" . }}"
    - secretRef:
        name: "{{ template "mysqldump.fullname" . }}"
    volumeMounts:
    - name: backups
      mountPath: /backup
    - name: xtrabackup-script
      mountPath: /scripts
  restartPolicy: Never
  volumes:
  - name: backups
{{- if .Values.persistentVolumeClaim }}
    persistentVolumeClaim:
      claimName: {{ .Values.persistentVolumeClaim }}
{{- else -}}
{{- if .Values.persistence.enabled }}
    persistentVolumeClaim:
      claimName: {{ template "mysqldump.fullname" . }}
{{- else }}
    emptyDir: {}
{{- end }}
{{- end }}
  - name: xtrabackup-script
    configMap:
      name: {{ template "mysqldump.fullname" . }}-script

На этом знакомство с азами шаблонизации в Helm подошло к концу…

Заключение

В статье рассказано о структуре Helm-чартов и подробно разобрана главная сложность в их создании — шаблонизация: основные принципы, синтаксис, функции и операторы Go-шаблонизатора, дополнительные функции.

Как начать со всем этим работать? Поскольку Helm — это уже целая экосистема, всегда можно посмотреть на примеры чартов схожих пакетов. Например, если вы хотите запаковать новый message queue, взгляните на публичный чарт RabbitMQ [12]. Конечно, никто не обещает вам идеальных реализаций в уже существующих пакетах, однако они отлично подойдут как отправная точка. Остальное же приходит с практикой, в которой вам помогут команды отладки helm template и helm lint, а также запуск инсталяции с опцией --dry-run.

Для получения более обширного представления о разработке Helm-чартов, лучших практиках и используемых технологиях предлагаю ознакомиться с материалами по следующим ссылкам (все на английском языке):

А в конце очередного материала про Helm прикрепляю опрос, который поможет лучше понять, какие ещё статьи о Helm ждут (или не ждут?) читатели Хабра. Спасибо за внимание!

P.S.

Читайте также в нашем блоге:

Автор: Алексей Игрычев

Источник [18]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/open-source/292965

Ссылки в тексте:

[1] прошлой статье: https://habr.com/company/flant/blog/420437/

[2] официальному руководству разработчика чарта: https://docs.helm.sh/chart_template_guide/

[3] официальном репозитории: https://github.com/helm/charts

[4] Go-шаблонизатор: https://golang.org/pkg/text/template/

[5] здесь: https://habr.com/company/flant/blog/417079/

[6] данных релиза: https://github.com/helm/helm/blob/e8d80729ac20f402a80c5107b8b2513008de13fc/pkg/chartutil/values.go#L362-L370

[7] информации о чарте: https://github.com/helm/helm/blob/250d25fdceaf5546f29228f3711cedd8d6776fcd/pkg/proto/hapi/chart/metadata.pb.go#L75-L112

[8] ссылке: https://github.com/helm/helm/blob/f7f686f7d065218ef6df3fbb75ce6348e699a0f3/pkg/chartutil/files.go

[9] информации о кластере: https://github.com/helm/helm/blob/89c29f3ff107bd107a3a115c69e41bcf89422bc0/pkg/chartutil/capabilities.go#L42

[10] следующей ссылке: https://golang.org/pkg/text/template/#hdr-Functions

[11] Sprig: http://masterminds.github.io/sprig/

[12] публичный чарт RabbitMQ: https://github.com/helm/charts/tree/master/stable/rabbitmq

[13] Лучшие практики: https://docs.helm.sh/chart_best_practices/

[14] Документация Go-шаблонизатора: https://golang.org/pkg/text/template

[15] Практика с dapp. Часть 2. Деплой Docker-образов в Kubernetes с помощью Helm: https://habr.com/company/flant/blog/336170/

[16] Сборка и дeплой приложений в Kubernetes с помощью dapp и GitLab CI: https://habr.com/company/flant/blog/345580/

[17] Лучшие практики CI/CD с Kubernetes и GitLab: https://habr.com/company/flant/blog/345116/

[18] Источник: https://habr.com/post/423239/?utm_source=habrahabr&utm_medium=rss&utm_campaign=423239