Обзор задачи

В статье рассматриваются различные варианты настройки CI/CD с использованием GitLab CI/CD и werf.

Конечный pipeline состоит из следующего набора стадий:

  • build-and-publish — стадия сборки и публикации образов приложения;
  • deploy — стадия деплоя приложения для одного из контуров кластера;
  • dismiss — стадия удаления приложения для review окружения;
  • cleanup — стадия очистки хранилища стадий и Docker registry.

Набор контуров (а равно — окружений GitLab) в кластере Kubernetes может варьироваться в зависимости от многих факторов. В статье будут приведены различные варианты организации окружений для следующих:

Далее последовательно рассматриваются стадии pipeline и различные варианты их организации. Изложение построено от общего к частному. В конце статьи приведены окончательные версии .gitlab-ci.yml для готовых workflow.

Независимо от workflow, все версии конфигурации подчиняются следующим правилам:

  • Сборка и публикация выполняется при каждом push в репозитории.
  • Выкат/удаление review окружений:
    • Выкат на review окружение возможен только в рамках Merge Request (MR).
    • Review окружения удаляются с помощью инструментария GitHub (по кнопке в разделе Environment), автоматически при удалении ветки или отсутствии активности в MR в течение суток.
  • Очистка запускается один раз в день по расписанию на master.

Для выкатов review окружения и staging и production окружений предложены самые популярные варианты по организации. Каждый вариант для staging и production окружений сопровождается всевозможными способами отката релиза в production.

С общей информацией по организации CI/CD с помощью werf, а также информацией по конструированию своего workflow, можно ознакомиться в общей статье

Требования

  • Кластер Kubernetes и настроенный для работы с ним kubectl.
  • GitLab-сервер версии выше 10.x либо учетная запись на gitlab.com.
  • Docker registry, встроенный в GitLab или выделенный.
  • Приложение, которое успешно собирается и деплоится с werf.
  • Понимание основных концептов GitLab CI/CD.

Инфраструктура

scheme

  • Кластер Kubernetes.
  • GitLab со встроенным Docker registry.
  • Узел или группа узлов, с предустановленным werf и зависимостями.

Организовать работу werf внутри Docker-контейнера можно, но мы не поддерживаем данный способ. Найти информацию по этому вопросу и обсудить можно в issue. В данном примере и в целом мы рекомендуем использовать shell executor.

Процесс деплоя требует наличия доступа к кластеру через kubectl, поэтому необходимо установить и настроить kubectl на узле, с которого будет запускаться werf. Если не указывать конкретный контекст опцией --kube-context или переменной окружения WERF_KUBE_CONTEXT, то werf будет использовать контекст kubectl по умолчанию.

В конечном счете werf требует наличия доступа на используемых узлах:

  • к Git-репозиторию кода приложения;
  • к Docker registry;
  • к кластеру Kubernetes.

Настройка runner

На узле, где предполагается запуск werf, установим и настроим GitLab-runner:

  1. Создадим проект в GitLab и добавим push кода приложения.
  2. Получим токен регистрации GitLab-runner’а:
    • заходим в проекте в GitLab Settings —> CI/CD;
    • во вкладке Runners необходимый токен находится в секции Setup a specific Runner manually.
  3. Установим GitLab-runner по инструкции.
  4. Зарегистрируем gitlab-runner, выполнив шаги за исключением следующих моментов:
    • используем werf в качестве тега runner’а;
    • используем shell в качестве executor для runner’а.
  5. Добавим пользователя gitlab-runner в группу docker.

    sudo usermod -aG docker gitlab-runner
    
  6. Установим Docker и настроим kubectl, если они не были установлены ранее.
  7. Установим зависимости werf.
  8. Установим multiwerf пользователем gitlab-runner:

    sudo su gitlab-runner
    mkdir -p ~/bin
    cd ~/bin
    curl -L https://raw.githubusercontent.com/flant/multiwerf/master/get.sh | bash
    
  9. Скопируем файл конфигурации kubectl в домашнюю папку пользователя gitlab-runner.
    mkdir -p /home/gitlab-runner/.kube &&
    sudo cp -i /etc/kubernetes/admin.conf /home/gitlab-runner/.kube/config &&
    sudo chown -R gitlab-runner:gitlab-runner /home/gitlab-runner/.kube
    

