В данном методе используется инструмент werf от компании Флант для сборки и доставки приложения и их накопленные знания конфигураций CI/CD и деплоя приложений в K8s.
Фактический результат
Имеем микросервисную архитектуру в k8s, где крутятся и деплоятся сервисы на пхп, го, js ... Под каждый микросервис создается отдельный репозиторий, который содержит помимо исходников кода набор кубернетис ресурсов (deployment, configMap, Secret, Job...)
![Пример проекта Laravel Пример проекта Laravel](https://habrastorage.org/getpro/habr/upload_files/478/3c2/7c9/4783c27c9da2dc4ce02d8a25fd6a3cc1.png)
Проблематика
Выкат и настройка каждого микросервиса становится головной болью, по сути каждый микросервис содержит одинаковые описания ресурсов для k8s (копипаст), нет единого места для изменения ресурсов кубурнетиса для проекта, много лишних знаний об инфроструктуре для программистов, да разработчики бывают разные, кто то может все настроить сам, понимает как это все работает, а кто то нет, по сути такая реализация и была задумана для того чтобы упростить настройку деплоя для разработчиков и девопсов.
Желаемый результат
Иметь единый стандартизированный helm репозиторий (php, go, js ...), поддержка семантического версирования чарта, в дочернем проекте иметь только файлы values для определенного окружения с помощью которых настраивать деплой приложения.
![Пример дочернего проекта Laravel Пример дочернего проекта Laravel](https://habrastorage.org/getpro/habr/upload_files/3c1/60c/114/3c160c11461550953949967bc1822c58.png)
Действующие лица
Gitlab, K8s, werf, docker (подразумевается что все доступы к k8s, Gitlab, docker уже имеются)
Поехали!
Werf
Установим werf на сервер Gitlab следуя официальной документации (werf можно использовать без установки на хост запуская image) https://ru.werf.io/documentation/v1.2/index.html?usage=ci&ci=gitlabCiCd&runnerType=hostRunner&os=linux&buildBackend=docker&projectType=simplified&sharedCICD=no&repoType=application
Настроим runner для проекта
Родительский репозиторий
Создаем новый репозиторий laravel-chart, структура файлов
![Структура родительского репозитория Структура родительского репозитория](https://habrastorage.org/getpro/habr/upload_files/75e/d6b/f20/75ed6bf201f3b5e2d80c414bef424b3b.png)
.helm/charts/laravel-chart/Chart.yaml
apiVersion: v2
name: laravel-chart
description: Laravel chart
version: 1.1.1
appVersion: "1.0.0"
.gitlab-ci.yaml подробное описание инструкций можно посмотреть в статье
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
- type trdl && . $(trdl use werf 1.2 stable)
# Активируем werf для gitlab
- type werf && source $(werf ci-env gitlab --as-file)
- |
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:
- |
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)
- |
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')
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)
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:
refs:
- master
tags:
- werf
3. Настроим токены для доступа к родительскому репозиторию, Settings
-> Repository
-> Deploy tokens,
создаём новый токен с правами read_package_registry и write_package_registry
Добавим переменные окружения CI/CD
REPO_NAME - laravel-chart
REPO_PUSH - название нашего токена который создали выше
REPO_PUSH_SECRET - секрет нашего токена который создали выше
Заходим на машину где будем запускать наш CI и и регестируем helm repo
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
Коммитим и пушим наш чарт в репозиторий, после пуша наш чарт должен появится в
Packages & Registries
->Package Registry нашего репозитория
Дочерний репозиторий
1. Создаем новый репозиторий структура файлов
![](https://habrastorage.org/getpro/habr/upload_files/8cf/012/aa1/8cf012aa1fd013bfd0371f5cbc271384.png)
.helm/Chart.yaml
apiVersion: v2
name: laravel
version: 1.0.2
dependencies:
- name: laravel-chart
export-values:
- parent: werf
child: werf
version: ~1.0
repository: "@laravel-chart"
werf.yaml
project: laravel
configVersion: 1
---
image: backend
dockerfile: deploy.Dockerfile
target: backend
---
image: frontend
dockerfile: deploy.Dockerfile
target: frontend
werf-giterminism.yaml (тут мы сознательно отключаем гитерминизм в werf т.к наш чарт не будет находится под гит контролем)
giterminismConfigVersion: 1
helm:
allowUncommittedFiles:
- ".helm/Chart.lock"
- ".helm/charts/*.tgz"
.gitlab-ci.yaml
stages:
- publish-chart
- build
- test
- deploy
- cleanup
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"
MAIN_REPO_NAME: "laravel-chart"
MAIN_HELM_URL: "${CI_SERVER_URL}/api/v4/projects/{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)
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
Publish Charts:
stage: publish-chart
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 $CI_PROJECT_NAME | { egrep "$MAIN_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 "$CLIENT_REPO_PUSH:$CLIENT_REPO_PUSH_SECRET" "$REPO_URL"
else
echo "Chart package $package already exists in Helm repo! Skip!"
fi
done
werf helm repo add --username $CLIENT_REPO_PULL --password $CLIENT_REPO_PULL_SECRET $CI_PROJECT_NAME $HELM_URL
werf helm repo update
only:
- development
- staging
- master
tags: [werf]
except: [schedules]
#подготавливаем образы приложения
Build and Publish:
stage: build
script:
- werf build
except: [schedules]
tags: [werf]
only:
- development
- staging
- master
#запускаем контейнер, запускаем в нем тесты и после прохождения удаляем его
Phpunit:
stage: test
script:
- werf helm dependency update .helm/
- werf converge --skip-build --env testing --values .helm/testing/values.yaml
- werf kube-run backend --log-verbose=true --env testing -- vendor/bin/phpunit --colors=never
- werf dismiss --env testing --with-namespace
only:
- development
- staging
- master
environment:
name: ${CI_COMMIT_REF_SLUG}
tags: [werf]
except: [schedules]
dependencies:
- Build and Publish
.base_deploy:
stage: deploy
tags: [werf]
except: [schedules]
dependencies:
- Build and Publish
#деплоим в дев окружение
Deploy to development:
extends: .base_deploy
script:
- werf helm dependency update .helm/
- werf converge --skip-build --env development --auto-rollback=true --values .helm/development/values.yaml
only:
- development
environment:
name: development
#деплоим в стаг окружение
Deploy to staging:
extends: .base_deploy
script:
- werf helm dependency update .helm/
- werf converge --skip-build --env staging --auto-rollback=true --values .helm/staging/values.yaml
only:
- staging
environment:
name: staging
#деплоим в прод окружение
Deploy to production:
extends: .base_deploy
script:
- werf helm dependency update .helm/
- werf converge --skip-build --env production --auto-rollback=true --values .helm/production/values.yaml
only:
- master
environment:
name: production
#запускаем таск для удаления не нужных образов в регистри
Cleanup:
stage: cleanup
script:
- werf cr login -u nobody -p ${WERF_IMAGES_CLEANUP_PASSWORD} ${WERF_REPO}
- werf cleanup --repo=${WERF_REPO}
only: [schedules]
tags: [werf]
Настроим токены для доступа к репозиторию,
Settings
->Repository
->Deploy tokens,
создаём новые токеныПервый с правами write_package_registry и помещаем полученные значения в переменные окружения дочернего репозитория CLIENT_REPO_PUSH и CLIENT_REPO_PUSH_SECRET
Второй с правами read_package_registry и помещаем полученные значения в переменные окружения дочернего репозиторияCLIENT_REPO_PULL и CLIENT_REPO_PULL_SECRET
Переходим в родительский репозиторий в раздел
Settings
->Repository
->Deploy tokens
и создаем токен с правами read_package_registry, помещаем полученные значения в переменные окружения дочернего репозитория MAIN_REPO_PULL и MAIN_REPO_PULL_SECRET
В статье не рассматриваются тонкости подготовки образов, настройки CI/CD и деплоя приложения используя werf. Это тема отдельной статьи.
Итог
После всех манипуляций мы имеем возможность настроить наш дочерний репозиторий имея только values в проекте, при запуске CI родительский чарт выкачается и запушится в дочерний package registry, имеем поддержку семантического версирования.