Pull to refresh

Искусство Helm Chart: паттерны из официальных чартов Kubernetes

Reading time11 min
Views9.7K
Original author: Ryan Dawson

Установка и управление Helm Charts может вызвать у вас некоторые сложности, с которыми вы, возможно, не сталкивались раньше.


Helm Charts упаковывает приложения для установки в кластеры Kubernetes. Установка Helm Chart немного похожа на запуск мастера установки, поэтому разработчики Helm Chart сталкиваются с некоторыми из тех же проблем, с которыми сталкиваются разработчики, производящие установщики:


  • Какие предположения можно сделать об окружении, в которой выполняется установка?
  • Может ли приложение взаимодействовать с другими приложениями?
  • Какие конфигурации должны быть доступны пользователю и как их предлагать?

Но эти вопросы связаны с особенностями Helm. Чтобы понять, почему, давайте начнем с картины того, что происходит, когда пользователь запускает helm install. Затем мы можем перейти к рассмотрению того, как некоторые официальные чарты Kubernetes решают эти вопросы.


Картина о запускеhelm install


Я хочу установить MySQL в свой кластер. Но мне не нужна версия MySQL, которую stable/MySQL устанавливает в файле values.yaml в официальном репозитории чартов. Итак, я создаю свой собственный файл values.yaml с именем mysql-values.yaml всего с одной строкой:


imageTag: “5.7.10”

Затем я запускаю helm install stable/mysql --values=mysqlvalues.yaml.


Helm генерирует для меня уникальное имя выпуска (ignorant-camel), и MySQL развернут в моем кластере. Вывод kubectl describe pod ignorant-camel-mysql-5dc6b947b-lf6p8 сообщает мне, что выбранный мной тег imageTag применен.


На самом деле мне не нужно было ничего устанавливать в моем кластере, чтобы увидеть, что мой выбор для imageTag будет применен. Я мог бы запустить helm install stabe/mysql --values=mysqlvalues.yaml --dry-run --debug, и Helm просто показал бы мне содержимое созданного им дескриптора развертывания Kubernetes, ничего не устанавливая.


Процесс доступа к сгенерированным дескрипторам развертывания Kubernetes можно лучше понять, подумав о структуре Helm Chart:


├── Chart.yaml
├── README.md
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── secrets.yaml
│   └── ...more yaml...
└── values.yaml

Когда пользователь запускает helm install stable/mysql, то записи из values.yaml в чарте и информация о выпуске Helm (например, уникальное имя выпуска) вводятся в шаблонные дескрипторы ресурсов yaml по мере того, как шаблон оценивается и визуализируется в чистые дескрипторы развертывания Kubernetes. Когда пользователь запускает helm install stable/mysql с параметрами или файлом значений, то файл параметров или значений будет наложен на значения в чарте. Мы должны думать о файле значений в чарте как об установке значений по умолчанию, которые могут быть переопределены.


Итак, values.yaml — это главный интерфейс между нами, разработчиками чартов, и нашими пользователями. То, что мы показываем в values.yaml, определяет, что пользователи могут и что не могут делать с нашими чартами.


Наши чарты и наши файлы values.yaml также должны удовлетворять другому сценарию. Другому разработчику чартов может понравиться наше приложение, и он решит включить его в пакет, который они хотят выпустить. Поэтому они добавляют наш чарт в requirements.yaml нового чарта, которую они создают, вместе с множеством других чартов. Они отменяют некоторые из наших значений по умолчанию, используя их values.yaml. А затем они распространяют его среди пользователей, которые устанавливают этот чарт c помощью Helm.


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


Проблемы написания чартов Helm


Приложения часто имеют множество вариантов конфигурации, и кластеры Kubernetes можно настраивать множеством разных способов. Поэтому при написании Helm Chart мы, естественно, сталкиваемся с такими вопросами:
• Что, если я забуду указать параметр конфигурации в values.yaml? Что, если мое приложение динамически анализирует конфигурацию, поэтому я не могу заранее сказать, какими будут все возможные имена параметров?
• Как я могу разрешить пользователям использовать ресурсы, определенные не непосредственно в моем чарте, а в чарте, в который мой чарт используется в качестве подчарта?
• Что, если другой разработчик чарта использует мой чарт самостоятельно и ему нужно добавить важные разделы внутри ресурса, который я определил (например, добавить дополнительный контейнер в модуль или пользовательский скрипт инициализации)?
• Как я могу разрешить своим пользователям открывать доступ к приложению извне различными способами, которые они могут захотеть?


