Используем GitLab в качестве удобного Helm-репозитория

от автора

GitLab – это мощный и в то же время простой инструмент для организации проектов. Как и любой крупный и самодостаточный продукт, GitLab постоянно развивается и дорабатывается. И сегодня хотелось бы обсудить новый функционал, который пока ещё находится в разработке, но уже доступен для использования. Речь идёт о поддержке размещения Helm-чартов в GitLab Package Registry. Для простоты далее я буду называть его GitLab Helm repo.

Зачастую основой для описания инфраструктуры, запускаемой в Kubernetes, являются Helm-чарты. Поэтому при работе команды инженеров с большим количеством проектов невольно приходят мысли о стандартизации подходов работы с этими чартами. С появлением GitLab 14.1 появилась возможность настраивать хранение общих чартов для всех проектов, с которыми ведется работа.

Немного про концепцию

До появления соответствующего функционала в GitLab для подобных задач можно  было использовать, например, ChartMuseum или вовсе не иметь единого хранилища для чартов, а хранить исходники манифестов прямо в репозитории с кодом приложения. Конечно, с точки зрения функциональности это не имело каких-то явных недостатков, но в смысле стандартизации подхода оформления проектов со временем — по мере увеличения количества проектов — всё превращалось в большую кашу.

С использованием же GitLab Helm repo можно иметь один репозиторий в корпоративном GitLab, в котором хранить общие Helm-чарты для часто используемых компонентов инфраструктуры и элементов «кубернетизации» приложений. Например, Helm-чарт для stateless-приложений, который из коробки включает в себя манифесты для полной настройки следующих ресурсов Kubernetes:

  • Deployment;

  • ConfigMap;

  • Secret;

  • Service;

  • HPA;

  • VPA;

  • PDB.

Представьте: у вас есть только values.yaml, где описана вся логика работы приложения, а все нужные манифесты отрендерятся сами при деплое.

Как это работает? Когда вы устанавливаете и настраиваете GitLab для нового проекта, у него создаётся «дочерний» репозиторий. Его CI завязан на подключение к вашему корпоративному хранилищу, скачивание из него последних актуальных версий Helm-чартов и повторный push в локальный репозиторий проекта. Такое простое взаимодействие даёт возможность модернизировать, дорабатывать и фиксить все компоненты в одном месте, а дочерние репозитории сами подтянут актуальные изменения. Кроме того, таким образом намного ближе становится решение вопроса о стандартизации подхода при оформлении новых проектов.

Реализация основного репозитория

Пошагово поднимем несколько репозиториев, и я наглядно покажу, как можно организовать взаимодействие между ними. Скажу наперёд, в CI будем использовать werf.

Использование werf для реализации идеи, рассматриваемой в статье, не является обязательным, но, так как этот инструмент обеспечивает комплексный подход для работы с сабчартами и мы владеем огромной экспертизой в нём, то весь процесс становится проще.

Приступим к созданию «главного» репозитория, в котором будут храниться общие Helm-чарты.

1. Создаём новый репозиторий:

2. Создаём структуру каталогов и базовые файлы для организации CI/CD:

. ├── .gitlab-ci.yml ├── .helm │   ├── Chart.lock │   ├── charts │   │   └── my-chart │   │       ├── Chart.yaml │   │       ├── templates │   │       │   └── main.yaml │   │       └── values.yaml │   └── Chart.yaml ├── README.md └── werf.yaml

Для простоты в качестве main.yaml я буду использовать примитивный манифест ConfigMap, который просто печатает название чарта:

#---- .helm/charts/my-chart/templates/main.yaml apiVersion: v1 kind: ConfigMap metadata:   name: {{ .Chart.Name }} data:   file: |     {{ .Chart.Name }}  #---- werf.yaml project: charts-repo configVersion: 1

Хочется уделить больше внимания файлу .gitlab-ci.yml. Он может читаться сложно, поэтому я постарался прокомментировать длинные и необычные команды:

