В данном методе используется инструмент werf от компании Флант для сборки и доставки приложения и их накопленные знания конфигураций CI/CD и деплоя приложений в K8s.

Фактический результат

Имеем микросервисную архитектуру в k8s, где крутятся и деплоятся сервисы на пхп, го, js ... Под каждый микросервис создается отдельный репозиторий, который содержит помимо исходников кода набор кубернетис ресурсов (deployment, configMap, Secret, Job...)

Пример проекта Laravel
Пример проекта Laravel

Проблематика

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

Желаемый результат

Иметь единый стандартизированный helm репозиторий (php, go, js ...), поддержка семантического версирования чарта, в дочернем проекте иметь только файлы values для определенного окружения с помощью которых настраивать деплой приложения.

Пример дочернего проекта Laravel
Пример дочернего проекта Laravel

Действующие лица

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 для проекта

Родительский репозиторий

  1. Создаем новый репозиторий laravel-chart, структура файлов

Структура родительского репозитория
Структура родительского репозитория
  1. .helm/charts/laravel-chart/Chart.yaml

apiVersion: v2
name: laravel-chart
description: Laravel chart

version: 1.1.1

appVersion: "1.0.0"
  1. .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 - секрет нашего токена который создали выше

  1. Заходим на машину где будем запускать наш 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
  1. Коммитим и пушим наш чарт в репозиторий, после пуша наш чарт должен появится в Packages & Registries -> Package Registry нашего репозитория

Дочерний репозиторий

1. Создаем новый репозиторий структура файлов

.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]
  1. Настроим токены для доступа к репозиторию, Settings -> Repository -> Deploy tokens,создаём новые токены

    1. Первый с правами write_package_registry и помещаем полученные значения в переменные окружения дочернего репозитория CLIENT_REPO_PUSH и CLIENT_REPO_PUSH_SECRET

    2. Второй с правами read_package_registry и помещаем полученные значения в переменные окружения дочернего репозиторияCLIENT_REPO_PULL и CLIENT_REPO_PULL_SECRET

    3. Переходим в родительский репозиторий в раздел Settings -> Repository -> Deploy tokens и создаем токен с правами read_package_registry, помещаем полученные значения в переменные окружения дочернего репозитория MAIN_REPO_PULL и MAIN_REPO_PULL_SECRET

В статье не рассматриваются тонкости подготовки образов, настройки CI/CD и деплоя приложения используя werf. Это тема отдельной статьи.

Итог

После всех манипуляций мы имеем возможность настроить наш дочерний репозиторий имея только values в проекте, при запуске CI родительский чарт выкачается и запушится в дочерний package registry, имеем поддержку семантического версирования.

Комментарии (4)


  1. aigrychev
    14.05.2023 11:27
    +6

    В статье вы демонстрируете сценарий публикации чартов в GitLab Package Registry, который мы со своей стороны никак не продвигаем и не развиваем — возможно зря и это хороший индикатор для нас. Для дистрибуции Helm-чартов мы рекомендуем придерживаться нашего концепта бандлов и сохранять чарты в GitLab Container Registry рядом с образами приложения.

    Заметил, что вы упоминаете гайд версии 1.1 для настройки GitLab-раннера. В этом нет никакой необходимости, т.к. всю необходимую информацию пользователь может найти на вкладках конфигуратора для произвольного раннера (shell, docker, kubernetes) и сценария с werf.

    Спасибо за вашу работу и за то, что делитесь своим опытом с сообществом. ???? от команды werf.


    1. VadimKogay Автор
      14.05.2023 11:27
      +2

      Спасибо и вам ребята за вашу работу. (ссылку на доку поправил)


  1. luxter
    14.05.2023 11:27

    А зачем здесь верфь, если можно обойтись одним helm для деплоя? По идее схема будет проще же. Я из любопытства ради интересуюсь)


    1. VadimKogay Автор
      14.05.2023 11:27

      Werf это инструмент полного цикла от сборки до деплоя, просто процесс сборки и подготовки тут не показан, хотелось бы написать про него тоже, но данный метод будет работать и с чистым хелмом