После того, как GitLab-runner настроен, можно переходить к настройке pipeline.

Сборка и публикация образов приложения

Build and Publish:
  stage: build-and-publish
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf build-and-publish
  except: [schedules]
  tags: [werf]

Забегая вперед, очистка хранилища стадий и Docker registry предполагает запуск соответствующего задания по расписанию. Так как при очистке не требуется выполнять сборку образов, то указываем except: [schedules], чтобы стадия сборки не запускалась в случае работы pipeline по расписанию.

Конфигурация задания достаточно проста, поэтому хочется сделать акцент на том, чего в ней нет — явной авторизации в Docker registry, вызова docker login.

В простейшем случае, при использовании встроенного Docker registry, авторизация выполняется автоматически при вызове команды werf ci-env. В качестве необходимых аргументов используются переменные окружения GitLab CI_JOB_TOKEN (подробнее про модель разграничения доступа при выполнении заданий в GitLab можно прочитать здесь) и CI_REGISTRY_IMAGE.

При вызове werf ci-env создаётся временный docker config, который используется всеми командами в shell-сессии (в том числе docker). Таким образов, параллельные задания никак не пересекаются при использовании docker и временный токен в конфигурации не перетирается.

Если необходимо выполнить авторизацию с произвольными учётными данными, docker login должен выполняться после вызова werf ci-env (подробнее про авторизацию в отдельной статье).

Выкат приложения

Прежде всего необходимо описать шаблон, общую часть для деплоя в любой контур, что позволит уменьшить размер файла .gitlab-ci.yml, улучшит его читаемость, а также позволит далее сосредоточиться на workflow.

.base_deploy: &base_deploy
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf deploy --set "global.ci_url=$(echo ${CI_ENVIRONMENT_URL} | cut -d / -f 3)"
  dependencies:
    - Build and Publish
  except: [schedules]
  tags: [werf]

При использовании шаблона base_deploy для каждого контура будет определяться своё окружение GitLab:

Example:
  <<: *base_deploy
  environment:
    name: <environment name>
    url: <url>
    ...
  ...

При выполнении задания, werf ci-env устанавливает переменную WERF_ENV в соответствии с именем окружения GitLab (CI_ENVIRONMENT_SLUG).

Для того, чтобы по-разному конфигурировать приложение для используемых контуров кластера в helm-шаблонах можно использовать Go-шаблоны и переменную .Values.global.env, что соответствует значению опции --env или переменной окружения WERF_ENV.

Также в шаблоне используется адрес окружения, URL для доступа к разворачиваемому в контуре приложению, который передаётся параметром global.ci_url. Это значение может использоваться в helm-шаблонах, например, для конфигурации Ingress-ресурсов.

Далее будут представлены популярные стратегии и практики, на базе которых мы предлагаем выстраивать ваши процессы в GitLab.

Варианты организации review окружения

Как уже было сказано ранее, review окружение — это временный контур, поэтому помимо выката, у этого окружения также должна быть и очистка.

Рассмотрим базовые конфигурации Review и Stop Review заданий, которые лягут в основу всех предложенных вариантов.

Review:
  <<: *base_deploy
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    url: http://${CI_PROJECT_NAME}-${CI_MERGE_REQUEST_ID}.kube.DOMAIN
    on_stop: Stop Review
    auto_stop_in: 1 day
  artifacts:
    paths:
      - werf.yaml
  only: [merge_requests]

Stop Review: 
  stage: dismiss
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf dismiss --with-namespace
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    action: stop
  variables:
    GIT_STRATEGY: none
  dependencies:
    - Review
  only: [merge_requests]
  when: manual
  tags: [werf]

В задание Review описывается выкат review-релиза в динамическое окружение, в основу имени которого закладывается уникальный идентификатор MR. Параметр auto_stop_in позволяет указать период отсутствия активности в MR, после которого окружение GitLab будет автоматически остановлено. Остановка окружения GitLab сама по себе никак не влияет на ресурсы в кластере, review-релиз, поэтому в дополнении необходимо определить задание, которое вызывается при остановке (on_stop). В нашем случае, это задание Stop Review.