Мы можем многое узнать о том, как справляться с подобными проблемами из чартов в официальном репозитории Helm Charts. Давайте рассмотрим некоторые шаблоны, которые используются в этих чартах, чтобы понять, как использовать те же шаблоны в наших собственных чартах.


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


Итак, давайте попробуем получить снимок паттернов из официальных чартов. Это не будет исчерпывающим, и я ожидаю, что появятся новые опции и новые шаблоны, когда Helm 3 добавит скрипты с Lua. Но это даст нам возможность взглянуть на современное состояние искусства.


1. Шаблоны для раскрытия параметров конфигурации


Допустим, мы определили ресурс развертывания в нашем шаблоне и настроили раздел env, чтобы разрешить установку некоторых переменных среды из values.yaml:


- name: ENV_VAR1
  value: {{ .Values.var1 }}
- name: ENV_VAR2
  value: {{ .Values.var2 }}

Теперь пользователи нашего чарта могут переопределить эти значения в своих файлах values.yaml или с помощью --set var1=foo. Но что смогут сделать наши пользователи, если мы упустили одну из них? Или, что еще хуже, если наше приложение может динамически анализировать параметры конфигурации (например, оно может читать ENV_VAR1 и создавать внутреннюю переменную с именем var1)? Тогда нет даже конечного набора имен параметров конфигурации, который можно было бы раскрыть. Итак, как мы можем позволить пользователям устанавливать имена переменных, а также значения?


Как сказано в руководстве разработчика Helm Charts, мы могли бы создать configmap с функцией диапазона. Хороший пример этого для стабильного/несвязанного чарта. Он содержит configmap, которая определяет его файл unbound.conf. Он монтирует этот файл в поды, созданные при его развертывании. Внутри configmap есть такие записи, как:


{{- range .Values.localRecords }}
local-data: "{{ .name }} A {{ .ip }}"
local-data-ptr: "{{ .ip }} {{ .name }}"
{{- end }}

И его values.yaml позволяет устанавливать записи в localRecords в виде списка, например:


localRecords:
- name: "fake3.host.net"
  ip: "10.12.10.10"
- name: "fake4.host.net"
  ip: "10.13.10.10"

Sonarqube chart применяет аналогичный подход непосредственно к переменным среды, определяя некоторые и позволяя устанавливать дополнительные переменные с помощью коллекции extraEnv:


{{- range $key, $value := .Values.extraEnv }}
 — name: {{ $key }}
   value: {{ $value }}
{{- end }}

Чтобы переменные были установлены в values.yaml, надо сделать например:


extraEnv:
- ENV_VAR1: var1
- ENV_VAR2: var2

Многие официальные чарты определяют extraEnv, но несколько иначе. Чарт Buildkite определяет его иначе, без использования диапазона. Вместо этого он берет соответствующий раздел values.yaml и просто помещает его в шаблон:


{{- if .Values.extraEnv }}
{{ toYaml .Values.extraEnv | indent 12 }}
{{- end }}

Таким образом, это означает, что вместо того, чтобы устанавливать записи extraEnv в values.yaml как простые пары, нам также нужно будет назвать каждый из ключей (имя) и значения (значение) в парах, например:


extraEnv:
 — name: ENV_VAR1
   value: "var1"
 — name: ENV_VAR2
   value: "var2"

Чарт Keycloak снова делает это иначе:


{{- with .Values.keycloak.extraEnv }}
{{ tpl . $ | indent 12 }}
{{- end }}

Таким образом, он обрабатывает extraEnv как строку, отправляет ее через функцию tpl, чтобы она оценивалась как часть шаблона и гарантирует, что применяется правильный отступ. Это интересно для нас, поскольку позволяет устанавливать такие значения, как:


extraEnv: |
 — name: KEYCLOAK_LOGLEVEL
   value: DEBUG
 — name: HOSTNAME
   value: {{ .Release.Name }}-keycloak

Обычно невозможно использовать директиву шаблона вроде {{ .Release.Name }} внутри values.yaml, но здесь мы можем, потому что содержимое будет проходить через tpl. Это может быть большим преимуществом в случаях, когда мы включаем чарт в разрабатываемый родительский чарт, и нам нужно обратиться к службе, которая является частью того же родительского чарта (подробнее об этом в следующем разделе). Недостатки в том, что он немного более абстрактный и что содержимое в values.yaml обрабатывается там как строка, поэтому проблемы с форматированием не обязательно обнаруживаются вашим редактором.


2. Ссылки на взаимно развертываемые ресурсы