stages: - publish-charts  variables:   REPO_URL: "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts"  before_script:   - set -eo pipefail   # Активируем werf.   - type trdl && . $(trdl use werf 1.2 stable)   - type werf && source $(werf ci-env GitLab --as-file)   - |     # Обновляем доступные Helm repo через werf.     werf helm repo update     # Ищем все файлы с описанием чартов и используем их для построения зависимостей.     find . -type f -regex '.*/\(Chart.ya?ml\|requirements.ya?ml\)' -exec \       sh -c 'werf helm dependency build $(dirname "{}") --skip-refresh' \;  "publish charts":   stage: publish-charts   script:   - |     # Пробегаем по всем директориям с чартами и упаковываем чарты, помещая их в директорию .packages.     mkdir -p .packages     while read chart; do       echo "[PACKAGING CHART $chart]"       werf helm package "$chart" -d .packages     done < <(find .helm/charts -mindepth 1 -maxdepth 1 -type d)   - |     # Заполняем 2 переменные: CHART_NAME и CHART_VERSION.     find .packages -mindepth 1 -maxdepth 1 -type f -name '*.tgz' -exec sh -c 'basename "$0"' '{}' \; | while read package; do       CHART_NAME=$(echo $package | sed -e 's/-[0-9]\.[0-9]\.[0-9]\.tgz$//g')       CHART_VERSION=$(echo $package | sed -e 's/^[a-zA-Z-].*-//g' | sed -e 's/.tgz$//g')       # используя переменные выше, устанавливаем, есть ли уже чарт с таким именем и версией в Helm repo       CHART_EXISTS=$(werf helm search repo -l $REPO_NAME/$CHART_NAME | { egrep "$REPO_NAME/$CHART_NAME\s"||true; } | { egrep "$CHART_VERSION\s"||true; } | wc -l)       # если его нет, то пушим в package registry, иначе выводим сообщение, что чарт уже присутствует в Helm repo       if [ $CHART_EXISTS = 0 ]; then         curl -sSl --post301 --form "chart=@.packages/$package" --user "$REPO_PUSH:$REPO_PUSH_SECRET" "$REPO_URL"       else         echo "Chart package $package already exists in Helm repo! Skip!"       fi     done   only:   - tags

Для использования этого CI (то есть для push’а чарта в package registry) нам нужно создать некоторые ключи и оформить их в виде переменных окружения. Рассмотрим их подробнее на следующем шаге.

3. Переходим в настройки Repository (Settings -> Repository -> Deploy tokens) и создаём токен с правами read_package_registry и write_package_registry.

4. Переходим в настройки CI/CD (Settings -> CI/CD -> Variables) и создаём переменные окружения:

  • REPO_NAME — алиас (например, my-charts);

  • REPO_PUSH — название токена из п.3;

  • REPO_PUSH_SECRET — Secret для токена из п.3.

5. Заходим на машину с GitLab Runner’ом, на котором будет запускаться CI данного проекта, и регистрируем на нём Helm repo (команды werf helm здесь аналогичны обычным командам helm):