Задание Stop Review выполняет удаление review-релиза, а также остановку окружения GitLab (action: stop): werf удаляет helm-релиз и namespace в Kubernetes со всем его содержимым (werf dismiss). Задание Stop Review может быть запущено вручную после деплоя на review контур, а также автоматически GitLab-сервером, например, при удалении соответствующей ветки в результате слияния ветки с master и указания соответствующей опции в интерфейсе GitLab.

Для выполнения werf dismiss требуется werf.yaml, так как в нём содержаться шаблоны имени релиза и namespace. При удалении ветки нет возможности использовать исходники из git, поэтому в задании Stop Review используется werf.yaml, сохранённый при выполнении задания Review, и отключено подтягивание изменений из git (GIT_STRATEGY: none).

Таким образом, по умолчанию закладываем следующие варианты удаления review окружения:

  • вручную;
  • автоматически при отсутствии активности MR в течение суток и при удалении ветки.

Далее разберём основные стратегии при организации выката review окружения.

Мы не ограничиваем вас предложенными вариантами, даже напротив — рекомендуем комбинировать их и создавать конфигурацию workflow под нужды вашей команды

№1 Вручную

Данный вариант реализует подход описанный в разделе Выкат на review из pull request по кнопке

При таком подходе пользователь выкатывает и удаляет окружение по кнопке в pipeline.

Он самый простой и может быть удобен в случае, когда выкаты происходят редко и review окружение не используется при разработке. По сути, для проверки перед принятием MR.

Review:
  <<: *base_deploy
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    url: http://${CI_PROJECT_NAME}-${CI_MERGE_REQUEST_ID}.kube.DOMAIN
    on_stop: Stop Review
    auto_stop_in: 1 day
  artifacts:
    paths:
      - werf.yaml
  only: [merge_requests]
  when: manual

Stop Review: 
  stage: dismiss
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf dismiss --with-namespace
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    action: stop
  variables:
    GIT_STRATEGY: none
  dependencies:
    - Review
  only: [merge_requests]
  when: manual
  tags: [werf]

№2 Автоматически по имени ветки

Данный вариант реализует подход описанный в разделе Выкат на review из ветки по шаблону автоматически

В предложенном ниже варианте автоматический релиз выполняется для каждого коммита в MR, в случае, если имя git-ветки имеет префикс review-.

Review:
  <<: *base_deploy
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    url: http://${CI_PROJECT_NAME}-${CI_MERGE_REQUEST_ID}.kube.DOMAIN
    on_stop: Stop Review
    auto_stop_in: 1 day
  artifacts:
    paths:
      - werf.yaml
  rules:
    - if: $CI_MERGE_REQUEST_ID && $CI_COMMIT_REF_NAME =~ /^review-/

Stop Review:
  stage: dismiss
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf dismiss --with-namespace
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    action: stop
  variables:
    GIT_STRATEGY: none
  dependencies:
    - Review
  only: [merge_requests]
  when: manual
  tags: [werf]

№3 Полуавтоматический режим с лейблом (рекомендованный)

Данный вариант реализует подход описанный в разделе Выкат на review из pull request автоматически после ручной активации

Полуавтоматический режим с лейблом — это комплексное решение, объединяющие первые два варианта.

При проставлении специального лейбла, в примере ниже review, пользователь активирует автоматический выкат в review окружения для каждого коммита. При снятии лейбла происходит остановка окружения GitLab, удаление review-релиза.

Review:
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - >
      # do optional deploy/dismiss
      
      if [ -z "$PRIVATE_TOKEN" ]; then
        echo "\$PRIVATE_TOKEN is not defined" >&2
        exit 1
      fi

      api_url=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}

      if ! response_body=$(curl -sS --header "PRIVATE-TOKEN: ${PRIVATE_TOKEN}" ${api_url}); then
        echo "GET ${api_url}"
        echo ${response_body}
        exit 1
      fi

      if ! echo ${response_body} | jq .labels[] >/dev/null 2>&1; then
        echo "GET ${api_url}"
        echo ${response_body}
        exit 1
      fi

      if echo ${response_body} | jq .labels[] | grep -q '^"review"$'; then
        werf deploy --set "global.ci_url=$(echo ${CI_ENVIRONMENT_URL} | cut -d / -f 3)"
      else
        if werf helm get $(werf helm get-release) 2>/dev/null; then
          werf dismiss --with-namespace
        fi
      fi
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    url: http://${CI_PROJECT_NAME}-${CI_MERGE_REQUEST_ID}.kube.DOMAIN
    on_stop: Stop Review
    auto_stop_in: 1 day
  artifacts:
    paths:
      - werf.yaml
  dependencies:
    - Build and Publish
  only: [merge_requests]
  tags: [werf]

Stop Review:
  stage: dismiss
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf dismiss --with-namespace
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    action: stop
  variables:
    GIT_STRATEGY: none
  dependencies:
    - Review
  only: [merge_requests]
  when: manual
  tags: [werf]

Для проверки наличия лейбла у MR используется GitLab API. Так как токена CI_JOB_TOKEN недостаточно для работы с private репозиториями, необходимо сгенерировать специальный токен PRIVATE_TOKEN.

Варианты организации staging и production окружений

Предложенные далее варианты являются наиболее эффективными комбинациями правил выката staging и production окружений.

В нашем случае, данные окружения являются определяющими, поэтому названия вариантов соответствуют названиям окончательных готовых workflow, предложенных в конце статьи.

№1 Fast and Furious (рекомендованный)

Данный вариант реализует подходы описанные в разделах Выкат на production из master автоматически и Выкат на production-like из pull request по кнопке

Выкат в production происходит автоматически при любых изменениях в master. Выполнить выкат в staging можно по кнопке в MR.

Deploy to Staging:
  <<: *base_deploy
  environment:
    name: staging
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
  only: [merge_requests]
  when: manual

Deploy to Production:
  <<: *base_deploy
  environment:
    name: production
    url: https://www.company.org
  only: [master]

Варианты отката изменений в production:

  • revert изменений в master (рекомендованный);
  • выкат стабильного MR или воспользовавшись кнопкой Rollback.

№2 Push the Button

Данный вариант реализует подходы описанные в разделах Выкат на production из master по кнопке и Выкат на staging из master автоматически

Выкат production осуществляется по кнопке у коммита в master, а выкат в staging происходит автоматически при любых изменениях в master.

Deploy to Staging:
  <<: *base_deploy
  environment:
    name: staging
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
  only: [master]

Deploy to Production:
  <<: *base_deploy
  environment:
    name: production
    url: https://www.company.org
  only: [master]
  when: manual  

Варианты отката изменений в production:

  • по кнопке у стабильного коммита или воспользовавшись кнопкой Rollback (рекомендованный);
  • выкат стабильного MR и нажатии кнопки.

№3 Tag everything (рекомендованный)

Данный вариант реализует подходы описанные в разделах Выкат на production из тега автоматически и Выкат на staging из master по кнопке

Выкат в production выполняется при проставлении тега, а в staging по кнопке у коммита в master.

Deploy to Staging:
  <<: *base_deploy
  environment:
    name: staging
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
  only: [master]
  when: manual

Deploy to Production:
  <<: *base_deploy
  environment:
    name: production
    url: https://www.company.org
  only:
    - tags

Варианты отката изменений в production:

  • нажатие кнопки на другом теге (рекомендованный);
  • создание нового тега на старый коммит (так делать не надо).

№4 Branch, branch, branch!

Данный вариант реализует подходы описанные в разделах Выкат на production из ветки автоматически и Выкат на production-like из ветки автоматически

Выкат в production происходит автоматически при любых изменениях в ветке production, а в staging при любых изменениях в ветке master.

Deploy to Staging:
  <<: *base_deploy
  environment:
    name: staging
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
  only: [master]

Deploy to Production:
  <<: *base_deploy
  environment:
    name: production
    url: https://www.company.org
  only: [production]

Варианты отката изменений в production:

Очистка образов

Cleanup:
  stage: cleanup
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - docker login -u nobody -p ${WERF_IMAGES_CLEANUP_PASSWORD} ${WERF_IMAGES_REPO}
    - werf cleanup
  only: [schedules]
  tags: [werf]

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

Чтобы использовать очистку, необходимо создать Personal Access Token в GitLab с необходимыми правами. С помощью данного токена будет осуществляться авторизация в Docker registry перед очисткой.

