Автоматизируем перевод с деплоя bash-скриптами на helmfile при помощи Python: инструкция по применению

—

от автора

Выпускник курса Python для инженеров Максим Дубакин рассказал о рабочем проекте собственного производства, который заавтоматизировал повторяющиеся задачи по переводу с деплоя bash-скриптами на helmfile при помощи Python и уменьшил затраты времени на ~ 2 часа.

Проблема

Команда использует helm для деплоя приложений в K8s. Ранее helm-чарты деплоились с помощью bash-скриптов, которые в свою очередь запускали еще bash-скрипты. Это крайне неудобно с точки зрения поддержки и дебага: скрипты хоть и имели общую форму, но могли быть произвольно дописаны разработчиками. В итоге это превращалось в большую кашу.

Нашли решение, им оказался [helmfile] (https://github.com/helmfile/helmfile). Очень удобная программа, которая позволяет запускать несколько helm-чартов из одного места, указав при этом их последовательность. Это полезно, если у вас есть несколько окружений (у Максима — production / stage / QA), и каждые окружения имеют свои values файлы.

В компании более 250 сервисов, а переводить на helmfile приходилось вручную — это занимало порядка 6–12 часов на подготовку файлов, тестирование и перекатку каждой среды. В QA-средах используются helm-чарты для инфраструктуры типа баз данных, кешей и очередей, поэтому количество values файлов и длина helmfile могли быть существенными.

Пример типичного helmfile для QA:

```yaml environments:  default:    values:      - VERSION: {{ requiredEnv "version" | quote }}      - NAMESPACE: {{ requiredEnv "namespace" | quote }}      - CLUSTER: {{ requiredEnv "cluster" | quote }}      - RELEASE: <name_of_service> --- templates:  default: &default    namespace: {{ .Values.NAMESPACE }}    missingFileHandler: Warn   releases:  - name: "{{ .Values.RELEASE }}"    chart: artifactory/unified    <<: *default    version: "2.5.3"    values:      - values/common.yaml.gotmpl      - values/app.yaml.gotmpl      - values/override/{{.Values.CLUSTER}}/{{.Values.NAMESPACE}}/common.yaml      - values/override/{{.Values.CLUSTER}}/{{.Values.NAMESPACE}}/app.yaml    needs:      - "{{ .Values.RELEASE }}-migration"    - name: "{{ .Values.RELEASE }}-job"    ...    needs:      - {{ .Values.RELEASE }}    # migration  - name: "{{ .Values.RELEASE }}-migration"    ...    needs:      - "{{ .Values.RELEASE }}-drop-restore-db"   - name: "{{ .Values.RELEASE }}-drop-restore-db"    ...    needs:      - "{{ .Values.RELEASE }}-sku-generator"      - "pgsql-{{ .Values.RELEASE }}"      - "{{ .Values.RELEASE }}-rabbitmq"      - "{{ .Values.RELEASE }}-webdav"      - "{{ .Values.RELEASE }}-wiremock"      - "{{ .Values.RELEASE }}-mailhog"    # sku-generator  - name: "{{ .Values.RELEASE }}-sku-generator"    ...   # postgres  - name: "pgsql-{{ .Values.RELEASE }}"    ...    # rabbitmq  - name: "{{ .Values.RELEASE }}-rabbitmq"    ...   # webdav  - name: "{{ .Values.RELEASE }}-webdav"    ...   # wiremock  - name: "{{ .Values.RELEASE }}-wiremock"    ...    # mailhog  - name: "{{ .Values.RELEASE }}-mailhog"    ... bases:  - "../_helmfile.d/helm.yaml"  - ../_helmfile.d/repositories.yaml ```

Первым релизом идет основной helm-чарт приложения, затем — все его инфраструктурные зависимости. Под ... скрыты похожие части с указанием чарта, его версии и values файлов.

Здесь явно видна проблема ручного повторяющегося труда.

Решение

Во время перевода сервисов Максим как раз учился на курсе и решил в свободное время написать инструмент, который бы просто создавал файловую структуру проекта на helmfile, а также пустые файлы values и helmfile.yaml. Но со временем его проект начал разрастаться. Вот, что из этого вышло.

Как работает программа:

1. Принимает на вход: название проекта, нужно ли использовать миграции, в какой папке создавать проект, в какой namespace будет деплоиться приложение.

2. Создает структуру проекта в одном из репозиториев с helmfile (для production и QA мы используем разные репозитории).

3. Создает шаблоны файлов helmfile.yaml и пустые values для возможности override с помощью merge шаблонов jinja2 на стороне приложения (values и helmfile.yaml) и общих шаблонов gotmpl (шаблоны отдельных релизов и их values по умолчанию), которые хранятся в репозитории с helmfile.

4. Внутри helmfile будут сразу все возможные инфразависимости, но они будут закоменчены, так что остается только раскоментить нужные и удалить ненужные.

Результат

Таким образом, команда OPS перестала самостоятельно создавать все папки и файлы, указывать вручную кучу values и добавлять в helmfile.yaml те чарты, которые нужны.

Теперь про фичи:

1. Программа helmfile_template использует gotmpl шаблоны внутри репозитория в helmfile. Вот пример шаблона релиза:

```yaml pgsql: &pgsql  <<: *default  name: pgsql-{{ .Values.releaseName }}  chart: artifactory/postgres-instance  version: 0.4.2  values:    - ../_helmfile.d/templates/values/pgsql.yaml.gotmpl    - values/pgsql.yaml.gotmpl    - values/override/{{ .Values.CLUSTER }}/{{ .Values.NAMESPACE }}/pgsql.yaml ```

А вот шаблон values для pgsql:

```yaml pgsql:  version: 12  instances: 1  limits:    cpu: 200m    memory: 512Mi  requests:    cpu: 200m    memory: 512Mi  storageClass: ceph-block   database:  - name: {{ .Values.releaseName | replace "-" "_" }}    size: 10   service:  annotations:    external-dns.alpha.kubernetes.io/hostname: {{ .Release.Name }}{{ .Values.IngressPostfix }} ```

Как вы видите, первым values-файлом используется - ../_helmfile.d/templates/values/pgsql.yaml.gotmpl. Это как раз шаблон с values по умолчанию, о котором писалось выше. Если требуется, они могут быть перезаписаны с помощью файлов в папке с проектом.

2. Используются шаблоны [jinja2] (https://jinja.palletsprojects.com/en/3.1.x/).

3. Программа распространяется как один исполняемый файл. Для этого используется модуль [pyinstaller] (https://pyinstaller.org/en/stable/).

4. Программа может работать как в интерактивном режиме, так и запускаться с аргументами. Для этого используется модуль [argparse] (https://docs.python.org/3/library/argparse.html), который позволяет выводить классный help с помощью ключа -h.

Получение справки:

```bash helmfile_template -h usage: helmfile_template [-h] [-v] [-g] [-s PROJECT_NAME] [-p] [--no-production] [-n NAMESPACE] [-u] [--no-unified]                         [-m] [--no-migration]   Get helmfile templates.   options:  -h, --help            show this help message and exit  -v, --version         show program's version number and exit  -g, --get-help        get templates  -s PROJECT_NAME, --name PROJECT_NAME  -p, --production      use deploy repository  --no-production       use deploy repository  -n NAMESPACE, --namespace NAMESPACE  -u, --unified         use unified helmchart  --no-unified          don't use unified helmchart  -m, --migration       use migration  --no-migration        don't use migration ```

Пример работы в интерактивном режиме:

```bash $ helmfile_template ****************************** Введите название проекта: slurm ****************************** ****************************** 1. deploy 2. qa-deploy Выберите репозиторий (default: 2): 2 ****************************** Использовать unified? (y/n) (default: n): y ****************************** Нужны миграции? (y/n) (default: n): n Проект slurm был создан в папке /Users/<my_user>/<some_path>/qa-deploy. ```

Тот же проект, но созданный с помощью аргументов:

```bash $ helmfile_template -s slurm --no-production -u --no-migration Проект slurm был создан в папке /Users/<my_user>/<some_path>/qa-deploy. ```

Результат работы программы

Файловая структура:

``` slurm ├── helmfile.yaml └── values    ├── app.yaml.gotmpl    ├── common.yaml.gotmpl    ├── job.yaml.gotmpl    └── override ```

Содержимое helmfile.yaml:

```yaml {{ tpl (readFile "../_helmfile.d/environments.tmpl") . }} --- environments:  default:    values:      - releaseName: "slurm" --- {{ tpl (readFile "../_helmfile.d/templates.tmpl") . }} {{ tpl (readFile "../_helmfile.d/bases.yaml") . }} releases: - <<: *unified  labels:    type: app # - <<: *unified #   name: {{ .Values.releaseName }}-{{`{{ .Release.Labels.type }}`}} #   labels: #     type: job #   needs: #     - "pgsql-{{ .Values.releaseName }}" #     - "{{ .Values.releaseName }}-mysql" #     - "{{ .Values.releaseName }}-es" #     - "{{ .Values.releaseName }}-kafka" #     - "{{ .Values.releaseName }}-rabbitmq" #     - "{{ .Values.releaseName }}-redis" #     - "{{ .Values.releaseName }}-webdav" #     - "{{ .Values.releaseName }}-saml" # - <<: *pgsql # - <<: *mysql # - <<: *elastic # - <<: *kafka # - <<: *rabbitmq # - <<: *redis-standalone # - <<: *redis-sentinel # - <<: *webdav # - <<: *saml ```

Предположим, что сервису slurm нужны pgsql, kafka и saml. В таком случае можно немного отредактировать файл, и сервис готов к деплою:

```yaml {{ tpl (readFile "../_helmfile.d/environments.tmpl") . }} --- environments:  default:    values:      - releaseName: "slurm" --- {{ tpl (readFile "../_helmfile.d/templates.tmpl") . }} {{ tpl (readFile "../_helmfile.d/bases.yaml") . }} releases: - <<: *unified  labels:    type: app  needs:    - "pgsql-{{ .Values.releaseName }}"    - "{{ .Values.releaseName }}-kafka"    - "{{ .Values.releaseName }}-saml"   - <<: *pgsql   - <<: *kafka   - <<: *saml ```

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

Вместо заключения

Максим поделился о себе и о курсе, который изучал:

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

Я научился работать с модулями / библиотеками для работы с файлами, запросами. Получил неплохой опыт в написании программ больше 100 строк, научился разбивать программу на модули и использовать ООП в работе. Также написал свой Ansible модуль и Prometheus Exporter. Чего давно хотел, но не хватало знаний.

Больше всего мне понравилась сложность курса. Я тратил 8–12 часов на каждую «курсовую» работу, потом приходил грамотный и подробный ревью, и я еще часик-два тратил на исправление замечаний. Первую половину курса было очень сложно, но, пройдя через это, я получил колоссальный опыт».


ссылка на оригинал статьи https://habr.com/ru/company/southbridge/blog/705620/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *