GitLab – это мощный и в то же время простой инструмент для организации проектов. Как и любой крупный и самодостаточный продукт, GitLab постоянно развивается и дорабатывается. И сегодня хотелось бы обсудить новый функционал, который пока ещё находится в разработке, но уже доступен для использования. Речь идёт о поддержке размещения Helm-чартов в GitLab Package Registry. Для простоты далее я буду называть его GitLab Helm repo.
Зачастую основой для описания инфраструктуры, запускаемой в Kubernetes, являются Helm-чарты. Поэтому при работе команды инженеров с большим количеством проектов невольно приходят мысли о стандартизации подходов работы с этими чартами. С появлением GitLab 14.1 появилась возможность настраивать хранение общих чартов для всех проектов, с которыми ведется работа.
С использованием же GitLab Helm repo можно иметь один репозиторий в корпоративном GitLab, в котором хранить общие Helm-чарты для часто используемых компонентов инфраструктуры и элементов «кубернетизации» приложений. Например, Helm-чарт для stateless-приложений, который из коробки включает в себя манифесты для полной настройки следующих ресурсов Kubernetes:
Как это работает? Когда вы устанавливаете и настраиваете GitLab для нового проекта, у него создаётся «дочерний» репозиторий. Его CI завязан на подключение к вашему корпоративному хранилищу, скачивание из него последних актуальных версий Helm-чартов и повторный push в локальный репозиторий проекта. Такое простое взаимодействие даёт возможность модернизировать, дорабатывать и фиксить все компоненты в одном месте, а дочерние репозитории сами подтянут актуальные изменения. Кроме того, таким образом намного ближе становится решение вопроса о стандартизации подхода при оформлении новых проектов.
Пошагово поднимем несколько репозиториев, и я наглядно покажу, как можно организовать взаимодействие между ними. Скажу наперёд, в CI будем использовать werf.
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) и создаём переменные окружения:
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
Не забудьте заменить переменные окружения реальными значениями!
werf helm repo update
werf helm search repo my-charts
NAME CHART VERSION APP VERSION DESCRIPTION
my-charts/my-chart 1.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. Теперь разберём переменные окружения, которые используются в настройках проекта:
Первый — с правами write_package_registry. Полученные данные помещаем в переменные окружения:
На этом этапе мы закончили настройку дочернего репозитория. Если запустить pipeline на основной ветке в такой конфигурации, Job скачает из главного Helm repo все чарты, которые указаны в качестве зависимостей в файле .helm/Chart.yaml. Это добавляет дополнительной гибкости, так как позволяет включать в проект только те Helm-чарты, которые в нём требуются.
В данной реализации CI для дочернего репозитория из главного Helm repo скачивается только последний актуальный Helm-чарт. Можно доработать CI таким образом, чтобы он повторно push’ил все доступные версии Helm-чартов. Можно добавить какую-нибудь логику, которой вам будет удобно пользоваться, но так как это выходит за рамки статьи, я воспользуюсь правом не приводить примеры (кто сказал про рендер и валидацию Helm-чарта перед push’ем?..).
На этом этапе мы закончили настройку всех компонентов. Теперь при push’е пакета с Helm-чартом в родительский Package registry его можно будет скачать через CI дочерних (клиентский) репозиториев. В дополнение можно настраивать CI по своим нуждам — например, сделать так, чтобы дочерние CI запускались по расписанию и автоматом подтягивали обновления чартов к себе (без явного запуска пайплайна руками).
Зачастую основой для описания инфраструктуры, запускаемой в Kubernetes, являются Helm-чарты. Поэтому при работе команды инженеров с большим количеством проектов невольно приходят мысли о стандартизации подходов работы с этими чартами. С появлением GitLab 14.1 появилась возможность настраивать хранение общих чартов для всех проектов, с которыми ведется работа.
Немного про концепцию
До появления соответствующего функционала в GitLab для подобных задач можно было использовать, например, ChartMuseum или вовсе не иметь единого хранилища для чартов, а хранить исходники манифестов прямо в репозитории с кодом приложения. Конечно, с точки зрения функциональности это не имело каких-то явных недостатков, но в смысле стандартизации подхода оформления проектов со временем — по мере увеличения количества проектов — всё превращалось в большую кашу.С использованием же GitLab Helm repo можно иметь один репозиторий в корпоративном GitLab, в котором хранить общие Helm-чарты для часто используемых компонентов инфраструктуры и элементов «кубернетизации» приложений. Например, Helm-чарт для stateless-приложений, который из коробки включает в себя манифесты для полной настройки следующих ресурсов Kubernetes:
- Deployment;
- ConfigMap;
- Secret;
- Service;
- HPA;
- VPA;
- PDB.
Как это работает? Когда вы устанавливаете и настраиваете GitLab для нового проекта, у него создаётся «дочерний» репозиторий. Его CI завязан на подключение к вашему корпоративному хранилищу, скачивание из него последних актуальных версий Helm-чартов и повторный push в локальный репозиторий проекта. Такое простое взаимодействие даёт возможность модернизировать, дорабатывать и фиксить все компоненты в одном месте, а дочерние репозитории сами подтянут актуальные изменения. Кроме того, таким образом намного ближе становится решение вопроса о стандартизации подхода при оформлении новых проектов.
Реализация основного репозитория
Пошагово поднимем несколько репозиториев, и я наглядно покажу, как можно организовать взаимодействие между ними. Скажу наперёд, в CI будем использовать werf.
Приступим к созданию «главного» репозитория, в котором будут храниться общие Helm-чарты.Использование werf для реализации идеи, рассматриваемой в статье, не является обязательным, но, так как этот инструмент обеспечивает комплексный подход для работы с сабчартами и мы владеем огромной экспертизой в нём, то весь процесс становится проще.
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.
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
Не забудьте заменить переменные окружения реальными значениями!
6. Commit’им, push’им изменения, создаем новый тег (например, my-chart-1.0.0) и переходим в созданный pipeline. После окончания Job’а переходим в Packages & Registries -> Package Registry и проверяем, что наш Helm-чарт там присутствует.Примечание: для своего удобства я не стал создавать отдельную пару токен/Secret для GitLab Runner’а и поэтому буду использовать те, что получил в п.3. Но в реальных кейсах мы рекомендуем для настройки GitLab Runner’ов генерировать отдельный токен с правами только на read_package_registry.
7. Проверим состояние репозитория на GitLab Runner’е через werf:Примечание: я использую деплой по тегу, потому что в такой вариации мы всегда сможем быстро найти, с какого коммита был запущен тот или иной Helm-чарт. К тому же, бывают случаи, когда package registry утерян: в такой ситуации, когда после восстановления package registry он пустой, мы сможем за-push’ить только последние версии Helm-чартов, которые будут в main-ветке. Если какое-то приложение использует устаревшую версию Helm-чарта, то возникнут трудности. Имея на каждую версию чарта свой тег, мы сможем быстро восстановить для приложения необходимый компонент.
werf helm repo update
werf helm search repo my-charts
NAME CHART VERSION APP VERSION DESCRIPTION
my-charts/my-chart 1.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 в дочернем репозитории.
- Переходим в главный репозиторий во вкладку 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 главного репозитория.
Первый — с правами write_package_registry. Полученные данные помещаем в переменные окружения:
- CLIENT_REPO_PUSH — название токена,
- CLIENT_REPO_PUSH_SECRET — Secret токена.
- CLIENT_REPO_PULL — название токена,
- CLIENT_REPO_PULL_SECRET — Secret токена.
На этом этапе мы закончили настройку дочернего репозитория. Если запустить 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-чартами, как говорится, «не отходя от кассы»!Используем GitLab в качестве удобного Helm-репозитория
GitLab – это мощный и в то же время простой инструмент для организации проектов. Как и любой крупный и самодостаточный продукт, GitLab постоянно развивается и дорабатывается. И сегодня хотелось бы...
habr.com