Для вашего тестового проекта вы можете просто создать Personal Access Token а вашей учетной записи GitLab. Для этого откройте страницу Settings в GitLab (настройки вашего профиля), затем откройте раздел Access Token. Укажите имя токена, в разделе Scope отметьте api и нажмите Create personal access token — вы получите Personal Access Token.

Чтобы передать Personal Access Token в переменную окружения GitLab откройте ваш проект, затем откройте Settings —> CI/CD и разверните Variables. Создайте новую переменную окружения WERF_IMAGES_CLEANUP_PASSWORD и в качестве ее значения укажите содержимое Personal Access Token. Для безопасности отметьте созданную переменную как protected.

Стадия очистки запускается только по расписанию, которое вы можете определить открыв раздел CI/CD —> Schedules настроек проекта в GitLab. Нажмите кнопку New schedule, заполните описание задания и определите шаблон запуска в обычном cron-формате. В качестве ветки оставьте master (название ветки не влияет на процесс очистки), отметьте Active и сохраните pipeline.

Полный .gitlab-ci.yml для готовых workflow

Детали workflow

Подробнее про workflow можно почитать в отдельной статье

.gitlab-ci.yml

stages:
  - build-and-publish
  - deploy
  - dismiss
  - cleanup

Build and Publish:
  stage: build-and-publish
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf build-and-publish
  except: [schedules]
  tags: [werf]

.base_deploy: &base_deploy
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf deploy --set "global.ci_url=$(echo ${CI_ENVIRONMENT_URL} | cut -d / -f 3)"
  dependencies:
    - Build and Publish
  tags: [werf]

Review:
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - >
      # do optional deploy/dismiss
      
      if [ -z "$PRIVATE_TOKEN" ]; then
        echo "\$PRIVATE_TOKEN is not defined" >&2
        exit 1
      fi

      api_url=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}

      if ! response_body=$(curl -sS --header "PRIVATE-TOKEN: ${PRIVATE_TOKEN}" ${api_url}); then
        echo "GET ${api_url}"
        echo ${response_body}
        exit 1
      fi

      if ! echo ${response_body} | jq .labels[] >/dev/null 2>&1; then
        echo "GET ${api_url}"
        echo ${response_body}
        exit 1
      fi

      if echo ${response_body} | jq .labels[] | grep -q '^"review"$'; then
        werf deploy --set "global.ci_url=$(echo ${CI_ENVIRONMENT_URL} | cut -d / -f 3)"
      else
        if werf helm get $(werf helm get-release) 2>/dev/null; then
          werf dismiss --with-namespace
        fi
      fi
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    url: http://${CI_PROJECT_NAME}-${CI_MERGE_REQUEST_ID}.kube.DOMAIN
    on_stop: Stop Review
    auto_stop_in: 1 day
  artifacts:
    paths:
      - werf.yaml
  dependencies:
    - Build and Publish
  only: [merge_requests]
  tags: [werf]

Stop Review:
  stage: dismiss
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf dismiss --with-namespace
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    action: stop
  variables:
    GIT_STRATEGY: none
  dependencies:
    - Review
  only: [merge_requests]
  when: manual
  tags: [werf]

Deploy to Staging:
  <<: *base_deploy
  environment:
    name: staging
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
  only: [merge_requests]
  when: manual

Deploy to Production:
  <<: *base_deploy
  environment:
    name: production
    url: https://www.company.org
  only: [master]

Cleanup:
  stage: cleanup
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - docker login -u nobody -p ${WERF_IMAGES_CLEANUP_PASSWORD} ${WERF_IMAGES_REPO}
    - werf cleanup
  only: [schedules]
  tags: [werf]

Детали workflow

Подробнее про workflow можно почитать в отдельной статье

.gitlab-ci.yml

stages:
  - build-and-publish
  - deploy
  - dismiss
  - cleanup

Build and Publish:
  stage: build-and-publish
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf build-and-publish
  except: [schedules]
  tags: [werf]

.base_deploy: &base_deploy
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf deploy --set "global.ci_url=$(echo ${CI_ENVIRONMENT_URL} | cut -d / -f 3)"
  dependencies:
    - Build and Publish
  except: [schedules]
  tags: [werf]