werf helm repo add --username $REPO_PUSH --password $REPO_PUSH_SECRET $REPO_NAME ${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/helm/stable werf helm repo update

Не забудьте заменить переменные окружения реальными значениями!

Примечание: для своего удобства я не стал создавать отдельную пару токен/Secret для GitLab Runner’а и поэтому буду использовать те, что получил в п.3. Но в реальных кейсах мы рекомендуем для настройки GitLab Runner’ов генерировать отдельный токен с правами только на read_package_registry.

6. Commit’им, push’им изменения, создаем новый тег (например, my-chart-1.0.0) и переходим в созданный pipeline. После окончания Job’а переходим в Packages & Registries -> Package Registry и проверяем, что наш Helm-чарт там присутствует.

Примечание: я использую деплой по тегу, потому что в такой вариации мы всегда сможем быстро найти, с какого коммита был запущен тот или иной Helm-чарт. К тому же, бывают случаи, когда package registry утерян: в такой ситуации, когда после восстановления package registry он пустой, мы сможем за-push’ить только последние версии Helm-чартов, которые будут в main-ветке. Если какое-то приложение использует устаревшую версию Helm-чарта, то возникнут трудности. Имея на каждую версию чарта свой тег, мы сможем быстро восстановить для приложения необходимый компонент.

7. Проверим состояние репозитория на GitLab Runner’е через werf:

werf helm repo update werf helm search repo my-charts  NAME              CHART VERSIONAPP VERSIONDESCRIPTION my-charts/my-chart1.0.0 

Видим, что Helm-чарт появился в репозитории. Полдела сделано!

Реализация дочернего репозитория

Репозиторий на стороне проекта, который будет использовать общий чарт, настраиваем немного иначе.

1. Создаём пустой репозиторий и приводим его структуру к следующему виду:

. ├── .gitlab-ci.yml ├── .helm │   └── Chart.yaml ├── README.md └── werf.yaml

2. Рассмотрим содержимое ключевых файлов:

#---- .helm/Chart.yaml apiVersion: v2 name: client-charts-repo version: 1.0.0 dependencies: - name: my-chart   version: ~1.0   repository: "@my-charts"
#---- werf.yaml project: client-charts-repo configVersion: 1
#---- .gitlab-ci.yml stages: - publish-charts  variables:   REPO_URL: "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts"   HELM_URL: "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/helm/stable"  default:   before_script:   - set -eo pipefail   - type trdl && . $(trdl use werf 1.2 stable)   - type werf && source $(werf ci-env GitLab --as-file)  .base_publish_charts:   stage: publish-charts   script: |     werf helm repo add --force-update --username $MAIN_REPO_PULL --password $MAIN_REPO_PULL_SECRET $MAIN_REPO_NAME $MAIN_HELM_URL     werf helm repo update     werf helm dependency update .helm/     find .helm/charts -mindepth 1 -maxdepth 1 -type f -name '*.tgz' -exec sh -c 'basename "$0"' '{}' \; | while read package; do       CHART_NAME=$(echo $package | sed -e 's/-[0-9]\.[0-9]\.[0-9]\.tgz$//g')       CHART_VERSION=$(echo $package | sed -e 's/^[a-zA-Z-].*-//g' | sed -e 's/.tgz$//g')       CHART_EXISTS=$(werf helm search repo $REPO_NAME | { egrep "$REPO_NAME/$CHART_NAME\s" || true; } | { egrep "$CHART_VERSION\s" || true; } | wc -l)       if [ $CHART_EXISTS = 0 ]; then         curl -sSl --post301 --form "chart=@.helm/charts/$package" --user "$REPO_PUSH:$REPO_PUSH_SECRET" "$REPO_URL"       else         echo "Chart package $package already exists in Helm repo! Skip!"       fi     done     werf helm repo add --username $REPO_PULL --password $REPO_PULL_SECRET $REPO_NAME $HELM_URL     werf helm repo update     echo "Настройка на ПК инженера"     echo "REPO_URL: $REPO_URL"     echo "werf helm repo add --username $REPO_PULL --password $REPO_PULL_SECRET $REPO_NAME $HELM_URL"   rules:   - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'     when: on_success     allow_failure: true  "publish charts":   extends:   - .base_publish_charts   tags:   - werf

3. Теперь разберём переменные окружения, которые используются в настройках проекта:

  • MAIN_REPO_PULL — название токена с правами read_package_registry в главном репозитории.

  • MAIN_REPO_PULL_SECRET — Secret токена с правами read_package_registry в главном репозитории.

  • MAIN_REPO_NAME — алиас главного репозитория.

  • MAIN_HELM_URL — URL для доступа в главный репозиторий.

  • CLIENT_REPO_NAME — алиас дочернего репозитория.

  • CLIENT_REPO_PUSH — название токена с правами write_package_registry в дочернем репозитории.

  • CLIENT_REPO_PUSH_SECRET — Secret токена с правами write_package_registry в дочернем репозитории.

  • CLIENT_REPO_PULL — название токена с правами read_package_registry в дочернем репозитории.

  • CLIENT_REPO_PULL_SECRET — Secret токена с правами read_package_registry в дочернем репозитории.

4. Настроим переменные окружения дочернего репозитория для доступа в главный репозиторий:

  • Переходим в главный репозиторий во вкладку Settings -> Repository -> Deploy tokens и создаём новый токен с правами read_package_registry.

  • Помещаем полученные значение в переменные окружения MAIN_REPO_PULL и MAIN_REPO_PULL_SECRET дочернего репозитория.

    • MAIN_REPO_NAME  берём из REPO_NAME, который задавали в п.4 при настройке главного репозитория, MAIN_HELM_URL должна соответствовать значению ${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/helm/stable главного репозитория.

5. В дочернем репозитории переходим во вкладку Settings -> Repository -> Deploy tokens и создаём два токена.

Первый — с правами write_package_registry. Полученные данные помещаем в переменные окружения:

  • CLIENT_REPO_PUSH — название токена,

  • CLIENT_REPO_PUSH_SECRET — Secret токена.

Второй — с правами read_package_registry. Полученные данные помещаем в переменные окружения:

  • CLIENT_REPO_PULL — название токена,

  • CLIENT_REPO_PULL_SECRET — Secret токена.

Также придумываем алиас для Helm repo и помещаем его в переменную CLIENT_REPO_NAME. Например, CLIENT_REPO_NAME = client-charts-repo.

На этом этапе мы закончили настройку дочернего репозитория. Если запустить pipeline на основной ветке в такой конфигурации, Job скачает из главного Helm repo все чарты, которые указаны в качестве зависимостей в файле .helm/Chart.yaml. Это добавляет дополнительной гибкости, так как позволяет включать в проект только те Helm-чарты, которые в нём требуются.

В данной реализации CI для дочернего репозитория из главного Helm repo скачивается только последний актуальный Helm-чарт. Можно доработать CI таким образом, чтобы он повторно push’ил все доступные версии Helm-чартов. Можно добавить какую-нибудь логику, которой вам будет удобно пользоваться, но так как это выходит за рамки статьи, я воспользуюсь правом не приводить примеры (кто сказал про рендер и валидацию Helm-чарта перед push’ем?..).

На этом этапе мы закончили настройку всех компонентов. Теперь при push’е пакета с Helm-чартом в родительский Package registry его можно будет скачать через CI дочерних (клиентский) репозиториев. В дополнение можно настраивать CI по своим нуждам — например, сделать так, чтобы дочерние CI запускались по расписанию и автоматом подтягивали обновления чартов к себе (без явного запуска пайплайна руками).

Выводы

В статье мы рассмотрели, как научить GitLab нужного проекта выкачивать Helm-чарты из централизованного хранилища в другом репозитории. Это очень удобная и полезная функция в GitLab. Нам давно не хватало подобной возможности, и вот наконец мы можем управлять Helm-чартами, как говорится, «не отходя от кассы»!

P.S.

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


ссылка на оригинал статьи https://habr.com/ru/articles/667338/


Комментарии

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

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