Обычно ресурсы, развертываемые через Helm, имеют префикс с именем выпуска, так что несколько выпусков могут быть установлены из одного чарта (и в одном пространстве имен) без конфликтов имен между установленными ресурсами. Это означает, что директивы шаблона должны использоваться для префикса имен ресурсов, когда один ресурс в чарте должен ссылаться на другой или на ресурсы в того же родительского чарта.


Распространенный случай, когда необходимо сослаться на совместно развернутый ресурс, — это секрет базы данных. Например, чарт Xray включает в себя возможность развертывания базы данных Postgres. Он настраивает развертывание своего компонента индексатора с учетными данными, указывая на секрет Postgres (который сам является частью чарта в виде подчарта и, следовательно, также имеет префикс с тем же именем выпуска):


{{- if .Values.postgresql.enabled }}
 — name: POSTGRES_USER
   value: {{ .Values.postgresql.postgresUser }}
 — name: POSTGRESS_PASSWORD
   valueFrom:
     secretKeyRef:
       name: {{ .Release.Name }}-postgresql
       key: postgres-password
 — name: POSTGRESS_DB
   value: {{ .Values.postgresql.postgresDatabase }}
 {{- else }}
...

Здесь чарт Xray знает, к какой базе данных ей необходимо подключиться, поскольку она сама включает чарт Postgres. Но что, если бы мы разрабатывали чарт для приложения, в котором пользователь может выбрать базу данных? Затем может потребоваться разрешить пользователю включать наш чарт в родительский чарт вместе с выбранной им базой данных. Как бы мы тогда разрешили пользователю устанавливать конфигурацию базы данных?


Одним из вариантов может быть подход extraEnv, который мы уже видели в чарте Keycloak. Затем пользователь может настроить extraEnv нашем чарте так, чтобы оно указывало на Postgres, даже если мы не определили ее явно в исходном чарте. В values.yaml будет такая запись:


extraEnv: |
 — name: POSTGRES_USER
   value: {{ .Values.postgresql.postgresUser }}
 — name: POSTGRESS_PASSWORD
   valueFrom:
     secretKeyRef:
       name: {{ .Release.Name }}-postgresql
       key: postgres-password
 — name: POSTGRESS_DB
   value: {{ .Values.postgresql.postgresDatabase }}

Символ| нужен, чтобы раздел обрабатывался как строка, которая явно передается в функцию tpl.


Аналогичный вопрос возникает, если необходимо использовать префикс имени выпуска в записи в файле, который загружается в configmap. Типичный способ загрузки всего файла в конфигурационную карту — использовать .Files.Get. Однако, как и в values.yaml, содержимое загруженных таким образом файлов не является частью шаблона, и поэтому в них нельзя использовать директивы шаблона. Что позволяет использовать директивы шаблона внутри файла, так это загрузка содержимого файла с помощью .Files.Get и передача его в tpl. Тогда в разделе данных configmap будут такие записи, как:


conf_file1: {{ tpl (.Files.Get "files/conf_file1") . | quote }}

Или для загрузки в Secret нам нужно закодировать контент в base64:


conf_file1: {{ tpl (.Files.Get "files/conf_file1") . | b64enc | quote }}

Вместо одного файла мы можем загрузить набор файлов в ConfigMap, используя .Files.Glob:


{{ (tpl (.Files.Glob "files/*").AsConfig . ) | indent 2 }}

Хотя мы не можем применить тот же подход к AsSecret, поскольку тогда контент будет закодирован до прохождения tpl. Чтобы загрузить набор файлов в секрет, мы могли бы использовать Glob для поиска файлов и Get для их загрузки:


{{ range $path, $bytes := .Files.Glob "files/*" }}
{{ base $path }}: '{{ tpl ($root.Files.Get $path) . | b64enc }}'
{{ end }}

3. Расширение возможностей наших коллег-разработчиков чартов


Использование extraEnv в чарте Keycloak предполагает возможный паттерн для работы с другими типами определений, которые, возможно, потребуется ввести в чарт. Например, чарт Keycloak должна позволять пользователю упаковывать чарт Keycloak внутри родительской чарты, которая также предоставляет определенный пользователем файл JSON, который пользователь должен иметь возможность монтировать в модули Keycloak. Чарт поддерживает это, выставляя extraVolumes:


{{- with .Values.keycloak.extraVolumes }}
{{ tpl . $ | indent 8 }}
{{- end }}

И extraVolumeMounts:


          volumeMounts:
            - name: scripts
              mountPath: /scripts
{{- with .Values.keycloak.extraVolumeMounts }}
{{ tpl . $ | indent 12 }}
{{- end }}

Затем пользователь может указать свой секрет (который содержит файл JSON) и смонтировать его через values.yaml:


extraVolumes: |
 — name: custom-secret
   secret:
     secretName: custom-secret
extraVolumeMounts: |
 - name: custom-secret
   mountPath: "/realm/"
   readOnly: true

Фактически конфигурация томов (volumes) и volumeMounts экстернализируется в values.yaml. Чарт также применяет этот шаблон, чтобы позволить пользователям вводить дополнительные ресурсы в шаблон, такие как initContainers и даже добавлять целые дополнительные контейнеры (или sidecars). Этот паттерн дает пользователям чартов большую силу и гибкость, особенно для других разработчиков чартов, использующих наш чарт.


Этот шаблон может быть особенно эффективным в сочетании с открытием Keycloak других параметров, таких как переменная preStartScript, которая используется в сценарии инициализации чарты:


{{- with .Values.keycloak.preStartScript }}                           
echo 'Running custom pre-start script...'                       
{{ . | indent 4 }}                         
{{- end }}

Пользователь может использовать любые команды оболочки, которые им нравятся, в .Values.keycloak.preStartScript в своих values.yaml. Таким образом, этот подход позволяет пользователям не просто устанавливать свои собственные параметры конфигурации, но также загружать свои собственные файлы и запускать свои собственные сценарии с использованием этих файлов.


4. Различное внешнее экспонирование


Стартовая чарт Helm, сгенерированная helm create, включает в себя спецификацию службы (Service), но не Ingress. Многие общедоступные чарты действительно определяют ресурс Ingress. Это должно быть настраиваемым, поскольку пользователи могут не захотеть использовать вход. Таким образом, чарт RabbitMQ, как и многие другие, включает в себя все определение ресурса Ingress с помощью:


{{- if .Values.ingress.enabled }}
...
{{-end}

Это соответствует рекомендациям по обзору официального репозитория чартов, которые рекомендуют отключать вход по умолчанию.


Например, чарт RabbitMQ должна предоставить возможность раскрывать ли ее, задав правило входа на основе хоста (host-based):


rules:
  {{- if .Values.ingress.hostName }}
  - host: {{ .Values.ingress.hostName }}
    http:
  {{- else }}
  - http:
  {{- end }}

(На самом деле, пользователь может захотеть, чтобы несколько хостов направлялись к службе, поэтому в рекомендациях по обзору предлагается использовать функцию диапазона для хостов. Чарт RabbitMQ предлагает только один хост.)


Чарт RabbitMQ также позволяет не устанавливать хост (условие else выше). В этом случае вполне вероятно, что пользователь вместо этого переопределит путь (так, чтобы RabbitMQ был доступен на уникальном маршруте, отличном от других открытых сервисов):


- path: {{ default "/" .path }}
  backend:
    serviceName: {{ template "rabbitmq.fullname" . }}
    servicePort: {{ .Values.rabbitmq.managerPort }}

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


В руководстве по обзору предлагается поддержать это с помощью toYaml:


{{- with .Values.ingress.annotations }}
 annotations:
{{ toYaml . | indent 4 }}
{{- end }}

Это позволяет пользователю чарты устанавливать аннотации в values.yaml, например:


annotations:
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/rewrite-target: /

Таким образом, пользователь может установить любые необходимые ему аннотации на входе через значения.yaml без чарты, требующей знать, что это могут быть за аннотации. Просто выставление этих аннотаций может быть равносильно тому, чтобы дать возможность применить конфигурацию, подобную сценарию, к входящей маршрутизации. Например, при входе NGINX пользователь может применять правила для установки заголовков пользовательскими способами с помощью фрагментов конфигурации:


annotations:
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/rewrite-target: /
  nginx.ingress.kubernetes.io/configuration-snippet: |
     more_set_headers 'Access-Control-Allow-Origin: $http_origin';    

Что мы узнали об Art of the Helm Chart


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


Есть и другие проблемы, которые мы не затронули, такие как тестирование и безопасность. Это был просто взгляд на определенный фрагмент официальных чартов. Я попытался сосредоточиться на паттернах, которые кажутся мне особенно полезными для того, чтобы пользователи могли делать то, что им нужно, с вашими чартами. Официальные чарты Kubernetes были чрезвычайно полезны для меня в работе над чартми Helm для проекта Activity. Надеюсь, объяснение в этом посте поможет побудить других погрузиться в официальное репо и черпать вдохновение из его чартов.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 7: ↑7 and ↓0+7
Comments0

Articles