Review:
  <<: *base_deploy
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    url: http://${CI_PROJECT_NAME}-${CI_MERGE_REQUEST_ID}.kube.DOMAIN
    on_stop: Stop Review
    auto_stop_in: 1 day
  artifacts:
    paths:
      - werf.yaml
  only: [merge_requests]
  when: manual

Stop Review: 
  stage: dismiss
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf dismiss --with-namespace
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    action: stop
  variables:
    GIT_STRATEGY: none
  dependencies:
    - Review
  only: [merge_requests]
  when: manual
  tags: [werf]

Deploy to Staging:
  <<: *base_deploy
  environment:
    name: staging
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
  only: [master]

Deploy to Production:
  <<: *base_deploy
  environment:
    name: production
    url: https://www.company.org
  only: [master]
  when: manual  

Cleanup:
  stage: cleanup
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - docker login -u nobody -p ${WERF_IMAGES_CLEANUP_PASSWORD} ${WERF_IMAGES_REPO}
    - werf cleanup
  only: [schedules]
  tags: [werf]

Детали workflow

Подробнее про workflow можно почитать в отдельной статье

.gitlab-ci.yml

stages:
  - build-and-publish
  - deploy
  - dismiss
  - cleanup

Build and Publish:
  stage: build-and-publish
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf build-and-publish
  except: [schedules]
  tags: [werf]

.base_deploy: &base_deploy
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf deploy --set "global.ci_url=$(echo ${CI_ENVIRONMENT_URL} | cut -d / -f 3)"
  dependencies:
    - Build and Publish
  except: [schedules]
  tags: [werf]

Review:
  <<: *base_deploy
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
    on_stop: Stop Review
    auto_stop_in: 1 day
  artifacts:
    paths:
      - werf.yaml
  only: [merge_requests]
  when: manual

Stop Review: 
  stage: dismiss
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf dismiss --with-namespace
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    action: stop
  variables:
    GIT_STRATEGY: none
  dependencies:
    - Review
  only: [merge_requests]
  when: manual
  tags: [werf]

Deploy to Staging:
  <<: *base_deploy
  environment:
    name: staging
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
  only: [master]
  when: manual

Deploy to Production:
  <<: *base_deploy
  environment:
    name: production
    url: https://www.company.org
  only: [tags]

Cleanup:
  stage: cleanup
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - docker login -u nobody -p ${WERF_IMAGES_CLEANUP_PASSWORD} ${WERF_IMAGES_REPO}
    - werf cleanup
  only: [schedules]
  tags: [werf]

Детали workflow

Подробнее про workflow можно почитать в отдельной статье

.gitlab-ci.yml

stages:
  - build-and-publish
  - deploy
  - dismiss
  - cleanup

Build and Publish:
  stage: build-and-publish
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf build-and-publish
  except: [schedules]
  tags: [werf]

.base_deploy: &base_deploy
  stage: deploy
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf deploy --set "global.ci_url=$(echo ${CI_ENVIRONMENT_URL} | cut -d / -f 3)"
  dependencies:
    - Build and Publish
  except: [schedules]
  tags: [werf]

Review:
  <<: *base_deploy
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
    on_stop: Stop Review
    auto_stop_in: 1 day
  artifacts:
    paths:
      - werf.yaml
  rules:
    - if: $CI_MERGE_REQUEST_ID && $CI_COMMIT_REF_NAME =~ /^review-/

Stop Review: 
  stage: dismiss
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - werf dismiss --with-namespace
  environment:
    name: review-${CI_MERGE_REQUEST_ID}
    action: stop
  variables:
    GIT_STRATEGY: none
  dependencies:
    - Review
  only: [merge_requests]
  when: manual
  tags: [werf]

Deploy to Staging:
  <<: *base_deploy
  environment:
    name: staging
    url: http://${CI_PROJECT_NAME}.kube.DOMAIN
  only: [master]

Deploy to Production:
  <<: *base_deploy
  environment:
    name: production
    url: https://www.company.org
  only: [production]
    
Cleanup:
  stage: cleanup
  script:
    - type multiwerf && . $(multiwerf use 1.1 stable --as-file)
    - type werf && source $(werf ci-env gitlab --as-file)
    - docker login -u nobody -p ${WERF_IMAGES_CLEANUP_PASSWORD} ${WERF_IMAGES_REPO}
    - werf cleanup
  only: [schedules]
  tags: [werf]